0

Blue/green deployment — two identical environments, flip traffic on success

Intermediate5 min read·eng-18-009
interview

Concept

Blue-green deployment — a release strategy that maintains two identical production environments (Blue and Green). Only one environment serves live traffic at a time. To deploy, update the idle environment, verify it, then switch the load balancer to it.

The blue-green cycle:

  1. Blue is live (serving all traffic). Green is idle.
  2. Deploy new code to Green. Run migrations, tests, smoke checks on Green while Blue continues serving.
  3. Switch the load balancer to Green. Now Green serves all traffic.
  4. Blue is now idle. Keep it warm for a few minutes.
  5. If Green has issues → switch load balancer BACK to Blue (instant rollback).
  6. Next deployment: deploy to Blue (now idle), switch back to Blue.

Benefits:

  • Zero downtime: traffic switch is nearly instantaneous.
  • Instant rollback: switch load balancer back to the old environment.
  • Smoke testing on production infrastructure before going live.

Drawbacks:

  • Double the infrastructure cost (two full environments).
  • Database migrations are tricky: the new schema must be compatible with BOTH Blue code and Green code (during the period when you might roll back).
  • Long-running transactions during the switch need careful handling.

vs Canary deployments: Blue-green is all-or-nothing. Canary starts with 5% of traffic on new code, gradually increases, rolling back if errors spike.

In cloud environments: AWS CodeDeploy supports blue-green. AWS Elastic Beanstalk has built-in blue-green. Laravel Vapor uses blue-green under the hood (AWS Lambda versions).

Code Example

bash
# Blue-green deployment with Nginx
# Current state: Blue (nginx upstream = blue_servers) serving traffic

# 1. Deploy new code to Green environment
ssh app-green-1 "git pull && composer install && php artisan migrate --force && php artisan optimize"
ssh app-green-2 "git pull && composer install && php artisan optimize"

# 2. Smoke test Green (not yet live)
curl -f https://green.internal.example.com/health
curl -f https://green.internal.example.com/api/ping

# 3. Switch load balancer from Blue to Green
# /etc/nginx/upstream.conf
upstream active_servers {
    # server blue_1:9000;   # blue servers commented out
    # server blue_2:9000;
    server green_1:9000;    # green servers now active
    server green_2:9000;
}

nginx -s reload  # graceful reload — in-flight requests finish, new ones go to green

# 4. Monitor errors for 5-10 minutes

# 5a. Success — clean up Blue (will be used for next deployment)
echo "Deployment successful. Blue is now standby."

# 5b. Rollback — just switch back to Blue
upstream active_servers {
    server blue_1:9000;
    server blue_2:9000;
    # server green_1:9000;
    # server green_2:9000;
}
nginx -s reload  # instant rollback
php
<?php
// DATABASE compatibility requirement during blue-green:
// While Green is live, Blue might need to serve traffic during rollback
// New schema must work with BOTH old and new application code

// Safe: adding a nullable column (both versions can work with it)
Schema::table('orders', function (Blueprint $table) {
    $table->string('new_field')->nullable()->default(null);
});

// Unsafe: removing a column (old code (Blue) still reads it)
// Solution: 3-phase removal
// Phase 1: Stop reading the column in code (but don't drop it yet)
// Phase 2: Deploy phase 1, confirm Blue is retired
// Phase 3: Drop the column in a subsequent migration