Deployment Strategies Guide
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:
- ✓ Always have a rollback plan
- ✓ Deploy during low-traffic hours
- ✓ Use gradual rollouts (canary/rolling)
- ✓ Implement comprehensive health checks
- ✓ Monitor metrics during deployment
- ✓ Use feature flags for risky changes
- ✓ Automate deployment process
- ✓ Test in staging environment first
- ✓ Implement graceful shutdown
- ✓ Use immutable infrastructure
- ✓ Version all artifacts (Docker images, packages)
- ✓ Document deployment procedures
- ✓ Set up automated rollback triggers
- ✓ Maintain backward compatibility
- ✓ Use database migration tools
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.