Back to Home
Infrastructure

Zero-Downtime Infrastructure on a Single VPS

Maximizing utility and keeping operational costs near zero with Docker, Nginx, and GitHub Actions.

For a bootstrapped SaaS, operational costs matter. While PaaS platforms like Vercel or Heroku offer great velocity, they can get expensive fast. I wanted to achieve the same professional, zero-downtime deployment pipeline while squeezing maximum utility out of a low-cost VPS.

By using Docker and Nginx, I built a containerized architecture that strictly separates concerns and routes traffic via subdomains:

  • api. - Core backend and WebSocket server
  • app. - Business logic and dashboard
  • menu. - Customer-facing QR interface
  • auth. - Isolated authentication services
  • static. - Gzipped scripts, user uploads, and compressed WebP images

Routing all static assets through a dedicated static subdomain means that when traffic eventually scales, we can shift that entire subdomain behind a CDN with zero code changes.

Atomic CI/CD Deployments

The biggest challenge of self-hosting is handling updates without kicking active users offline. I automated the entire deployment pipeline using GitHub Actions.

[ Developer ] --> (git push) --> [ GitHub Actions ]
                                       |
                                  (Build Image)
                                       |
                                  (Push to GHCR)
                                       |
                               (SSH into VPS)
                                       |
[ Nginx Proxy ] <============== [ Docker Compose ]
   (Traffic)                    1. Pull new image
                                2. Start new container
                                3. Wait for health check
                                4. Route traffic to new
                                5. Kill old container

When code is pushed to the main branch, GitHub Actions builds the Docker image and pushes it to the GitHub Container Registry. It then SSHs into the VPS, pulls the image, and spins up the new container alongside the old one.

Nginx acts as the reverse proxy. It waits for the new container to pass its health checks before it atomically switches the traffic over. The old container is then gracefully spun down. This guarantees 0% deployment downtime and keeps operational costs near-zero for the first 1,000 users.