Tips > Ops & Security

Implement Blue-Green Deployments for Zero-Downtime Upgrades

Stopping n8n for an upgrade interrupts active executions and makes webhooks return errors.

Stopping n8n for an upgrade interrupts active executions and makes webhooks return errors. A blue-green deployment runs the new version alongside the old one, verifies it works, then switches traffic. Both versions share the same database, so n8n's migration system handles schema changes during startup of the new version.

Real-world example: You upgrade from 1.93.0 to 1.94.1 without any webhook downtime.

services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n_user -d n8n_db"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n-blue:
    image: n8nio/n8n:1.93.0
    restart: unless-stopped
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: n8n_db
      DB_POSTGRESDB_USER: n8n_user
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:5678/healthz || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

  n8n-green:
    image: n8nio/n8n:1.94.1
    restart: unless-stopped
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: n8n_db
      DB_POSTGRESDB_USER: n8n_user
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:5678/healthz || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  postgres_data:
```text
```bash title="blue-green-switch.sh"
#!/usr/bin/env bash
set -euo pipefail

# Step 1: Start the green (new) instance

docker compose -f docker-compose.blue-green.yml up -d n8n-green

# Step 2: Wait for green to pass health checks

echo "Waiting for green instance to become healthy..."
timeout 180 bash -c '
  until docker inspect --format="{{.State.Health.Status}}" n8n-green-1 2>/dev/null | grep -q healthy; do
    sleep 5
  done
'
echo "Green instance is healthy."

# Step 3: Update Caddy to point to green

# (swap upstream in Caddyfile from n8n-blue:5678 to n8n-green:5678)

sed -i 's/n8n-blue:5678/n8n-green:5678/' Caddyfile
docker exec caddy-1 caddy reload --config /etc/caddy/Caddyfile

# Step 4: Verify traffic is flowing to green

sleep 10
curl -sf https://n8n.example.com/healthz > /dev/null && echo "Traffic switched to green."

# Step 5: Stop the blue (old) instance

docker compose -f docker-compose.blue-green.yml stop n8n-blue

echo "Blue-green deployment complete."
```text
> **Warning: Database Migrations**
>
> n8n runs database migrations on startup. Once the green instance starts and migrates the schema, the blue instance may not be compatible with the new schema. Keep the cutover window short and always have a database backup from before the migration.

This approach eliminates the 30-120 second downtime window that a simple `docker compose pull && docker compose up -d` creates.

**Related:** [Set a Unique Encryption Key and Back It Up](../security-best-practices/01-set-a-unique-encryption-key-and-back-it-up.md) | [Configure Payload Size and Binary Data Mode for Large Files](../performance-and-large-files/01-configure-payload-size-and-binary-data-mode-for-large-files.md)

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

191 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book a 20-minute call