Docker 99 🐳 Zero-Downtime Rolling Deployments and Blue-Green Container Deployment Patterns
Zero-downtime deployment is not a single technique but a family of patterns that share one requirement: the old version must keep serving traffic until the new version is proven ready. The two patterns that matter most in Docker environments are rolling updates, which replace replicas incrementally, and blue-green, which runs two complete environments and switches traffic atomically. The choice between them is determined by the cost of running duplicate capacity, the need for instant rollback, and whether the application can tolerate two versions running simultaneously.
Key point: Zero downtime is not automatic. A rolling update only achieves it when the service has spare capacity for the new replica to start before the old one stops, and when the application’s health check accurately reports readiness. A blue-green switch only achieves it when the proxy reload is graceful and the health check passes before the switch.
Why these patterns exist
The downtime problem. The naive deployment pattern is stop-then-start: stop the old container, start the new one, and accept the gap. In a Docker Compose stack, docker compose up -d after an image change recreates containers, and the recreation is not instantaneous. For a single-replica service, the gap is the container start time. For a service with users, that gap is downtime.
The rollback problem. A deployment that replaces the old version immediately has no rollback path except redeploying the old image. If the new version fails health checks or produces errors under real traffic, the time to detect and revert is the time the users experience the failure. Blue-green addresses this by keeping the old environment running until the new one is validated. Rolling updates address it through the orchestrator’s failure action, which can automatically roll back a failed update .
The capacity problem. Blue-green requires double the capacity during the deployment window: two full environments running side by side. Rolling updates require only enough spare capacity for one extra replica. For a resource-constrained host, rolling is the only viable option. For a service where instant rollback is worth the cost, blue-green is the better trade .
a. Rolling Updates with Docker Swarm
Docker Swarm has native support for rolling updates through the update_config block in the service definition. The key parameters are parallelism (how many replicas to update at a time), delay (how long to wait between batches), order (start-first or stop-first), and failure_action (what to do when an update fails) .
The order: start-first setting is what enables zero downtime. It starts the new replica before stopping the old one, so the service briefly has more replicas than its desired count. Without start-first, the default stop-first order stops the old replica before starting the new one, and the service is degraded during the gap.
services:
api:
image: myapp/api:${IMAGE_TAG}
deploy:
replicas: 4
update_config:
parallelism: 1
delay: 15s
order: start-first
monitor: 60s
failure_action: rollback
max_failure_ratio: 0
rollback_config:
parallelism: 2
delay: 5s
order: stop-first
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 20s
The monitor window is the period after a task starts during which Swarm observes whether it stays running. If the task stops within the window, the update counts as failed, and failure_action: rollback triggers an automatic revert . The health check is what makes the monitor window meaningful: without it, Swarm only knows whether the process is running, not whether the application is ready.
A critical caveat: start-first requires spare capacity. If the host is already at its memory or CPU limit, the new replica cannot start, and the update fails. The rolling update is zero-downtime only when the infrastructure has room for the overlap .
b. Blue-Green with an Nginx Reverse Proxy
Blue-green runs two identical environments—blue and green—behind a reverse proxy. Only one is live at a time. The deployment process builds the new version in the idle color, health-checks it directly, and then switches the proxy’s upstream to the new color. The switch is a configuration file change and a graceful Nginx reload, not a container restart .
The proxy configuration uses an upstream block that points to the active color. Switching is a file swap:
# active_upstream.conf
upstream app_active {
server app-blue:8080;
# server app-green:8080; # inactive
}
The deployment script detects the current live color, starts the new version in the idle color, waits for the health endpoint to return 200, swaps the upstream file, and reloads Nginx. The reload is graceful: existing connections complete, and new connections go to the new upstream .
#!/bin/bash
# deploy.sh — blue-green switch
NEW_TAG=$1
CURRENT=$(cat active_color.txt)
if [ "$CURRENT" = "blue" ]; then
NEW_COLOR="green"
else
NEW_COLOR="blue"
fi
# Start new version in idle color
docker compose up -d --build app-$NEW_COLOR
# Health check (10 retries, 3s apart)
for i in {1..10}; do
if curl -sf http://app-$NEW_COLOR:8080/health; then
break
fi
sleep 3
done
# Switch upstream and reload
echo "upstream app_active { server app-$NEW_COLOR:8080; }" > nginx/active_upstream.conf
docker compose exec nginx nginx -s reload
# Verify
curl -sf http://localhost/health && echo "Switched to $NEW_COLOR"
echo "$NEW_COLOR" > active_color.txt
The critical detail is that the health check must target the new color directly, bypassing the proxy. Checking through the proxy verifies the old color, not the new one. The verification after the switch confirms that traffic is actually reaching the new color .
A shared database is required for blue-green to work. If each color had its own database, the switch would lose data written during the deployment. The shared database means schema migrations must be backward-compatible: the old color must tolerate the new schema, or rollback will break .
c. Choosing Between the Patterns
The choice depends on four factors: capacity, rollback speed, database complexity, and orchestration.
| Factor | Rolling Update | Blue-Green |
|---|---|---|
| Capacity required | One extra replica | Full duplicate environment |
| Rollback speed | Automatic on failure, but only to previous image | Instant switch back to running old environment |
| Database | Standard migrations | Requires backward-compatible migrations |
| Orchestration | Swarm, Kubernetes, ECS | Any proxy + two environments |
| Resource cost | Low | High during deployment |
For a stateless API with four replicas on a host with spare capacity, rolling is the right choice. For a service where a failed deployment must be reverted in seconds, and the budget allows running two full environments, blue-green is the right choice. For a service on a single host without Swarm, a simple blue-green with Nginx is the most direct path to zero downtime.
Complete Example Session
# ============================================
# PART 1: SWARM ROLLING UPDATE
# ============================================
docker service create \
--name api \
--replicas 4 \
--update-parallelism 1 \
--update-delay 15s \
--update-order start-first \
--update-failure-action rollback \
--update-monitor 60s \
--health-cmd "curl -f http://localhost:8000/health || exit 1" \
--health-interval 10s \
--health-retries 3 \
myregistry/api:v1.0.0
# Deploy new version
docker service update --image myregistry/api:v1.1.0 api
# Monitor
docker service ps api --filter desired-state=running
# ============================================
# PART 2: BLUE-GREEN WITH NGINX
# ============================================
# Initial state: blue is live
# nginx/active_upstream.conf:
# upstream app_active { server app-blue:8080; }
# ============================================
# PART 3: DEPLOY NEW VERSION TO GREEN
# ============================================
docker compose up -d --build app-green
# ============================================
# PART 4: HEALTH CHECK GREEN DIRECTLY
# ============================================
for i in {1..10}; do
if curl -sf http://localhost:8082/health; then
echo "Green is healthy"
break
fi
sleep 3
done
# ============================================
# PART 5: SWITCH UPSTREAM
# ============================================
echo "upstream app_active { server app-green:8080; }" > nginx/active_upstream.conf
docker compose exec nginx nginx -s reload
# ============================================
# PART 6: VERIFY TRAFFIC
# ============================================
curl -s http://localhost/health
# Should return green's response
# ============================================
# PART 7: ROLLBACK IF NEEDED
# ============================================
echo "upstream app_active { server app-blue:8080; }" > nginx/active_upstream.conf
docker compose exec nginx nginx -s reload
# ============================================
# PART 8: ZERO-DOWNTIME PROOF
# ============================================
# In a separate terminal, run:
while true; do curl -s -o /dev/null -w "%{http_code} " http://localhost/; sleep 0.2; done
# Run the deploy script
# The output should be an unbroken stream of 200s
The ten parts trace the two patterns. Parts 1 shows the Swarm rolling update with the key flags. Parts 3 through 6 show the blue-green switch with the health check and the proxy reload.
Quick Reference
Swarm Rolling Update Flags
| Flag | Purpose |
|---|---|
--update-parallelism | Replicas updated at once |
--update-delay | Wait between batches |
--update-order start-first | New replica before old stops |
--update-failure-action rollback | Auto-rollback on failure |
--update-monitor | Observation window after start |
Blue-Green Components
| Component | Purpose |
|---|---|
| Two environments | Blue and green, identical |
| Reverse proxy | Nginx with upstream switch |
| Health endpoint | Direct check on new color |
| Shared database | Data persistence across switch |
| Graceful reload | nginx -s reload |
When to Use Which
| Scenario | Pattern |
|---|---|
| Spare capacity limited | Rolling |
| Instant rollback required | Blue-green |
| No orchestrator | Blue-green with Nginx |
| Database migrations | Rolling (simpler) |
| Full validation before switch | Blue-green |
Best Practices
✅ Do This:
# Use start-first for zero-downtime rolling
order: start-first # ✅
# Health check the new color directly, bypassing the proxy
curl -sf http://app-green:8080/health # ✅
# Use graceful reload for the proxy switch
nginx -s reload # ✅
# Keep the old color running until the new one is verified
# Instant rollback is the point of blue-green # ✅
❌ Don’t Do This:
# Use stop-first and expect zero downtime
order: stop-first # old stops before new starts # ❌
# Health check through the proxy during blue-green deploy
# This checks the old color, not the new one # ❌
# Run incompatible schema migrations during blue-green
# The old color must tolerate the new schema for rollback # ❌
# Assume start-first works without spare capacity
# The new replica cannot start if the host is full # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Rolling update causes downtime | stop-first order used | Use start-first |
| New replica never becomes healthy | Insufficient capacity | Add capacity or reduce replicas |
| Blue-green switch causes errors | Proxy not reloaded gracefully | Use nginx -s reload |
| Rollback fails | Database schema incompatible | Backward-compatible migrations |
| Health check passes but app is broken | Health check too shallow | Check a real dependency |
Real-World Examples
1. Swarm Rolling Update
docker service update --image myapp:v2 --update-order start-first myapp
2. Swarm Auto-Rollback
failure_action: rollback
3. Blue-Green Upstream File
upstream app_active { server app-green:8080; }
4. Graceful Nginx Reload
nginx -s reload
5. Direct Health Check
curl -sf http://app-green:8080/health
6. Rollback Switch
upstream app_active { server app-blue:8080; }
7. Shared Database
# Both colors point to the same DB service
8. Zero-Downtime Test
while true; do curl -s -o /dev/null -w "%{http_code} " http://localhost/; sleep 0.2; done
9. Health Check in Compose
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
10. Monitor Window
monitor: 60s
Visual
Rolling Update with start-first
┌──────────────────────────────────────────────────────────────┐
│ BEFORE: 4 replicas of v1 │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ v1 │ │ v1 │ │ v1 │ │ v1 │ │
│ └────┘ └────┘ └────┘ └────┘ │
│ │
│ STEP 1: start v2, keep v1 (5 replicas briefly) │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ v1 │ │ v1 │ │ v1 │ │ v1 │ │ v2 │ ← new starts │
│ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ │
│ STEP 2: stop one v1 (back to 4) │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ v1 │ │ v1 │ │ v1 │ │ v2 │ │
│ └────┘ └────┘ └────┘ └────┘ │
│ │
│ REPEAT until all are v2. │
│ The service never drops below 4 replicas. │
│ │
└──────────────────────────────────────────────────────────────┘
Blue-Green Switch
┌──────────────────────────────────────────────────────────────┐
│ BEFORE │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Nginx upstream → blue ││
│ └─────────────────────────────────────────────────────────┘│
│ ┌─────────┐ ┌─────────┐ │
│ │ BLUE v1 │ │ GREEN │ ← idle, no traffic │
│ │ (live) │ │ (idle) │ │
│ └─────────┘ └─────────┘ │
│ │
│ DEPLOY: build v2 in green, health-check green directly │
│ ┌─────────┐ ┌─────────┐ │
│ │ BLUE v1 │ │ GREEN v2│ ← healthy, ready │
│ │ (live) │ │ (ready) │ │
│ └─────────┘ └─────────┘ │
│ │
│ SWITCH: swap upstream file, nginx -s reload │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Nginx upstream → green ││
│ └─────────────────────────────────────────────────────────┘│
│ ┌─────────┐ ┌─────────┐ │
│ │ BLUE v1 │ │ GREEN v2│ ← now live │
│ │ (idle) │ │ (live) │ │
│ └─────────┘ └─────────┘ │
│ │
│ ROLLBACK: swap back, reload. Blue is still running. │
│ │
└──────────────────────────────────────────────────────────────┘
Zero-Downtime Proof
┌──────────────────────────────────────────────────────────────┐
│ Terminal 1: continuous requests │
│ while true; do curl -s -o /dev/null -w "%{http_code} "; │
│ sleep 0.2; done │
│ │
│ Terminal 2: deploy script │
│ ./deploy.sh v2 │
│ │
│ Output of Terminal 1 during deploy: │
│ 200 200 200 200 200 200 200 200 200 200 200 200 200 200 │
│ │
│ No 5xx. No connection refused. No gap. │
│ That is the definition of zero downtime. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Rolling update | Incremental replica replacement |
start-first | New replica before old stops |
stop-first | Old stops before new starts (downtime) |
failure_action: rollback | Auto-revert on failed update |
| Blue-green | Two environments, proxy switch |
| Proxy switch | Upstream file swap + graceful reload |
| Health check | Must target the new environment directly |
| Shared database | Required for blue-green |
| Rollback | Instant in blue-green; image revert in rolling |
Key takeaways:
- Rolling updates are zero-downtime only with
start-firstand spare capacity. The defaultstop-firstorder stops the old replica before starting the new one, which creates a gap.start-firststarts the new replica first, but it requires the host to have room for the overlap . - Blue-green requires a reverse proxy and a shared database. The proxy’s upstream is switched from the old color to the new color after the new color passes health checks. The shared database ensures that data written during the deployment is not lost when the switch happens .
- The health check must target the new environment directly. Checking through the proxy during a blue-green deployment verifies the old environment, not the new one. The check must bypass the proxy and hit the new color’s health endpoint .
- Swarm’s
failure_action: rollbackautomates the revert. When an updated task fails within the monitor window, Swarm rolls back to the previous image automatically. The monitor window and the health check together determine whether the new version is considered healthy . - Blue-green rollback is a proxy switch; rolling rollback is an image revert. Blue-green keeps the old environment running, so rollback is a file swap and a reload. Rolling has already replaced the old image, so rollback means redeploying the previous image, which takes longer.
Remember: Zero downtime is a property of the deployment process, not a property of Docker. A rolling update without start-first is a stop-and-start with extra steps. A blue-green switch without a direct health check is a switch to an unverified environment. The patterns provide the mechanism; the configuration determines whether the mechanism achieves the goal. The test is the continuous request loop during the deployment: if every request returns 200, the deployment is zero-downtime. If any request fails, the configuration has a gap, and the gap is in the order of operations, the health check, or the proxy reload.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!