Deployment Strategies: Blue/Green, Canary, and Rolling Deployments

A deployment strategy is not just a technical implementation detail. It determines how much risk you take with each release, how quickly you can recover from problems, and what infrastructure you need to support it. Choosing without understanding the trade-offs is choosing accidentally.

Rolling Deployments: The Default

Rolling deployments incrementally replace old instances with new ones. At any point during the deployment, both old and new versions are running simultaneously.

Before:  [v1][v1][v1][v1]
Step 1:  [v2][v1][v1][v1]
Step 2:  [v2][v2][v1][v1]
Step 3:  [v2][v2][v2][v1]
After:   [v2][v2][v2][v2]

In Kubernetes, this is the default Deployment update strategy:

apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1  # Never take more than 1 pod offline
      maxSurge: 1        # Allow 1 extra pod during deployment

Advantages:

  • No additional infrastructure — you use your existing pod count
  • Gradual rollout naturally validates the new version
  • Built-in Kubernetes support requires no additional tooling

Disadvantages:

  • Old and new versions run simultaneously — backward-compatible API changes required
  • Rollback means rolling forward to the previous version (another rolling update), not instant
  • Partial traffic splitting is implicit, not controlled — hard to measure the new version’s error rate in isolation

When to use it: most deployments, most services. Acceptable risk, minimal overhead.

Blue/Green Deployments: Instant Rollback

Blue/green maintains two identical production environments. One is live (blue), one is idle (green). To deploy, you bring up the new version on green and switch traffic.

Blue (active):  [v1][v1][v1][v1] ← 100% of traffic
Green (idle):   [v2][v2][v2][v2]

Switch:
Blue (idle):    [v1][v1][v1][v1]
Green (active): [v2][v2][v2][v2] ← 100% of traffic

Rollback (if needed): switch back in seconds

Implementation with a load balancer:

# Deploy new version to green
kubectl set image deployment/app-green app=myapp:v2

# Wait for green to be healthy
kubectl rollout status deployment/app-green

# Switch traffic (update service selector)
kubectl patch service app-lb -p '{"spec":{"selector":{"version":"green"}}}'

# If something goes wrong, rollback instantly
kubectl patch service app-lb -p '{"spec":{"selector":{"version":"blue"}}}'

Advantages:

  • Instant rollback: switching back is as fast as switching forward
  • No mixed-version state: the switch is atomic from the user’s perspective
  • Clean environment for the new version: not sharing resources with the old version

Disadvantages:

  • Double the infrastructure cost (two full environments)
  • Database migrations must be backward-compatible: both versions need to work with the same database
  • Warm-up time: the green environment needs to be fully operational before the switch

Database migrations with blue/green: this is the hard problem. Blue/green deployments require that your database schema supports both versions simultaneously during the transition period.

The expand/contract pattern handles this:

  1. Expand: add the new column (nullable, with default) — both v1 and v2 work
  2. Deploy v2
  3. Migrate: fill the new column, remove old column dependency
  4. Contract: remove old column (after v1 is decommissioned)

When to use it: services where instant rollback is critical, services that can tolerate the infrastructure cost, services with backward-compatible database changes.

Canary Deployments: Risk-Controlled Rollout

Canary deployments route a small percentage of traffic to the new version while the majority continues to the stable version.

v1 (stable):  [v1][v1][v1][v1] ← 95% of traffic
v2 (canary):  [v2]              ← 5% of traffic

After validation:
v1 → v2 (full rollout)

In Kubernetes with an ingress controller or service mesh:

# Nginx ingress with canary annotation
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "5"  # 5% to canary
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        backend:
          service:
            name: app-v2
            port:
              number: 8080

Progressive canary: automate the rollout based on metrics.

def progressive_canary(deployment, target_percentage=100, step=10):
    current = 5  # Start at 5%
    while current <= target_percentage:
        set_canary_weight(deployment, current)
        wait(10 * 60)  # 10 minutes
        
        metrics = get_metrics(deployment, window="10m")
        if metrics.error_rate > 0.01:  # >1% error rate
            rollback(deployment)
            alert("Canary failed at {}%: error rate {}".format(
                current, metrics.error_rate))
            return
        
        if metrics.p99_latency > 500:  # >500ms p99
            rollback(deployment)
            alert("Canary failed: latency degraded")
            return
        
        current += step
    
    promote_to_stable(deployment)

Advantages:

  • Limits blast radius: only the canary percentage of users are exposed to a bad release
  • Provides real traffic validation: the canary handles real requests, not synthetic tests
  • Automated rollback: metrics-based rollout can automatically abort on quality degradation

Disadvantages:

  • Observability requirements: you need to measure canary vs stable metrics separately to detect problems
  • Longer deployment time: waiting at each traffic percentage step
  • Session consistency: users may hit v1 for some requests and v2 for others if not using sticky sessions
  • More complex than rolling or blue/green

When to use it: user-facing services where a bad release affects real users, services with measurable quality metrics, teams with mature observability.

Database Migrations and All Three Strategies

The hardest constraint on deployment strategies is the database. Any strategy where old and new code versions run simultaneously (all three) requires that:

Migrations are backward-compatible: the old version must be able to run against the new schema.

The sequence for breaking changes:

  1. Deploy v2 with old schema support still present
  2. Run additive migration (add new column/table, don’t remove old)
  3. Migrate data to new structure (background job)
  4. Deploy v2-final that uses new structure
  5. Run removal migration (drop old column/table) only after v1 is fully decommissioned

For column renames:

-- Step 1: Add new column (backward compatible — v1 ignores it, v2 uses both)
ALTER TABLE orders ADD COLUMN customer_reference VARCHAR(255);

-- Step 2: Backfill (background job)
UPDATE orders SET customer_reference = legacy_ref WHERE customer_reference IS NULL;

-- Step 3: After v1 is decommissioned
ALTER TABLE orders DROP COLUMN legacy_ref;

Feature Flags: The Software Switch

Deployment strategies handle infrastructure-level risk. Feature flags handle code-level risk separately.

With feature flags, you can deploy new code to all instances but control who sees the new behavior:

if (featureFlags.isEnabled("new-checkout-flow", user)) {
    return newCheckoutService.process(request);
} else {
    return legacyCheckoutService.process(request);
}

Feature flags allow:

  • Canary behavior without canary infrastructure (by enabling for X% of users in code)
  • Instant disable of a feature without deployment
  • A/B testing at the application level
  • Dark launches (deploy the feature, enable for internal users first)

Combine deployment strategies with feature flags for fine-grained risk control.

Observability Requirements

All deployment strategies require observability to be effective. You need to:

  • Distinguish v1 and v2 traffic in your metrics and logs
  • Measure error rates and latency per version during the deployment window
  • Detect anomalies within minutes of the switch/promotion
  • Know when to rollback based on data, not instinct

Without this, “it deployed successfully” means “the process completed without error” — not “users are being served correctly by the new version.”

Choosing

Rolling Blue/Green Canary
Infrastructure cost Low Low
Rollback speed Slow Instant Fast
Observability required Moderate Low High
Database complexity Moderate High Moderate
Deployment complexity Low Medium High
Risk reduction Moderate Moderate High

Start with rolling deployments. They’re good enough for most services. Add blue/green when instant rollback is critical for a specific service. Add canary when you need progressive validation on high-traffic, user-facing services.