How to Set Up Zero-Downtime Deployments with Docker

Restarting a single container to deploy a new version causes a brief service interruption. This guide covers running multiple replicas so Docker can update them one at a time, keeping the application available throughout the deployment.

The Core Approach: Rolling Updates

Instead of one container, run multiple instances behind Nginx, updating them one at a time — at every moment, at least one instance remains available to serve traffic.

Prerequisites

  • Docker Compose installed
  • Nginx as a reverse proxy in front of the application

Step 1 — Run Multiple Replicas

services:
  app:
    image: myapp:latest
    deploy:
      replicas: 3
    ports:
      - "3000-3002:3000"

Note: the deploy.replicas key works fully with Docker Swarm; for plain Docker Compose, define multiple explicit services instead (see below) if not using Swarm mode.

Alternative for Plain Docker Compose (Non-Swarm)

services:
  app1:
    image: myapp:latest
    ports:
      - "3001:3000"
  app2:
    image: myapp:latest
    ports:
      - "3002:3000"
  app3:
    image: myapp:latest
    ports:
      - "3003:3000"

Step 2 — Configure Nginx to Load Balance Across Replicas

upstream backend {
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

Step 3 — Add Health Checks

services:
  app1:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      retries: 3

See How to Add Health Check Endpoints to Your Application for the application side of this.

Step 4 — Perform a Rolling Update Script

sudo nano /usr/local/bin/rolling-deploy.sh
#!/bin/bash
set -e

docker compose pull app1
docker compose up -d --no-deps app1
sleep 15
curl -f http://127.0.0.1:3001/health || exit 1

docker compose pull app2
docker compose up -d --no-deps app2
sleep 15
curl -f http://127.0.0.1:3002/health || exit 1

docker compose pull app3
docker compose up -d --no-deps app3
sleep 15
curl -f http://127.0.0.1:3003/health || exit 1

echo "Rolling deployment complete"

Each instance is updated and verified healthy before moving to the next — Nginx continues routing to the remaining healthy instances throughout, so users experience no interruption.

Step 5 — Remove Nginx's Reference to an Unhealthy Instance Temporarily (Advanced)

For extra safety, mark an instance as down in the Nginx upstream during its own update window:

upstream backend {
    server 127.0.0.1:3001 down;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}
sudo systemctl reload nginx

Using Docker Swarm for Native Rolling Updates (Alternative)

Docker Swarm mode natively supports deploy.replicas and rolling update configuration without the manual scripting above:

services:
  app:
    image: myapp:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first

order: start-first starts the new container before stopping the old one, ensuring capacity is never reduced during the update.

Common Errors

Brief errors during deployment despite multiple replicas — verify health checks are actually gating traffic correctly, and that Nginx isn't routing to an instance still starting up.

Database connection pool exhaustion during rolling updates — temporarily running extra instances (old + new simultaneously) increases total database connections; ensure your connection pool limits account for this overlap.

Best Practices

  • Always verify health before routing traffic to a newly updated instance
  • Run at least 2-3 replicas minimum for meaningful zero-downtime capability
  • Consider Docker Swarm or Kubernetes if you need this pattern at larger scale

Continue Reading

Browse more articles in DevOps & CI/CD.

  • zero downtime deployment, rolling update, docker deployment, high availability deployment
  • 0 Users Found This Useful
Was this answer helpful?

Related Articles

How to Set Up a Self-Hosted GitHub Actions Runner on a VPS

GitHub Actions' hosted runners work well for most projects, but a self-hosted runner on your own...

How to Deploy Automatically on Git Push (Webhook-Based Deployment)

Automating deployment whenever you push to a specific branch removes the manual "SSH in and pull"...

How to Set Up Blue-Green Deployment on a VPS

Blue-green deployment runs two identical production environments — only one live at a time...

How to Use Ansible for Server Configuration Management

Ansible automates server configuration through simple, human-readable YAML files — letting...

Infrastructure as Code Basics: Managing VPS Config with Terraform

Terraform lets you define infrastructure (VPS instances, networks, DNS records) as code, applied...