Every client project we ship eventually asks the same question: "what happens to users mid-deploy?" The answer, if you set it up right, is nothing — they never notice. Here's the blue-green setup we reuse across projects, built entirely on Docker and GitHub Actions with no extra orchestration platform required.

The core idea: two identical environments, one switch

Blue-green deployment means running two identical production environments — call them blue and green — with a router or load balancer in front that only sends traffic to one at a time. You deploy the new version to the idle environment, verify it's healthy, then flip the switch. If anything goes wrong, you flip back instantly because the old environment never stopped running.

The GitHub Actions workflow

jobs:
  deploy:
    steps:
      - name: Build and push image
        run: |
          docker build -t $REGISTRY/app:${{ github.sha }} .
          docker push $REGISTRY/app:${{ github.sha }}

      - name: Deploy to idle environment
        run: ./scripts/deploy.sh $IDLE_ENV ${{ github.sha }}

      - name: Health check
        run: ./scripts/healthcheck.sh $IDLE_ENV --retries 10 --interval 5

      - name: Switch traffic
        if: success()
        run: ./scripts/switch-traffic.sh $IDLE_ENV

      - name: Rollback on failure
        if: failure()
        run: ./scripts/rollback.sh

The health check is the part people skip

A deployment that "succeeds" but serves a broken app is worse than one that fails loudly. Our health check script doesn't just ping /health and call it done — it hits three or four representative endpoints, checks response times against a baseline, and confirms the database connection pool is actually accepting connections. If any check fails after the retry budget, the workflow stops before traffic ever switches.

Key takeaway

Zero-downtime deployment isn't really about Docker or GitHub Actions — those are just tools. The actual engineering is in the health check: it has to be strict enough to catch real problems and fast enough not to block every deploy for ten minutes.

Rollbacks should be boring

Because the previous environment never stops running during a blue-green deploy, our rollback script is a single traffic switch back to the last-known-good environment — no rebuilding, no restoring backups, no 2am database surgery. We test the rollback path in every project's staging environment before it ever goes near production, because a rollback script nobody has run is not a rollback plan.