Continuous Delivery: Blue-Green, Canary, and Rolling Deployments
Zero-downtime deployments minimize risk and enable rapid delivery.
Blue-Green Deployment
Maintain two identical production environments; switch traffic between them.
# Kubernetes blue-green with service selector
# Blue deployment (currently active)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-blue
spec:
replicas: 3
selector:
matchLabels:
app: api
version: blue
template:
metadata:
labels:
app: api
version: blue
spec:
containers:
- name: api
image: myapp:1.0.0
# Green deployment (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-green
spec:
replicas: 3
selector:
matchLabels:
app: api
version: green
template:
metadata:
labels:
app: api
version: green
spec:
containers:
- name: api
image: myapp:2.0.0
---
# Service - switch by updating selector
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
selector:
app: api
version: blue # change to 'green' to switch
ports:
- port: 80
targetPort: 3000
# Switch traffic to green
kubectl patch service api-service -p '{"spec":{"selector":{"version":"green"}}}'
# Roll back instantly if issues
kubectl patch service api-service -p '{"spec":{"selector":{"version":"blue"}}}'
Canary Deployment
Gradually shift traffic to new version.
# Argo Rollouts canary
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api-rollout
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to new version
- pause: { duration: 10m }
- setWeight: 30
- pause: { duration: 10m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
analysis:
templates:
- templateName: success-rate
startingStep: 1
Rolling Update (Kubernetes Default)
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # max pods unavailable during update
maxSurge: 1 # max extra pods during update
Database Migrations for Zero-Downtime
Expand-Contract pattern:
1. Expand: Add new column (nullable or with default)
2. Deploy new app version that writes to both old and new
3. Backfill: Migrate existing data
4. Contract: Remove old column
5. Deploy cleaned-up app version
-- Step 1: Add new column (backward compatible)
ALTER TABLE users ADD COLUMN email_normalized VARCHAR(255);
-- Step 3: Backfill
UPDATE users SET email_normalized = LOWER(email);
ALTER TABLE users ALTER COLUMN email_normalized SET NOT NULL;
-- Step 5: Remove old column after app no longer uses it
ALTER TABLE users DROP COLUMN email;
Health Checks for Deployments
// Readiness probe - is the app ready to receive traffic?
app.get('/ready', async (req, res) => {
try {
await db.query('SELECT 1');
await redis.ping();
res.status(200).json({ status: 'ready' });
} catch (error) {
res.status(503).json({ status: 'not ready', error: error.message });
}
});
// Liveness probe - is the app alive?
app.get('/live', (req, res) => {
res.status(200).json({ status: 'alive' });
});
Proper deployment strategies let you ship confidently—knowing you can roll back instantly.