DEV Community

Ramer Labs
Ramer Labs

Posted on

The Ultimate Checklist for Zero‑Downtime Deploys with Docker & Nginx

Introduction

Zero‑downtime deployments are a non‑negotiable expectation for modern SaaS products. As a DevOps lead, you’ve probably wrestled with traffic spikes, rolling back broken releases, and the dreaded "service unavailable" page. This checklist walks you through a pragmatic, Docker‑centric workflow that pairs a lightweight Nginx reverse proxy with blue‑green deployment patterns. Follow each step, and you’ll keep your users blissfully unaware when you push new code.


Prerequisites

Before you start, make sure you have:

  • A Linux host (Ubuntu 22.04+ recommended) with Docker Engine ≥ 20.10 and Docker Compose ≥ 2.0 installed.
  • A domain name pointing to the host’s public IP.
  • Basic familiarity with Nginx configuration syntax.
  • A Git repository that builds a Docker image for your app (Node.js, Python, Go – any language works).

If any of these are missing, pause the checklist and get them in place. Skipping prerequisites is the fastest way to introduce friction later.


1️⃣ Prepare a Stable Base Image

  • Pin the base: Use an immutable tag (e.g., node:18-alpine) instead of latest.
  • Run as non‑root: Add a low‑privilege user inside the Dockerfile.
  • Health checks: Declare a HEALTHCHECK instruction so Docker can detect a bad container.
FROM node:18-alpine

# Create app user
RUN addgroup -S app && adduser -S -G app app
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

# Run as non‑root
USER app

# Health check – adjust path to your health endpoint
HEALTHCHECK --interval=30s --timeout=5s \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

2️⃣ Build and Tag Images Atomically

When you push a new version, tag it with the Git SHA. This eliminates ambiguity during rollouts.

# Build and tag with the current commit hash
COMMIT=$(git rev-parse --short HEAD)
docker build -t myapp:$COMMIT .
# Push to your registry (Docker Hub, ECR, GCR, etc.)
docker push myregistry.com/myapp:$COMMIT
Enter fullscreen mode Exit fullscreen mode

3️⃣ Set Up Nginx as a Smart Reverse Proxy

Nginx will sit in front of two upstream groups: blue and green. Only one group receives traffic at a time.

# /etc/nginx/conf.d/app.conf
upstream blue {
    server 127.0.0.1:8001; # Docker container on host port
}

upstream green {
    server 127.0.0.1:8002;
}

# The active upstream is controlled via a variable
map $http_x_deploy_target $upstream {
    default   blue;
    "green"  green;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://$upstream;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
Enter fullscreen mode Exit fullscreen mode

Reload Nginx after any change:

sudo nginx -s reload
Enter fullscreen mode Exit fullscreen mode

4️⃣ Docker‑Compose Blueprint for Blue‑Green

Keep both environments alive in the same compose file but expose them on different host ports.

version: "3.9"
services:
  app-blue:
    image: myregistry.com/myapp:${BLUE_TAG:-latest}
    container_name: app_blue
    ports:
      - "8001:3000"
    restart: always
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  app-green:
    image: myregistry.com/myapp:${GREEN_TAG:-latest}
    container_name: app_green
    ports:
      - "8002:3000"
    restart: always
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
Enter fullscreen mode Exit fullscreen mode

Tip: Keep the unused service stopped to save resources. You can docker compose stop app-green when blue is live.

5️⃣ Deploy Workflow Checklist

✅ Step What to Verify
1. Pull latest images docker compose pull pulls both blue and green tags.
2. Update the standby tag Export GREEN_TAG=$COMMIT (or BLUE_TAG if green is live).
3. Start the standby container docker compose up -d app-green (or app-blue).
4. Wait for health docker inspect --format='{{json .State.Health}}' $(docker ps -qf "name=app-green") should show healthy.
5. Switch Nginx upstream curl -X POST -H "X-Deploy-Target: green" http://localhost/switch (custom endpoint) or edit the X-Deploy-Target header in your load‑balancer.
6. Verify traffic curl -I http://example.com should return 200 and the Server header from the new container.
7. Drain old containers docker compose stop app-blue after confirming no 5xx errors.
8. Tag promotion Promote the new tag to latest in your registry if you want a stable fallback.

6️⃣ Observability & Rollback

  • Metrics: Export container health to Prometheus via the cAdvisor exporter.
  • Logs: Ship Nginx access logs and app stdout to Loki or ELK. Include the $upstream variable in logs to trace which version served a request.
  • Rollback: If the new version spikes error rates, repeat step 5 but point the header back to the previous upstream, then stop the problematic container.

7️⃣ Automate with CI/CD

Most teams embed the checklist into a pipeline. Below is a minimal GitHub Actions job that performs steps 1‑4 and leaves the manual switch for a post‑deployment approval.

name: Deploy Blue‑Green
on:
  push:
    branches: [main]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Log in to registry
        uses: docker/login-action@v2
        with:
          registry: myregistry.com
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASS }}
      - name: Build & push image
        env:
          COMMIT: ${{ github.sha }}
        run: |
          docker build -t myregistry.com/myapp:${COMMIT} .
          docker push myregistry.com/myapp:${COMMIT}
      - name: Deploy standby
        env:
          GREEN_TAG: ${{ github.sha }}
        run: |
          docker compose pull
          docker compose up -d app-green
Enter fullscreen mode Exit fullscreen mode

After the workflow finishes, a senior engineer can trigger the Nginx switch via a secure endpoint or a simple ssh command.


Conclusion

Zero‑downtime deployments don’t require exotic tooling; they need a disciplined checklist, immutable Docker images, and a proxy that can route traffic atomically. By keeping blue and green environments isolated, health‑checking rigorously, and observability wired from day one, you protect both your users and your team’s sanity.

If you need help shipping this, the team at https://ramerlabs.com can help.

Top comments (0)