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




Introduction

As a DevOps lead, you’ve probably faced the dreaded moment when a production deployment brings the site down. Zero‑downtime deployments are no longer a luxury; they’re a baseline expectation for modern services. This checklist walks you through a practical, Docker‑centric workflow that leverages Nginx as a smart reverse proxy, blue‑green releases, and observability hooks. Follow each step, and you’ll be able to push new code without breaking existing traffic.




1. Prepare Your Docker Images

  • Immutable builds: Use a multi‑stage Dockerfile so the final image contains only runtime artifacts.
  • Tagging strategy: Tag images with both a semantic version (v1.3.2) and a short Git SHA (v1.3.2‑a1b2c3).
  • Scan for vulnerabilities: Run docker scan or integrate Trivy into your CI pipeline.
# Dockerfile (Node.js example)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
Enter fullscreen mode

Exit fullscreen mode



2. Set Up Nginx as a Traffic Router

Nginx will sit in front of two identical app containers – blue (current) and green (next). By swapping the upstream group, you achieve an instant cut‑over.

# docker-compose.yml (excerpt)
version: "3.8"
services:
  nginx:
    image: nginx:stable-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app_blue
      - app_green

  app_blue:
    image: myapp:v1.3.2-a1b2c3
    environment:
      - NODE_ENV=production
    expose:
      - "3000"

  app_green:
    image: myapp:latest
    environment:
      - NODE_ENV=production
    expose:
      - "3000"
Enter fullscreen mode

Exit fullscreen mode

# nginx.conf (simplified)
worker_processes auto;
events { worker_connections 1024; }
http {
  upstream backend {
    server app_blue:3000 max_fails=3 fail_timeout=30s;
    # The green server will be added/removed by the deploy script
  }

  server {
    listen 80;
    location / {
      proxy_pass http://backend;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
    }
  }
}
Enter fullscreen mode

Exit fullscreen mode



3. Automate Blue‑Green Swaps

A small Bash helper can drive the swap. It performs three actions:

  1. Pull the new image.
  2. Add the green container to the Nginx upstream.
  3. Wait for health checks, then remove the blue container.
#!/usr/bin/env bash
set -euo pipefail

NEW_TAG=$1   # e.g. v1.4.0‑d4e5f6

# 1️⃣ Pull the new image
docker pull myapp:${NEW_TAG}

# 2️⃣ Spin up the green service (named app_green) with the new tag
docker compose up -d --no-deps --scale app_green=1 app_green

# 3️⃣ Add green to Nginx upstream (via Docker exec)
docker exec nginx nginx -s reload

# 4️⃣ Simple health check loop (adjust URL/timeout as needed)
for i in {1..30}; do
  if curl -sSf http://localhost/healthz | grep -q "ok"; then
    echo "✅ Green is healthy"
    break
  fi
  echo "⏳ Waiting for green…"
  sleep 2
done

# 5️⃣ Drain traffic from blue (optional: use Nginx "max_fails" or a weighted upstream)
# Here we just stop the blue container after green passes health check
docker compose stop app_blue

# 6️⃣ Clean up old image (optional)
docker image prune -f

echo "🚀 Deploy complete – blue‑green swap successful"
Enter fullscreen mode

Exit fullscreen mode



4. Integrate with CI/CD

  • Pipeline stage: Build → Scan → Push → Deploy.
  • GitHub Actions example:
  name: Deploy
  on:
    push:
      tags: ["v*.*.*"]
  jobs:
    build-and-deploy:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v3
        - name: Set up Docker Buildx
          uses: docker/setup-buildx-action@v2
        - name: Build image
          run: |
            TAG=${GITHUB_REF##*/}
            docker build -t myapp:${TAG} .
        - name: Scan image
          uses: aquasecurity/trivy-action@master
          with:
            image-ref: myapp:${TAG}
        - name: Push to registry
          run: |
            echo ${{ secrets.REGISTRY_PASSWORD }} | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin registry.example.com
            docker push myapp:${TAG}
        - name: Trigger swap script on server
          run: |
            ssh deploy@prod "./swap.sh ${TAG}"
Enter fullscreen mode

Exit fullscreen mode



5. Observability & Logging

  • Structured logs: Output JSON logs from the app; forward them to Loki or Elasticsearch.
  • Metrics: Expose Prometheus /metrics endpoint; scrape both blue and green containers.
  • Health checks: Nginx can perform active checks (proxy_next_upstream) or rely on a separate sidecar like caddy-healthcheck.
  • Alerting: Set up an alert if the green container fails its health check three times in a row.



6. Rollback Plan

Even with a checklist, things can go sideways. Keep the previous image tag handy and reverse the swap:

# Rollback to previous tag stored in a file
PREV_TAG=$(cat /var/deploy/last_successful_tag)
./swap.sh $PREV_TAG
Enter fullscreen mode

Exit fullscreen mode

  • Ensure the rollback script also updates the blue container and removes the faulty green instance.
  • Verify rollback health before announcing success.



7. Security Hardening (Bonus)

  • Least‑privilege containers: Run as non‑root user (USER node in Dockerfile).
  • TLS termination: Let Nginx handle HTTPS; use cert‑bot or Let’s Encrypt automation.
  • Secret management: Pull environment variables from Vault or AWS Secrets Manager at container start, never bake them into images.



Conclusion

Zero‑downtime deployments with Docker and Nginx become repeatable once you codify the steps above. By treating the blue‑green swap as an automated script, wiring it into your CI/CD pipeline, and backing it with solid observability, you can ship features several times a day without frightening your users. If you need help shipping this, the team at https://ramerlabs.com can help.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *