← Back to Guides

Deployment Strategies Guide

📖 16 min read | 📅 Updated: January 2025 | 🏷️ DevOps & Cloud

Introduction

Modern deployment strategies minimize downtime and risk. This guide covers blue-green deployments, canary releases, rolling updates, A/B testing, feature flags, and zero-downtime deployment patterns for production systems.

1. Blue-Green Deployment

# Blue-Green Deployment Concept
# Two identical environments:
# - Blue (current production)
# - Green (new version)
# Switch traffic instantly

# AWS Elastic Beanstalk - Blue-Green
aws elasticbeanstalk create-environment \
  --application-name myapp \
  --environment-name myapp-green \
  --version-label v2.0.0 \
  --cname-prefix myapp-green

# Test green environment
curl https://myapp-green.us-east-1.elasticbeanstalk.com

# Swap URLs (instant switch)
aws elasticbeanstalk swap-environment-cnames \
  --source-environment-name myapp-blue \
  --destination-environment-name myapp-green

# Kubernetes Blue-Green with Services
# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
        - name: app
          image: myapp:1.0.0
          ports:
            - containerPort: 3000

---
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
        - name: app
          image: myapp:2.0.0
          ports:
            - containerPort: 3000

---
# service.yaml - Initially points to blue
apiVersion: v1
kind: Service
metadata:
  name: app-service
spec:
  selector:
    app: myapp
    version: blue  # Change to green for switch
  ports:
    - port: 80
      targetPort: 3000

# Deploy green
kubectl apply -f green-deployment.yaml

# Test green internally
kubectl port-forward deployment/app-green 3000:3000

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

# Rollback if needed
kubectl patch service app-service -p '{"spec":{"selector":{"version":"blue"}}}'

# Nginx Blue-Green with Upstream
# nginx.conf
upstream backend {
    server blue.internal:3000;  # Change to green.internal:3000
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

# Reload without downtime
nginx -s reload

2. Canary Deployment

# Canary - Gradual rollout to subset of users

# Kubernetes with Ingress weights
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"  # 10% to canary
spec:
  rules:
    - host: myapp.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app-canary
                port:
                  number: 80

# Gradual increase
# 10% -> 25% -> 50% -> 75% -> 100%
kubectl patch ingress app-ingress -p \
  '{"metadata":{"annotations":{"nginx.ingress.kubernetes.io/canary-weight":"25"}}}'

# AWS ALB Canary with Target Groups
aws elbv2 modify-listener \
  --listener-arn arn:aws:elasticloadbalancing:... \
  --default-actions \
    Type=forward,ForwardConfig='{
      "TargetGroups":[
        {"TargetGroupArn":"arn:aws:elasticloadbalancing:.../stable","Weight":90},
        {"TargetGroupArn":"arn:aws:elasticloadbalancing:.../canary","Weight":10}
      ]
    }'

# Istio Canary with VirtualService
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: app
spec:
  hosts:
    - myapp.com
  http:
    - match:
        - headers:
            user-agent:
              regex: ".*mobile.*"
      route:
        - destination:
            host: app-canary
          weight: 100
    - route:
        - destination:
            host: app-stable
          weight: 90
        - destination:
            host: app-canary
          weight: 10

# Automated canary with Flagger
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: app
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app
  service:
    port: 80
  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/
      metadata:
        cmd: "hey -z 1m -q 10 -c 2 http://app-canary/"

3. Rolling Updates

# Kubernetes Rolling Update (default)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2        # 2 extra pods during update
      maxUnavailable: 1  # Max 1 pod down at a time
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:2.0.0
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 5

# Update image
kubectl set image deployment/app app=myapp:2.0.0

# Monitor rollout
kubectl rollout status deployment/app

# Rollout history
kubectl rollout history deployment/app

# Rollback
kubectl rollout undo deployment/app
kubectl rollout undo deployment/app --to-revision=2

# Pause/Resume rollout
kubectl rollout pause deployment/app
kubectl rollout resume deployment/app

# Docker Swarm Rolling Update
docker service update \
  --image myapp:2.0.0 \
  --update-parallelism 2 \
  --update-delay 10s \
  --update-failure-action rollback \
  myapp

# AWS ECS Rolling Update
aws ecs update-service \
  --cluster production \
  --service myapp \
  --task-definition myapp:2 \
  --deployment-configuration \
    minimumHealthyPercent=100,maximumPercent=200

4. Feature Flags

# Feature flag implementation

// Simple feature flags
export const features = {
  newCheckout: process.env.ENABLE_NEW_CHECKOUT === 'true',
  darkMode: process.env.ENABLE_DARK_MODE === 'true',
  betaFeatures: process.env.ENABLE_BETA === 'true'
};

// Usage
import { features } from './config';

function renderCheckout() {
  if (features.newCheckout) {
    return ;
  }
  return ;
}

// Advanced with LaunchDarkly
import * as LDClient from 'launchdarkly-node-server-sdk';

const client = LDClient.init(process.env.LAUNCHDARKLY_SDK_KEY);

await client.waitForInitialization();

// Check flag
const user = { key: userId, email: userEmail };
const showNewFeature = await client.variation('new-feature', user, false);

if (showNewFeature) {
  // New feature code
}

// Percentage rollout
const rolloutPercentage = await client.variation('feature-rollout', user, 0);
if (Math.random() * 100 < rolloutPercentage) {
  // Enable for percentage of users
}

// Custom feature flag service
class FeatureFlagService {
  constructor() {
    this.flags = new Map();
    this.loadFromDatabase();
  }
  
  async loadFromDatabase() {
    const flags = await db.query('SELECT * FROM feature_flags');
    flags.forEach(flag => {
      this.flags.set(flag.name, {
        enabled: flag.enabled,
        percentage: flag.percentage,
        allowlist: flag.allowlist
      });
    });
  }
  
  isEnabled(flagName, userId) {
    const flag = this.flags.get(flagName);
    if (!flag) return false;
    if (!flag.enabled) return false;
    
    // Allowlist check
    if (flag.allowlist && flag.allowlist.includes(userId)) {
      return true;
    }
    
    // Percentage rollout
    if (flag.percentage) {
      const hash = this.hashUserId(userId);
      return (hash % 100) < flag.percentage;
    }
    
    return flag.enabled;
  }
  
  hashUserId(userId) {
    // Consistent hash for same user
    let hash = 0;
    for (let i = 0; i < userId.length; i++) {
      hash = ((hash << 5) - hash) + userId.charCodeAt(i);
      hash |= 0;
    }
    return Math.abs(hash);
  }
}

// React feature flag hook
function useFeatureFlag(flagName) {
  const { user } = useAuth();
  const [enabled, setEnabled] = useState(false);
  
  useEffect(() => {
    const checkFlag = async () => {
      const result = await featureFlagService.isEnabled(flagName, user.id);
      setEnabled(result);
    };
    checkFlag();
  }, [flagName, user.id]);
  
  return enabled;
}

// Usage
function MyComponent() {
  const showNewUI = useFeatureFlag('new-ui');
  
  return showNewUI ?  : ;
}

5. A/B Testing

# A/B testing implementation

// Simple A/B test
function getVariant(userId, experimentName) {
  const hash = hashString(`${experimentName}-${userId}`);
  return hash % 2 === 0 ? 'A' : 'B';
}

// Usage
const variant = getVariant(user.id, 'checkout-redesign');

if (variant === 'A') {
  return ;
} else {
  return ;
}

// Track metrics
analytics.track('checkout_completed', {
  experiment: 'checkout-redesign',
  variant: variant,
  revenue: order.total
});

// Google Optimize integration


// Split.io for A/B testing
import { SplitFactory } from '@splitsoftware/splitio';

const factory = SplitFactory({
  core: {
    authorizationKey: process.env.SPLIT_IO_KEY,
    key: userId
  }
});

const client = factory.client();

await client.ready();

const treatment = client.getTreatment('checkout-redesign');

if (treatment === 'on') {
  // Show new checkout
} else {
  // Show original
}

// Track events
client.track('user', 'checkout_completed', order.total);

// Multi-variant testing
const treatments = ['control', 'variant_a', 'variant_b', 'variant_c'];
const treatment = client.getTreatment('multi-variant-test');

switch (treatment) {
  case 'variant_a':
    return ;
  case 'variant_b':
    return ;
  case 'variant_c':
    return ;
  default:
    return ;
}

// Statistical significance
function calculateSignificance(controlConversion, variantConversion, controlSample, variantSample) {
  const z = (variantConversion - controlConversion) / 
    Math.sqrt((controlConversion * (1 - controlConversion) / controlSample) +
              (variantConversion * (1 - variantConversion) / variantSample));
  
  const pValue = 1 - normalCDF(Math.abs(z));
  
  return {
    zScore: z,
    pValue: pValue,
    significant: pValue < 0.05,
    improvement: ((variantConversion - controlConversion) / controlConversion) * 100
  };
}

6. Zero-Downtime Deployments

# Database migrations without downtime

# Step 1: Add new column (nullable)
ALTER TABLE users ADD COLUMN email_verified BOOLEAN NULL;

# Deploy application that writes to both old and new columns
# (backward compatible)

# Step 2: Backfill data
UPDATE users SET email_verified = is_verified WHERE email_verified IS NULL;

# Deploy application using new column

# Step 3: Remove old column
ALTER TABLE users DROP COLUMN is_verified;

# Expand-Contract pattern
# Expand: Add new schema alongside old
# Contract: Remove old schema after migration

# Node.js graceful shutdown
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, closing server...');
  
  server.close(async () => {
    console.log('HTTP server closed');
    
    // Close database connections
    await db.close();
    
    // Close message queues
    await queue.close();
    
    // Close cache connections
    await redis.quit();
    
    console.log('All connections closed');
    process.exit(0);
  });
  
  // Force shutdown after 30 seconds
  setTimeout(() => {
    console.error('Forced shutdown');
    process.exit(1);
  }, 30000);
});

// Kubernetes graceful shutdown
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 15"]
      terminationGracePeriodSeconds: 30

# HAProxy zero-downtime reload
haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid)

# Nginx zero-downtime reload
nginx -s reload

# Connection draining
# AWS ALB
aws elbv2 modify-target-group-attributes \
  --target-group-arn arn:aws:elasticloadbalancing:... \
  --attributes Key=deregistration_delay.timeout_seconds,Value=30

7. Rollback Strategies

# Automated rollback triggers

# Kubernetes rollback on high error rate
apiVersion: flagger.app/v1beta1
kind: Canary
spec:
  analysis:
    metrics:
      - name: error-rate
        thresholdRange:
          max: 5  # Max 5% errors
        interval: 1m
    webhooks:
      - name: rollback
        type: rollback
        url: http://flagger-loadtester/rollback

# AWS CodeDeploy automatic rollback
{
  "deploymentConfigName": "CodeDeployDefault.OneAtATime",
  "autoRollbackConfiguration": {
    "enabled": true,
    "events": [
      "DEPLOYMENT_FAILURE",
      "DEPLOYMENT_STOP_ON_ALARM"
    ]
  },
  "alarmConfiguration": {
    "enabled": true,
    "alarms": [
      {
        "name": "HighErrorRate"
      }
    ]
  }
}

# Database rollback with migrations
# migrations/001_up.sql
CREATE TABLE new_users (...);

# migrations/001_down.sql
DROP TABLE new_users;

# Run migration
migrate -path ./migrations -database postgres://... up

# Rollback
migrate -path ./migrations -database postgres://... down 1

# Git-based rollback
git revert abc123
git push origin main

# Or tag-based
git checkout v1.0.0
git tag -a v1.0.0-rollback -m "Rollback to stable"
git push origin v1.0.0-rollback

# Kubernetes rollback
kubectl rollout undo deployment/app
kubectl rollout undo deployment/app --to-revision=3

# Docker rollback
docker service update --rollback myapp

# Infrastructure rollback with Terraform
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.web

8. Health Checks & Readiness

# Health check endpoints

// Express.js health checks
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy' });
});

app.get('/readiness', async (req, res) => {
  try {
    // Check database
    await db.query('SELECT 1');
    
    // Check Redis
    await redis.ping();
    
    // Check external APIs
    await fetch('https://api.example.com/health');
    
    res.status(200).json({ 
      status: 'ready',
      checks: {
        database: 'ok',
        redis: 'ok',
        api: 'ok'
      }
    });
  } catch (error) {
    res.status(503).json({ 
      status: 'not ready',
      error: error.message 
    });
  }
});

// Kubernetes probes
apiVersion: v1
kind: Pod
spec:
  containers:
    - name: app
      livenessProbe:
        httpGet:
          path: /health
          port: 3000
        initialDelaySeconds: 30
        periodSeconds: 10
        timeoutSeconds: 5
        failureThreshold: 3
      
      readinessProbe:
        httpGet:
          path: /readiness
          port: 3000
        initialDelaySeconds: 10
        periodSeconds: 5
        timeoutSeconds: 3
        failureThreshold: 3
      
      startupProbe:
        httpGet:
          path: /health
          port: 3000
        initialDelaySeconds: 0
        periodSeconds: 10
        timeoutSeconds: 3
        failureThreshold: 30

# ALB health checks
aws elbv2 modify-target-group \
  --target-group-arn arn:aws:elasticloadbalancing:... \
  --health-check-enabled \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3

9. Best Practices

✓ Deployment Best Practices:

Conclusion

Modern deployment strategies minimize risk and downtime. Choose blue-green for instant rollback, canary for gradual validation, or rolling updates for zero-downtime. Always combine with feature flags, comprehensive monitoring, and automated rollback for production resilience.

💡 Pro Tip: Implement progressive delivery combining canary deployments with feature flags and automated rollback based on metrics. Use tools like Flagger or Argo Rollouts for Kubernetes to automate the entire process with traffic shifting, metric analysis, and automatic rollback on anomalies.