Blue-Green Deployment
A deep dive into blue-green deployment — how it works, when to use it, how to handle database migrations, traffic switching, rollback, Kubernetes implementation, and the trade-offs every production engineer should know.
Introduction
Blue-green deployment is a release strategy that maintains two identical production environments running in parallel.
At any point in time, only one environment — Blue — is receiving live traffic. The other — Green — is idle and available for the next deployment.
When a new version is ready to release:
- The new version is deployed to the idle environment (Green)
- Green is tested and verified in isolation
- All traffic is switched from Blue to Green in a single atomic step
- Blue is retained as the immediate rollback target
If anything goes wrong after the switch, rollback is a single traffic switch back to Blue — taking seconds, not minutes.
This is the defining advantage of blue-green: instant, zero-risk rollback at any point after a release.
The Core Problem Blue-Green Solves
Every deployment strategy faces the same fundamental question: what happens if the new version is broken?
| Strategy | Rollback Mechanism | Rollback Time |
|---|---|---|
| Recreate | Redeploy previous image | 5–15 minutes |
| Rolling Update | Roll forward with fixed version | 5–15 minutes |
| Canary | Set traffic weight to 0% | 1–3 minutes |
| Blue-Green | Switch traffic back to idle environment | < 60 seconds |
Blue-green achieves the fastest rollback of any deployment strategy because the previous version is already running and fully warmed up. No containers need to start, no dependencies need to initialize.
Architecture
The Two Environments
flowchart TB
DNS["DNS / Load Balancer\n(Controls traffic routing)"]
subgraph BlueEnv["Blue Environment — LIVE"]
B_GW["API Gateway"]
B_S1["Service A: v1"]
B_S2["Service B: v1"]
B_S3["Service C: v1"]
end
subgraph GreenEnv["Green Environment — IDLE"]
G_GW["API Gateway"]
G_S1["Service A: v2"]
G_S2["Service B: v2"]
G_S3["Service C: v2"]
end
SharedDB[("Shared Database")]
DNS -->|"100% traffic"| BlueEnv
DNS -.->|"0% traffic"| GreenEnv
BlueEnv --> SharedDB
GreenEnv --> SharedDB
Both environments:
- Are identical in configuration — same instance types, same networking, same secrets
- Share the same database — schema must be backward compatible during the cutover window
- Are independently deployable — Green is updated while Blue serves 100% of traffic
State Machine
Blue-green alternates which environment is live with each release.
flowchart LR
S1["Release 1\nBlue: v1 LIVE\nGreen: idle"]
S2["Deploy Release 2\nBlue: v1 LIVE\nGreen: v2 ready"]
S3["Switch Traffic\nBlue: v1 idle\nGreen: v2 LIVE"]
S4["Deploy Release 3\nBlue: v3 ready\nGreen: v2 LIVE"]
S5["Switch Traffic\nBlue: v3 LIVE\nGreen: v2 idle"]
S1 --> S2 --> S3 --> S4 --> S5
Each release cycle flips which colour is live. Blue and Green swap roles with every deployment.
The Complete Deployment Flow
sequenceDiagram
participant Engineer
participant Pipeline
participant Green
participant LoadBalancer
participant Blue
participant Database
Note over Blue: Serving 100% live traffic
Engineer->>Pipeline: Trigger release
Pipeline->>Database: Run schema migration (expand phase)
Pipeline->>Green: Deploy v2 to idle environment
Pipeline->>Green: Run smoke tests
Green-->>Pipeline: All tests pass
Pipeline->>LoadBalancer: Switch 100% traffic → Green
LoadBalancer-->>Green: Live traffic begins
LoadBalancer-->>Blue: Traffic stops
Note over Green: Serving 100% live traffic
Note over Blue: Idle — ready for instant rollback
Engineer->>Pipeline: Confirm release stable (after soak)
Pipeline->>Blue: Scale down or prepare for next deploy
Traffic Switching Methods
How traffic is switched between Blue and Green depends on the infrastructure layer used.
DNS-Based Switching
Update the DNS record to point to the Green load balancer.
flowchart LR
Client --> DNS["DNS Record\npayments.company.com"]
DNS -->|"Before"| BlueLB["Blue Load Balancer\n10.0.1.100"]
DNS -.->|"After switch"| GreenLB["Green Load Balancer\n10.0.2.100"]
Consideration: DNS TTL (Time To Live) determines how long clients cache the old address. Use a low TTL (30–60 seconds) ahead of the deployment window. DNS switching is not instant — it takes TTL time to propagate globally.
Load Balancer Target Group Switching
Switch at the load balancer level by changing which target group receives traffic.
flowchart TB
Internet --> ALB["AWS Application Load Balancer"]
ALB -->|"Listener Rule\n(before switch)"| BlueTG["Blue Target Group\n(v1 instances)"]
ALB -.->|"Listener Rule\n(after switch)"| GreenTG["Green Target Group\n(v2 instances)"]
This is the preferred approach for AWS deployments:
- Switch is instant — no DNS propagation delay
- Single API call changes the listener rule
- Rollback is the same operation in reverse
- Health checks on Green target group must pass before switching
Kubernetes Service Selector Switch
In Kubernetes, traffic is controlled by pod label selectors. Switching is an update to the Service object.
flowchart TB
Service["Kubernetes Service\n(payments-svc)"]
subgraph Before["Before Switch"]
Selector1["selector:\n app: payments\n version: blue"]
end
subgraph After["After Switch"]
Selector2["selector:\n app: payments\n version: green"]
end
Service --> Before
Service -.-> After
Before --> BluePods["Blue Pods (v1)\n✅ Receiving traffic"]
After --> GreenPods["Green Pods (v2)\n✅ Receiving traffic"]
Before deployment:
# Service selector points to Blue
selector:
app: payments
version: blue
After switch:
# Single patch command switches all traffic
selector:
app: payments
version: green
The switch takes effect immediately — Kubernetes updates the endpoint slice within seconds.
Database Strategy
The database is the most complex aspect of blue-green deployment. Both environments share one database, which means the schema must simultaneously support both versions during the transition.
Phase 1 — Expand (Before Switch)
Add the new schema elements without removing or modifying existing ones. Both v1 and v2 can operate against the same schema.
flowchart LR
DB["Database"]
DB --> OldCol["existing columns\n(v1 reads these)"]
DB --> NewCol["new columns added\n(v2 reads these, v1 ignores)"]
Safe operations in this phase:
- Add new nullable columns
- Add new tables
- Add indexes (using
CONCURRENTLY) - Add new enum values
Phase 2 — Backfill (During Soak Period)
After the switch, v2 is live. Run background jobs to backfill data into new columns for existing rows.
sequenceDiagram
participant V2App
participant BackfillJob
participant Database
Note over V2App: Live — writing to both old and new columns
BackfillJob->>Database: UPDATE rows SET new_col = derive(old_col) WHERE new_col IS NULL
BackfillJob-->>Database: Batch complete
Note over Database: All rows now have new_col populated
Phase 3 — Contract (Next Release Cycle)
Once all v1 instances are retired and the blue environment is confirmed decommissioned, clean up:
- Remove old columns no longer read by any version
- Add NOT NULL constraints now that all rows are backfilled
- Drop compatibility views or shims
flowchart LR
Phase1["Expand\nAdd new_col\n(nullable)"]
Phase2["Backfill\nPopulate new_col\nfor all rows"]
Phase3["Contract\nDrop old_col\nAdd NOT NULL"]
Phase1 --> Phase2 --> Phase3
Schema Change Safety Table
| Migration Type | Safe During Switch? | Notes |
|---|---|---|
| Add nullable column | ✅ Yes | v1 ignores the new column |
| Add new table | ✅ Yes | v1 is unaware of the table |
| Add index (CONCURRENTLY) | ✅ Yes | Non-blocking |
| Drop unused column | ✅ Yes (post-retire) | Only after v1 is fully decommissioned |
| Rename column | ❌ No (directly) | Use expand + contract across two releases |
| Change column type | ❌ No (directly) | Add new column, migrate, drop old |
| Remove NOT NULL constraint | ✅ Yes | Relaxing constraints is safe |
| Add NOT NULL constraint | ❌ No (directly) | Backfill nulls first, then add constraint |
Rollback
Rollback in blue-green is the fastest available for any deployment strategy.
sequenceDiagram
participant OnCall
participant Alerting
participant LoadBalancer
participant Blue
participant Green
Note over Green: Live — serving 100% traffic
Alerting->>OnCall: Error rate spike — 5% on /payments
OnCall->>LoadBalancer: Switch traffic back to Blue
LoadBalancer-->>Blue: 100% traffic (< 5 seconds)
LoadBalancer-->>Green: 0% traffic
Note over Blue: Fully restored
Note over Green: Idle — debugging begins
Rollback properties:
- Blue environment is already warm — no cold start penalty
- All Blue connections to the database are pre-established
- Blue pods are in
Runningstate — notPendingorContainerCreating - SLO recovery time: typically under 60 seconds from decision to restored traffic
What rollback does NOT undo:
- Database schema expansions (new nullable columns remain — they are backward compatible)
- Data written by v2 (new columns populated by v2 remain — v1 ignores them)
- Audit logs created during the v2 window
These are safe to leave in place. The contract phase of the next release cycle will clean them up.
Blue-Green with Databases — Full Timeline
flowchart TB
T1["T-1: Expand migration\nDeploy schema additions\n(new columns, new tables)"]
T2["T0: Deploy v2 to Green\nRun smoke tests"]
T3["T0+5min: Switch traffic\nGreen becomes live"]
T4["T0+30min: Soak period\nMonitor SLOs on Green"]
T5["T0+60min: Confirm stable\nBlue kept for rollback window"]
T6["T+24hrs: Decommission Blue\nBegin contract phase"]
T7["Next release: Contract\nDrop old columns, add constraints"]
T1 --> T2 --> T3 --> T4 --> T5 --> T6 --> T7
Never decommission Blue immediately after switching. Keep Blue running for at least 1–2 hours to preserve the instant rollback capability through the highest-risk window.
Blue-Green vs Canary
These two strategies are often compared. They solve different problems.
flowchart LR
subgraph BlueGreen["Blue-Green"]
BG1["100% traffic on v1"]
BG2["Switch → 100% on v2"]
BG1 --> BG2
end
subgraph Canary["Canary"]
C1["100% on v1"]
C2["5% on v2, 95% on v1"]
C3["50% on v2, 50% on v1"]
C4["100% on v2"]
C1 --> C2 --> C3 --> C4
end
| Dimension | Blue-Green | Canary |
|---|---|---|
| Traffic exposure | All-at-once switch | Gradual percentage shift |
| Rollback speed | Instant (< 60s) | Instant (set to 0%) |
| Blast radius if broken | 100% of users briefly | Small % of users for longer |
| Infrastructure cost | 2x during deployment window | Small overhead for canary pods |
| Best for | Clean cutover, full environment test | High-traffic, risk-sensitive changes |
| Real traffic validation | After 100% switch | Before full rollout |
Combined pattern: Some teams use blue-green for the environment switch and canary for the traffic shift — deploy to Green, shift 5% → 100% while keeping Blue as the rollback target.
Kubernetes Implementation
A complete blue-green setup in Kubernetes using two Deployments and one Service.
Blue Deployment (current live)
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-blue
labels:
app: payments
version: blue
spec:
replicas: 4
selector:
matchLabels:
app: payments
version: blue
template:
metadata:
labels:
app: payments
version: blue
spec:
containers:
- name: payments
image: payments:v1.4.2
Green Deployment (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-green
labels:
app: payments
version: green
spec:
replicas: 4
selector:
matchLabels:
app: payments
version: green
template:
metadata:
labels:
app: payments
version: green
spec:
containers:
- name: payments
image: payments:v1.5.0
Service (controls which version receives traffic)
apiVersion: v1
kind: Service
metadata:
name: payments-svc
spec:
selector:
app: payments
version: blue # Change to "green" to switch traffic
ports:
- port: 80
targetPort: 8080
Traffic Switch Command
# Switch to Green
kubectl patch service payments-svc \
-p '{"spec":{"selector":{"app":"payments","version":"green"}}}'
# Rollback to Blue
kubectl patch service payments-svc \
-p '{"spec":{"selector":{"app":"payments","version":"blue"}}}'
Monitoring During a Blue-Green Release
After switching traffic to Green, watch these signals for the soak period (minimum 15–30 minutes):
flowchart LR
Switch["Traffic → Green"]
Switch --> M1["Error rate\n(compare vs Blue baseline)"]
Switch --> M2["p99 latency\n(must not regress)"]
Switch --> M3["Business metrics\n(payment success rate)"]
Switch --> M4["JVM / Runtime\n(memory, GC, threads)"]
Switch --> M5["Database\n(connection pool, query latency)"]
M1 --> Decision{"All signals\nhealthy?"}
M2 --> Decision
M3 --> Decision
M4 --> Decision
M5 --> Decision
Decision -->|"Yes"| Confirm["Confirm release\nDecommission Blue"]
Decision -->|"No"| Rollback["Rollback to Blue\n< 60 seconds"]
Key comparisons to make:
| Metric | Blue (baseline) | Green (post-switch) | Action if regressed |
|---|---|---|---|
| Error rate (5xx) | 0.02% | < 0.05% | Rollback immediately |
| p99 latency | 180ms | < 220ms | Investigate |
| Payment success rate | 99.8% | > 99.7% | Rollback if declining |
| DB connection pool | 40% utilised | < 70% | Investigate |
When to Use Blue-Green
Strong fit:
- Regulated systems (banking, healthcare) requiring a clean, auditable cutover
- Major version releases with significant behaviour changes
- High-value services (payments, auth) where instant rollback must be available
- Full environment testing is required before any production traffic touches the new version
- Compliance requirements mandate that the previous version be immediately restorable
Weak fit:
- Services with very frequent deploys (multiple times per hour) — infrastructure cost becomes significant
- Systems that cannot afford 2x resource cost even temporarily
- Services that benefit more from gradual traffic exposure (use canary instead)
- Environments with strict database coupling that makes backward-compatible migrations impractical
Production Readiness Checklist
Environment Setup
- Blue and Green environments are identical in configuration (instance type, memory, CPU)
- Both environments share the same database with backward-compatible schema
- Health checks are passing on Green before any traffic switch
- Smoke tests pass on Green against production database
Traffic Switching
- Load balancer or service selector switch mechanism is tested in staging
- Rollback switch command is documented and tested
- DNS TTL is set low ahead of deployment (if DNS-based switching)
- Traffic switch takes effect within 60 seconds
Database
- Schema migration is expand-only (no destructive changes before switch)
- Backfill jobs are idempotent and can be re-run safely
- Contract phase is scheduled for the release after v1 is decommissioned
Monitoring
- Dashboards are open before switch with Blue baseline captured
- Alert thresholds defined for error rate and latency
- Soak period defined (minimum 15 minutes post-switch before confirming)
- Rollback decision criteria documented (what metric threshold triggers rollback)
Post-Switch
- Blue retained for minimum 1-hour rollback window
- Blue scaled down only after stability confirmed
- Contract migration scheduled for next release cycle
- Release documented in change log
Summary
Blue-green deployment is the gold standard for high-confidence, zero-downtime releases where instant rollback is a hard requirement.
flowchart LR
Deploy["Deploy v2\nto Green\n(isolated)"]
Test["Test Green\n(smoke tests\nfull env)"]
Switch["Switch traffic\nBlue → Green\n(atomic)"]
Monitor["Soak period\n(15–30 min)"]
Confirm["Confirm stable\nRetire Blue"]
Deploy --> Test --> Switch --> Monitor --> Confirm
Monitor -->|"Issue detected"| Rollback["Rollback\nGreen → Blue\n< 60 seconds"]
Key principles:
- Blue is always your rollback — never decommission it until stability is confirmed
- Database migrations must be backward compatible — expand first, contract later
- Switch at the load balancer level — not DNS, to avoid propagation delays
- Soak before confirming — monitor SLOs for 15–30 minutes before decommissioning Blue
- Smoke test Green before switching — never switch to an untested environment