Deployment Patterns
Choosing the right deployment strategy reduces downtime, limits blast radius, and enables fast rollback. The strategy depends on your infrastructure, team confidence, and risk tolerance.
Blue-Green Deployment
# Blue-Green with Kubernetes and a load balancer
# Two identical deployments; switch traffic by updating the Service selector
# Deploy new version to green
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
slot: green
template:
metadata:
labels:
app: myapp
slot: green
spec:
containers:
- name: myapp
image: myapp:v2.0.0
---
# Service currently pointing to blue
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
slot: blue # switch to 'green' after smoke tests
ports:
- port: 80
targetPort: 3000
# Switching traffic (after green smoke tests pass):
# kubectl patch service myapp -p '{"spec":{"selector":{"slot":"green"}}}'
# Rollback (instant):
# kubectl patch service myapp -p '{"spec":{"selector":{"slot":"blue"}}}'Canary Releases
# Canary with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to canary
- pause: { duration: 5m } # wait 5 min, monitor metrics
- setWeight: 30
- pause: { duration: 10m }
- setWeight: 60
- pause: { duration: 10m }
- setWeight: 100 # full rollout
analysis:
templates:
- templateName: error-rate
args:
- name: service-name
value: myapp
---
# AnalysisTemplate — auto-rollback if error rate > 5%
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: error-rate
spec:
metrics:
- name: error-rate
interval: 1m
successCondition: result[0] < 0.05
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{status=~"5.."}[1m]))
/ sum(rate(http_requests_total[1m]))Feature Flags
// Feature flags — decouple deployment from release
// Tools: LaunchDarkly, Unleash, Flagsmith, GrowthBook, Vercel Edge Config
// LaunchDarkly example
import { init } from 'launchdarkly-node-server-sdk';
const client = init(process.env.LAUNCHDARKLY_SDK_KEY!);
await client.waitForInitialization();
// Evaluate a flag for a user
const context = { kind: 'user', key: user.id, email: user.email, plan: 'pro' };
const showNewDashboard = await client.variation('new-dashboard', context, false);
if (showNewDashboard) {
// Show new feature to % of users
}
// React hook with OpenFeature (vendor-agnostic)
import { useBooleanFlagValue } from '@openfeature/react-sdk';
function Dashboard() {
const showNewChart = useBooleanFlagValue('new-chart', false);
return showNewChart ? <NewChart /> : <OldChart />;
}
// Kill switch pattern — emergency disable without deploy
// 1. Check flag before expensive operation
// 2. Set flag to false in dashboard to instantly disable
// 3. No code change or deployment needed
async function processPayment(order: Order) {
const paymentsEnabled = await flags.get('payments-enabled', true);
if (!paymentsEnabled) throw new Error('Payments temporarily disabled');
return stripe.createCharge(order);
}Rollback Strategies & Monitoring
# Kubernetes rollback
kubectl rollout status deployment/myapp # check rollout health
kubectl rollout history deployment/myapp # view revision history
kubectl rollout undo deployment/myapp # rollback to previous revision
kubectl rollout undo deployment/myapp --to-revision=3 # rollback to specific revision
# Docker Swarm rollback
docker service update --rollback myapp
# Vercel — instant rollback to any previous deployment
vercel rollback [deployment-url]
# Database migrations — always write reversible migrations
# Forward: ALTER TABLE users ADD COLUMN feature_flags jsonb DEFAULT '{}';
# Backward: ALTER TABLE users DROP COLUMN feature_flags;
# Zero-downtime DB schema changes (expand-contract pattern):
# Step 1 — Expand: add new column (nullable, no constraint yet)
# Step 2 — Migrate: backfill data in batches
# Step 3 — Contract: add NOT NULL constraint, drop old column
# Health check endpoint — used by load balancers to detect failures
# GET /health → 200 { status: "ok", db: "ok", cache: "ok" }
# GET /ready → 200 when ready to serve traffic (startup probe)Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free