Taking down your API to ship a new version is acceptable at 3 AM with three users. In production, it's not. Here's a deployment pattern I've used that achieves zero downtime with nothing more than Docker, Nginx, and a short script.
The two-upstream trick
Run two instances of your app behind Nginx — app-blue and app-green. At any time, one is live and the other is idle. A deploy spins up the idle one, waits for it to be healthy, then flips Nginx's upstream.
upstream app_live {
server app-blue:3000;
}
server {
location / {
proxy_pass http://app_live;
proxy_set_header Host $host;
}
}
Health checks before the switch
Never flip the upstream until the new container passes a health check. A simple /health endpoint returning 200 OK after the app boots is enough:
until curl -sf http://localhost:3001/health; do
echo "waiting..."
sleep 1
done
The sequence
- Build the new image.
- Start it on the idle port.
- Poll
/healthuntil it responds. - Update Nginx upstream to point at the new container.
- Reload Nginx (
nginx -s reload— no dropped connections). - Stop the old container.
What about rollbacks?
Because the previous container is still defined, a rollback is just flipping the upstream back. You can be back online in seconds.
The beauty of this approach is its simplicity — no Kubernetes, no service mesh, just containers and a reverse proxy doing what they do best.