Blue-green deployment maintains two identical production environments — “blue” running the current release, “green” the new version — and switches traffic only after green passes all tests, giving you zero-downtime releases with instant rollback. The testing discipline is what makes the pattern safe: smoke tests, data-migration validation, and traffic-switch verification on the green environment before a single user touches it. This guide covers how to test each phase of a blue-green release.

TL;DR

  • Before the switch, run smoke tests (health endpoint, database connectivity, critical dependencies) and integration tests against the green environment
  • Validate database migrations — schema version, indexes, foreign keys — and keep schema changes backwards compatible so rolling back to blue stays possible
  • Shift traffic to green gradually, watch error rate and p99 latency at each step, and compare blue and green responses
  • Automate rollback: define metric thresholds that switch traffic back to blue

Best for: Teams that release with two identical production environments and need to validate green before the traffic switch Skip if: You cannot run two production environments in parallel — blue-green has the highest resource cost of the strategies compared below

What is Blue-Green Deployment?

Blue-green deployment is a release strategy that maintains two identical production environments: “blue” (current production) and “green” (new version). Traffic switches from blue to green only after the green environment passes all tests, enabling instant rollback if issues arise.

Key benefits:

  • Zero downtime during deployments
  • Instant rollback capability (just switch traffic back)
  • Full production environment testing before going live
  • Reduced deployment risk and stress

How it differs from other strategies:

StrategyDowntimeRollback SpeedResource CostComplexity
Blue-GreenNoneInstantHigh (2x)Medium
RollingMinimalSlowLowLow
CanaryNoneMediumMediumHigh
RecreateHighSlowLowVery Low

Testing Fundamentals for Blue-Green Deployments

Pre-Deployment Testing Phase

Before switching traffic to your green environment, you need comprehensive validation:

1. Smoke Tests Quick sanity checks that verify basic functionality:

#!/bin/bash
# smoke-test.sh - Basic health check for green environment

GREEN_URL="https://green.example.com"

# Check application is responding
if ! curl -f -s "${GREEN_URL}/health" > /dev/null; then
    echo "❌ Health endpoint not responding"
    exit 1
fi

# Verify database connectivity
if ! curl -f -s "${GREEN_URL}/api/db-check" | grep -q "OK"; then
    echo "❌ Database connection failed"
    exit 1
fi

# Check critical dependencies
for service in redis kafka elasticsearch; do
    if ! curl -f -s "${GREEN_URL}/api/check/${service}" | grep -q "healthy"; then
        echo "❌ ${service} dependency check failed"
        exit 1
    fi
done

echo "✅ All smoke tests passed"

2. Integration Tests Verify that all system components work together:

# test_green_integration.py
import pytest
import requests

GREEN_BASE_URL = "https://green.example.com"

def test_user_registration_flow():
    """Test complete user registration workflow"""
    # Create user
    response = requests.post(f"{GREEN_BASE_URL}/api/users", json={
        "email": "test@example.com",
        "password": "SecurePass123!"
    })
    assert response.status_code == 201
    user_id = response.json()["id"]

    # Verify email sent
    email_check = requests.get(f"{GREEN_BASE_URL}/api/emails/{user_id}")
    assert email_check.json()["type"] == "verification"

    # Complete verification
    token = email_check.json()["token"]
    verify = requests.post(f"{GREEN_BASE_URL}/api/verify", json={"token": token})
    assert verify.status_code == 200

def test_payment_processing():
    """Verify payment gateway integration"""
    response = requests.post(f"{GREEN_BASE_URL}/api/payments", json={
        "amount": 1000,
        "currency": "USD",
        "method": "card"
    })
    assert response.status_code == 200
    assert response.json()["status"] == "processed"

3. Database Migration Validation Critical for ensuring data integrity:

-- validate_migration.sql
-- Run these checks before traffic switch

-- 1. Verify schema version
SELECT version FROM schema_migrations
ORDER BY version DESC LIMIT 1;
-- Expected: 20251102_latest_migration

-- 2. Check data consistency
SELECT
    (SELECT COUNT(*) FROM users) as total_users,
    (SELECT COUNT(*) FROM users WHERE created_at > NOW() - INTERVAL '1 hour') as recent_users;
-- Recent users should be 0 (green is new)

-- 3. Validate indexes
SELECT schemaname, tablename, indexname
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename IN ('users', 'orders', 'products');
-- All expected indexes must exist

-- 4. Check foreign key constraints
SELECT COUNT(*) FROM information_schema.table_constraints
WHERE constraint_type = 'FOREIGN KEY'
AND table_schema = 'public';
-- Should match blue environment count

Post-Switch Validation

After switching traffic to green, monitor these critical metrics:

1. Golden Signals Monitoring

# prometheus-alerts.yml - Monitor green environment
groups:

  - name: blue_green_deployment
    interval: 30s
    rules:
      # Latency spike detection
      - alert: GreenLatencyHigh
        expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{env="green"}[5m])) > 1.5
        for: 2m
        annotations:
          summary: "Green environment showing high latency"

      # Error rate increase
      - alert: GreenErrorRateHigh
        expr: rate(http_requests_total{env="green", status=~"5.."}[5m]) > 0.05
        for: 1m
        annotations:
          summary: "Green error rate exceeds 5%"

      # Traffic saturation
      - alert: GreenSaturation
        expr: rate(http_requests_total{env="green"}[1m]) > 10000
        for: 5m
        annotations:
          summary: "Green environment handling high load"

2. Comparison Testing Run parallel traffic analysis between blue and green:

# parallel_test.py - Compare blue vs green responses
import asyncio
import aiohttp
import statistics

async def compare_endpoints(endpoint, iterations=100):
    """Compare response times and results between blue and green"""
    blue_times = []
    green_times = []
    discrepancies = []

    async with aiohttp.ClientSession() as session:
        for i in range(iterations):
            # Test blue
            start = asyncio.get_event_loop().time()
            async with session.get(f"https://blue.example.com{endpoint}") as resp:
                blue_result = await resp.json()
                blue_times.append(asyncio.get_event_loop().time() - start)

            # Test green
            start = asyncio.get_event_loop().time()
            async with session.get(f"https://green.example.com{endpoint}") as resp:
                green_result = await resp.json()
                green_times.append(asyncio.get_event_loop().time() - start)

            # Check for discrepancies
            if blue_result != green_result:
                discrepancies.append({
                    'iteration': i,
                    'blue': blue_result,
                    'green': green_result
                })

    return {
        'blue_avg': statistics.mean(blue_times),
        'green_avg': statistics.mean(green_times),
        'blue_p99': statistics.quantiles(blue_times, n=100)[98],
        'green_p99': statistics.quantiles(green_times, n=100)[98],
        'discrepancies': len(discrepancies),
        'discrepancy_rate': len(discrepancies) / iterations
    }

# Run comparison
results = asyncio.run(compare_endpoints('/api/products'))
print(f"Blue avg: {results['blue_avg']:.3f}s, Green avg: {results['green_avg']:.3f}s")
print(f"Discrepancy rate: {results['discrepancy_rate']*100:.2f}%")

Advanced Testing Techniques

Shadow Traffic Testing

Send duplicate production traffic to green environment without impacting users:

# nginx.conf - Shadow traffic to green environment
upstream blue_backend {
    server blue.example.com:8080;
}

upstream green_backend {
    server green.example.com:8080;
}

server {
    listen 80;

    location / {
        # Primary traffic goes to blue
        proxy_pass http://blue_backend;

        # Mirror traffic to green (async, no response used)
        mirror /mirror;
        mirror_request_body on;
    }

    location /mirror {
        internal;
        proxy_pass http://green_backend$request_uri;
        proxy_set_header X-Shadow-Request "true";
    }
}

Benefits of shadow testing:

  • Test green with real production patterns
  • No user impact if green fails
  • Validate performance under actual load
  • Discover edge cases missed in testing

Synthetic Transaction Monitoring

Deploy continuous synthetic tests that mimic real user behavior:

// synthetic-monitor.js - Datadog/New Relic style
const puppeteer = require('puppeteer');

async function runSyntheticTest(environment) {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    try {
        // Monitor page load time
        const startTime = Date.now();
        await page.goto(`https://${environment}.example.com`);
        const loadTime = Date.now() - startTime;

        // Test critical user journey
        await page.click('#search-input');
        await page.type('#search-input', 'test product');
        await page.click('#search-button');
        await page.waitForSelector('.search-results');

        // Add to cart
        await page.click('.product-card:first-child .add-to-cart');
        await page.waitForSelector('.cart-notification');

        // Verify cart
        await page.click('#cart-icon');
        const cartItems = await page.$$('.cart-item');

        return {
            success: cartItems.length > 0,
            loadTime: loadTime,
            environment: environment,
            timestamp: new Date().toISOString()
        };
    } catch (error) {
        return {
            success: false,
            error: error.message,
            environment: environment
        };
    } finally {
        await browser.close();
    }
}

// Run every 5 minutes
setInterval(async () => {
    const greenResults = await runSyntheticTest('green');
    if (!greenResults.success) {
        // Alert on failure
        console.error('❌ Green synthetic test failed:', greenResults);
    }
}, 5 * 60 * 1000);

Database State Validation

Ensure database consistency between blue and green:

# db_validator.py - Compare database states
import psycopg2
from datetime import datetime, timedelta

def compare_databases(blue_conn, green_conn):
    """Compare critical database metrics between environments"""
    checks = []

    # 1. Row counts must match (with tolerance for recent writes)
    tables = ['users', 'orders', 'products', 'inventory']
    for table in tables:
        blue_count = execute_query(blue_conn, f"SELECT COUNT(*) FROM {table}")
        green_count = execute_query(green_conn, f"SELECT COUNT(*) FROM {table}")

        # Allow 1% difference for active writes
        tolerance = blue_count * 0.01
        if abs(blue_count - green_count) > tolerance:
            checks.append({
                'table': table,
                'status': 'FAIL',
                'blue_count': blue_count,
                'green_count': green_count,
                'difference': abs(blue_count - green_count)
            })
        else:
            checks.append({
                'table': table,
                'status': 'PASS'
            })

    # 2. Check recent data replication
    cutoff = datetime.now() - timedelta(hours=1)
    for table in ['orders', 'user_sessions']:
        query = f"SELECT COUNT(*) FROM {table} WHERE updated_at > %s"
        blue_recent = execute_query(blue_conn, query, (cutoff,))
        green_recent = execute_query(green_conn, query, (cutoff,))

        # Green should have similar or more recent data
        if green_recent < blue_recent * 0.95:
            checks.append({
                'check': f'{table}_recent_data',
                'status': 'FAIL',
                'message': 'Green missing recent updates'
            })

    return checks

def execute_query(conn, query, params=None):
    with conn.cursor() as cur:
        cur.execute(query, params)
        return cur.fetchone()[0]

Implementation Patterns

Canary Analysis and Gradual Rollout

A blue-green switch is often combined with canary analysis and a gradual rollout:

  1. Canary analysis - Send a small share of traffic to the new version first
  2. Failure injection - Inject failures in green to test resilience
  3. Metric comparison - Compare key metrics between the old and the new version
  4. Gradual rollout - Increase traffic to green step by step while watching the metrics
  5. Automatic rollback - Triggered if metrics degrade beyond thresholds

Key metrics to monitor:

  • Request latency (p50, p90, p99)
  • Error rates by service
  • Success rate of critical user flows

Blue/Green on AWS Elastic Beanstalk

Elastic Beanstalk supports blue/green deployments through a CNAME swap. The AWS documentation describes the process:

  1. Clone the current environment, or launch a new one
  2. Deploy the new application version to the new environment
  3. Test the new version on the new environment
  4. Swap the environment URLs — Elastic Beanstalk swaps the CNAME records of the two environments, redirecting traffic from the old version to the new one
  5. Verify that the new environment responds on the old environment URL, and do not terminate the old environment until the DNS changes have propagated and the old DNS records have expired

The same page notes that a blue/green deployment requires the environment to run independently of the production database.

Dual-Write Database Migration

A dual-write strategy handles database migrations in blue-green deployments by writing to both the old and the new schema:

Phase 1: Dual-write mode

# Write to both old and new schema
def save_user(user_data):
    # Write to old schema (blue)
    old_db.users.insert({
        'name': user_data['name'],
        'email': user_data['email']
    })

    # Write to new schema (green)
    new_db.users.insert({
        'full_name': user_data['name'],
        'email_address': user_data['email'],
        'created_at': datetime.now()
    })

Phase 2: Read from new, validate against old

def get_user(user_id):
    # Read from new schema
    user = new_db.users.find_one({'_id': user_id})

    # Async validation against old schema
    asyncio.create_task(validate_data(user_id, user))

    return user

async def validate_data(user_id, new_data):
    old_data = old_db.users.find_one({'_id': user_id})
    if not data_matches(old_data, new_data):
        log_discrepancy(user_id, old_data, new_data)

Best Practices

✅ Pre-Deployment Checklist

Create a comprehensive checklist for every deployment:

  • All automated tests passing in green environment
  • Database migrations completed successfully
  • Schema changes are backwards compatible
  • Feature flags configured for new features
  • Load testing completed with production-like traffic
  • Security scanning passed (OWASP, dependency audit)
  • Smoke tests executed successfully
  • Monitoring dashboards created for new features
  • Rollback plan documented and tested
  • On-call team notified and available
  • Customer-facing documentation updated
  • Internal runbooks updated

✅ Monitoring and Alerting

Set up comprehensive monitoring before switching traffic:

Critical metrics to track:

# Key Performance Indicators (KPIs)
response_time:
  p50: < 100ms
  p95: < 300ms
  p99: < 1000ms

error_rate:
  warning: > 0.5%
  critical: > 1%

throughput:
  min_rps: 1000  # Should handle normal load
  max_rps: 5000  # Should handle peak

resource_usage:
  cpu: < 70%
  memory: < 80%
  disk: < 75%

dependencies:
  database_connections: < 80% of pool
  cache_hit_rate: > 90%
  queue_depth: < 1000 messages

✅ Gradual Traffic Shifting

Don’t switch 100% of traffic immediately:

# traffic_controller.py - Gradual traffic shift
import time

def gradual_traffic_shift(duration_minutes=60):
    """Shift traffic from blue to green over specified duration"""
    steps = [1, 5, 10, 25, 50, 75, 100]  # Percentage to green
    step_duration = duration_minutes * 60 / len(steps)

    for percentage in steps:
        print(f"Shifting {percentage}% traffic to green...")
        update_load_balancer(green_weight=percentage, blue_weight=100-percentage)

        # Monitor for issues
        time.sleep(step_duration)
        metrics = get_green_metrics()

        if metrics['error_rate'] > 0.01 or metrics['p99_latency'] > 1.5:
            print(f"❌ Metrics degraded at {percentage}%, rolling back")
            rollback_to_blue()
            return False

        print(f"✅ {percentage}% traffic handling well")

    return True

✅ Automated Rollback Triggers

Implement automatic rollback based on metrics:

# auto_rollback.py
from prometheus_api_client import PrometheusConnect

prom = PrometheusConnect(url="http://prometheus:9090")

def check_rollback_conditions():
    """Check if automatic rollback should trigger"""

    # 1. Error rate spike
    error_rate_query = 'rate(http_requests_total{env="green",status=~"5.."}[5m])'
    error_rate = prom.custom_query(error_rate_query)[0]['value'][1]
    if float(error_rate) > 0.05:  # 5% error rate
        return True, "Error rate exceeded 5%"

    # 2. Latency degradation
    latency_query = 'histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{env="green"}[5m]))'
    p99_latency = prom.custom_query(latency_query)[0]['value'][1]
    if float(p99_latency) > 2.0:  # 2 second p99
        return True, "P99 latency exceeded 2 seconds"

    # 3. Resource exhaustion
    cpu_query = 'avg(rate(container_cpu_usage_seconds_total{env="green"}[5m]))'
    cpu_usage = prom.custom_query(cpu_query)[0]['value'][1]
    if float(cpu_usage) > 0.9:  # 90% CPU
        return True, "CPU usage exceeded 90%"

    return False, None

# Run every 30 seconds
while True:
    should_rollback, reason = check_rollback_conditions()
    if should_rollback:
        print(f"🚨 AUTOMATIC ROLLBACK TRIGGERED: {reason}")
        execute_rollback()
        send_alert(reason)
        break
    time.sleep(30)

Common Pitfalls and How to Avoid Them

⚠️ Database Schema Incompatibility

Problem: New code requires schema changes that break old code during rollback.

Solution: Use backwards-compatible migrations:

# BAD - Breaking change
# Migration 1: Add NOT NULL column
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NOT NULL;

# GOOD - Backwards compatible
# Migration 1: Add nullable column
ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL;

# Migration 2: Backfill data
UPDATE users SET phone = 'UNKNOWN' WHERE phone IS NULL;

# Migration 3: Add constraint (deploy after traffic fully on green)
ALTER TABLE users ALTER COLUMN phone SET NOT NULL;

⚠️ Session State Issues

Problem: User sessions lost or corrupted during traffic switch.

Solution: Use centralized session storage:

# BAD - In-memory sessions (lost on environment switch)
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'secret'

@app.route('/login')
def login():
    session['user_id'] = 123  # Stored locally, lost on switch

# GOOD - Redis-backed sessions (persistent across environments)
from flask import Flask
from flask_session import Session
import redis

app = Flask(__name__)
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://shared-redis:6379')
Session(app)

@app.route('/login')
def login():
    session['user_id'] = 123  # Stored in Redis, survives switch

⚠️ Third-Party API Rate Limits

Problem: Green environment gets rate-limited because blue already used quota.

Solution: Request separate API keys or implement smart rate limiting:

# rate_limit_manager.py
class EnvironmentAwareRateLimiter:
    def __init__(self, redis_client):
        self.redis = redis_client
        self.env = os.getenv('ENVIRONMENT')  # 'blue' or 'green'

    def check_limit(self, api_name, limit_per_hour):
        """Check rate limit with environment-specific keys"""
        key = f"ratelimit:{self.env}:{api_name}:{datetime.now().hour}"
        current = self.redis.incr(key)
        self.redis.expire(key, 3600)  # 1 hour TTL

        return current <= limit_per_hour

    def use_quota(self, api_name):
        """Use quota from shared pool if blue environment"""
        if self.env == 'blue':
            # Use production quota
            return self.check_limit(api_name, 10000)
        else:
            # Use reduced quota for green testing
            return self.check_limit(api_name, 1000)

⚠️ Static Asset Caching

Problem: Users get old JavaScript/CSS from CDN cache after deployment.

Solution: Use cache-busting with versioned assets:

<!-- BAD - Same URL, cache may serve old version -->
<script src="/static/app.js"></script>

<!-- GOOD - Unique URL per build, no cache issues -->
<script src="/static/app.js?v=build-20251102-1534"></script>

<!-- BETTER - Content-based hashing -->
<script src="/static/app.a8f3d9e2.js"></script>

Tools and Frameworks

Terraform for Infrastructure

# blue-green.tf - Complete blue-green setup
resource "aws_lb_target_group" "blue" {
  name     = "app-blue-tg"
  port     = 8080
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    path                = "/health"
    interval            = 30
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}

resource "aws_lb_target_group" "green" {
  name     = "app-green-tg"
  port     = 8080
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    path                = "/health"
    interval            = 30
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }
}

resource "aws_lb_listener_rule" "production" {
  listener_arn = aws_lb_listener.main.arn
  priority     = 100

  action {
    type             = "forward"
    target_group_arn = var.active_environment == "blue" ?
                       aws_lb_target_group.blue.arn :
                       aws_lb_target_group.green.arn
  }

  condition {
    path_pattern {
      values = ["/*"]
    }
  }
}

Spinnaker for Orchestration

Open-source, multi-cloud continuous delivery platform with built-in support for the blue/green strategy (Spinnaker concepts):

FeatureDescriptionBest For
Pipeline TemplatesReusable deployment workflowsStandardizing deployments
Automated Canary AnalysisStatistical comparison of metricsRisk reduction
Multi-Cloud SupportAWS, GCP, Azure, KubernetesHybrid environments
RBACRole-based access controlEnterprise security

Pros:

  • ✅ Comprehensive deployment strategies support
  • ✅ Strong Kubernetes integration
  • ✅ Active community

Cons:

  • ❌ Complex setup and configuration
  • ❌ Steep learning curve
  • ❌ Resource-intensive (requires dedicated cluster)

AWS CodeDeploy

Native AWS service for automated deployments:

# appspec.yml - CodeDeploy configuration
version: 0.0
Resources:

  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "arn:aws:ecs:us-east-1:123456:task-definition/app:2"
        LoadBalancerInfo:
          ContainerName: "app"
          ContainerPort: 8080
        PlatformVersion: "LATEST"

Hooks:

  - BeforeInstall: "scripts/pre-deployment-tests.sh"
  - AfterInstall: "scripts/smoke-tests.sh"
  - AfterAllowTestTraffic: "scripts/integration-tests.sh"
  - BeforeAllowTraffic: "scripts/validation.sh"
  - AfterAllowTraffic: "scripts/post-deployment-monitoring.sh"

Flagger for Kubernetes

Progressive delivery operator for Kubernetes:

# flagger-canary.yml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: app
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app
  service:
    port: 8080
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:

    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 1m
    webhooks:

    - name: load-test
      url: http://flagger-loadtester/
      timeout: 5s
      metadata:
        cmd: "hey -z 1m -q 10 -c 2 http://app:8080/"

Conclusion

Blue-green deployment testing is not just about having two environments—it’s about building confidence through comprehensive validation at every step. By implementing the testing strategies, monitoring practices, and automation tools covered in this guide, you can switch traffic with confidence and roll back quickly when something goes wrong.

Key takeaways:

  1. Test comprehensively before switching - Smoke tests, integration tests, and database validation are non-negotiable
  2. Use gradual traffic shifting - Don’t switch 100% at once; monitor metrics at each step
  3. Automate rollback decisions - Define clear thresholds and let systems react faster than humans can
  4. Maintain backwards compatibility - Especially critical for database schemas and API contracts
  5. Monitor the right metrics - Focus on latency, errors, saturation, and traffic (the four golden signals)

Next steps:

  • Start with automated smoke tests for your current deployment process
  • Implement health checks and monitoring before your next release
  • Gradually introduce blue-green deployments to one service at a time
  • Build confidence through repetition and continuous improvement

For more DevOps testing strategies, explore our guides on Kubernetes testing, CI/CD pipeline optimization, and infrastructure as code testing.

Additional resources:

FAQ

What is blue-green deployment testing? Blue-green deployment testing is the validation of a new release on the green environment before and after traffic is switched from blue, the current production. It includes smoke tests, integration tests and database migration validation before the switch, then monitoring of latency, error rate and saturation after it. Traffic moves to green only after green passes all tests.

What should you test on the green environment before switching traffic? Run smoke tests that check the health endpoint, database connectivity and critical dependencies, then integration tests for key flows such as user registration and payment processing. Validate database migrations: schema version, data consistency, indexes and foreign key constraints. The pre-deployment checklist also lists load testing with production-like traffic, security scanning and a documented, tested rollback plan.

How do you validate the traffic switch in a blue-green deployment? Do not switch 100% of traffic at once: shift it to green in steps and check error rate and p99 latency at each step. After the switch, monitor the golden signals and compare blue and green responses for discrepancies. Shadow traffic and synthetic transaction tests let you exercise green with real production patterns without impacting users.

How do you handle database migrations in a blue-green deployment? Use backwards-compatible migrations so the old code keeps working if you roll back: add a nullable column, backfill the data, and add the NOT NULL constraint only after traffic is fully on green. Before the switch, verify the schema version, indexes and foreign key constraints, and compare row counts between blue and green. A dual-write strategy writes to both the old and the new schema and validates reads from the new schema against the old one.

How do you prepare and test rollback in a blue-green deployment? Rollback means switching traffic back to the blue environment. The rollback plan should be documented and tested before the release, and schema changes must stay backwards compatible so the old code still works. Automated triggers can roll back when the error rate, p99 latency or CPU usage on green crosses a defined threshold.

See Also