DEV Community

Ramer Lacida
Ramer Lacida

Posted on

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

Introduction

As a DevOps lead, you know that downtime is the enemy of user trust. Deploying new versions of a service without interrupting traffic is a classic challenge, but with Docker and Nginx you can build a reliable, zero‑downtime pipeline. This checklist walks you through the essential steps—from container image hygiene to Nginx blue‑green routing—so you can ship updates confidently.


1. Prepare a Production‑Ready Docker Image

1.1 Use a Minimal Base Image

  • Alpine is tiny (≈5 MB) and reduces attack surface.
  • Pin the exact version to avoid surprise upgrades.
FROM alpine:3.18
LABEL maintainer="you@example.com"

# Install only what you need
RUN apk add --no-cache python3 py3-pip

COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt

CMD ["python3", "-m", "gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
Enter fullscreen mode Exit fullscreen mode

1.2 Multi‑Stage Builds for Smaller Images

Separate the build environment from the runtime:

# ---- Build Stage ----
FROM node:20-alpine AS builder
WORKDIR /src
COPY package*.json ./
RUN npm ci && npm run build

# ---- Runtime Stage ----
FROM nginx:alpine
COPY --from=builder /src/dist /usr/share/nginx/html
Enter fullscreen mode Exit fullscreen mode

1.3 Verify Image Size & Layers

docker image ls --format "{{.Repository}}:{{.Tag}} {{.Size}}"
Enter fullscreen mode Exit fullscreen mode

Keep the final image under 150 MB for faster pulls.


2. Implement Blue‑Green Deployments with Nginx

2.1 Nginx Upstream Configuration

Define two upstream blocks—green and blue—pointing at the respective Docker containers.

upstream green {
    server 127.0.0.1:8001;
    keepalive 16;
}

upstream blue {
    server 127.0.0.1:8002;
    keepalive 16;
}

# Default to green (current production)
server {
    listen 80;
    location / {
        proxy_pass http://green;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
Enter fullscreen mode Exit fullscreen mode

2.2 Switch Traffic Atomically

When the new version is ready, update the proxy_pass line to point at blue and reload Nginx without dropping connections:

# Edit the config (or use a templating tool)
sed -i 's/http:\/\/green/http:\/\/blue/' /etc/nginx/conf.d/app.conf

# Reload gracefully
nginx -s reload
Enter fullscreen mode Exit fullscreen mode

Because Nginx reloads the configuration in a worker‑by‑worker fashion, existing connections finish on the old upstream while new requests flow to the updated container.


3. Automate the Workflow with Docker Compose

A single docker-compose.yml can spin up both green and blue containers, expose the correct ports, and tag the services for easy swapping.

version: "3.9"
services:
  app-green:
    image: myorg/app:{{CURRENT_TAG}}
    ports: ["8001:8000"]
    restart: always
  app-blue:
    image: myorg/app:{{NEW_TAG}}
    ports: ["8002:8000"]
    restart: always
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    ports: ["80:80"]
    depends_on:
      - app-green
      - app-blue
Enter fullscreen mode Exit fullscreen mode

Deploy steps:

  1. Pull the new image (docker pull myorg/app:1.2.3).
  2. Bring up the blue container (docker-compose up -d app-blue).
  3. Verify health checks.
  4. Switch Nginx upstream (as shown above).
  5. After traffic stabilizes, tear down the green container.

4. Health Checks & Observability

4.1 Container‑Level Health Checks

Add a simple HTTP endpoint (/healthz) to your app and declare it in Docker:

HEALTHCHECK --interval=10s --timeout=2s \
  CMD curl -f http://localhost:8000/healthz || exit 1
Enter fullscreen mode Exit fullscreen mode

Docker will mark the container unhealthy if the endpoint fails, preventing Nginx from routing to a broken instance.

4.2 Centralized Logging

Send logs to a lightweight sidecar like Fluent Bit and forward them to Loki or CloudWatch. Example fluent-bit.conf:

[INPUT]
    Name tail
    Path /var/log/app/*.log
    Tag app.*

[OUTPUT]
    Name stdout
    Match *
Enter fullscreen mode Exit fullscreen mode

4.3 Metrics for Rollback Decisions

Expose Prometheus metrics (/metrics) and watch the error rate during the switch. If the new version spikes above a threshold, roll back by re‑pointing Nginx to the green upstream.


5. Security & Secrets Management

  • Store Docker registry credentials in Docker secrets or a secret manager (AWS Secrets Manager, HashiCorp Vault).
  • Use TLS termination at Nginx: generate certs with Let’s Encrypt or use a corporate PKI.
  • Add a strict Content‑Security‑Policy header in Nginx to mitigate XSS.
add_header Content-Security-Policy "default-src 'self'" always;
Enter fullscreen mode Exit fullscreen mode

6. Checklist Summary

  • [ ] Build minimal, multi‑stage Docker images.
  • [ ] Tag images with immutable version identifiers.
  • [ ] Define green and blue upstreams in Nginx.
  • [ ] Use Docker Compose to run both versions side‑by‑side.
  • [ ] Add container health checks and expose /healthz.
  • [ ] Verify new container health before traffic switch.
  • [ ] Reload Nginx gracefully (nginx -s reload).
  • [ ] Monitor Prometheus metrics for error spikes.
  • [ ] Log to a central system (Fluent Bit → Loki/CloudWatch).
  • [ ] Secure secrets and enable TLS on Nginx.
  • [ ] After successful rollout, decommission the old container.

Conclusion

Zero‑downtime deployments become repeatable when you treat the Docker image, Nginx routing, and observability as a single, versioned artifact. By following this checklist you’ll reduce risk, improve developer velocity, and keep users blissfully unaware of any behind‑the‑scenes changes. If you need help shipping this, the team at https://ramerlabs.com can help.

Top comments (0)