A deployment strategy is not just a technical implementation detail. It determines how much risk you take with each release, how quickly you can recover from problems, and what infrastructure you need to support it. Choosing without understanding the trade-offs is choosing accidentally.
Rolling Deployments: The Default
Rolling deployments incrementally replace old instances with new ones. At any point during the deployment, both old and new versions are running simultaneously.
Before: [v1][v1][v1][v1]
Step 1: [v2][v1][v1][v1]
Step 2: [v2][v2][v1][v1]
Step 3: [v2][v2][v2][v1]
After: [v2][v2][v2][v2]
In Kubernetes, this is the default Deployment update strategy:
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # Never take more than 1 pod offline
maxSurge: 1 # Allow 1 extra pod during deployment
Advantages:
- No additional infrastructure — you use your existing pod count
- Gradual rollout naturally validates the new version
- Built-in Kubernetes support requires no additional tooling
Disadvantages:
- Old and new versions run simultaneously — backward-compatible API changes required
- Rollback means rolling forward to the previous version (another rolling update), not instant
- Partial traffic splitting is implicit, not controlled — hard to measure the new version’s error rate in isolation
When to use it: most deployments, most services. Acceptable risk, minimal overhead.
Blue/Green Deployments: Instant Rollback
Blue/green maintains two identical production environments. One is live (blue), one is idle (green). To deploy, you bring up the new version on green and switch traffic.
Blue (active): [v1][v1][v1][v1] ← 100% of traffic
Green (idle): [v2][v2][v2][v2]
Switch:
Blue (idle): [v1][v1][v1][v1]
Green (active): [v2][v2][v2][v2] ← 100% of traffic
Rollback (if needed): switch back in seconds
Implementation with a load balancer:
# Deploy new version to green
kubectl set image deployment/app-green app=myapp:v2
# Wait for green to be healthy
kubectl rollout status deployment/app-green
# Switch traffic (update service selector)
kubectl patch service app-lb -p '{"spec":{"selector":{"version":"green"}}}'
# If something goes wrong, rollback instantly
kubectl patch service app-lb -p '{"spec":{"selector":{"version":"blue"}}}'
Advantages:
- Instant rollback: switching back is as fast as switching forward
- No mixed-version state: the switch is atomic from the user’s perspective
- Clean environment for the new version: not sharing resources with the old version
Disadvantages:
- Double the infrastructure cost (two full environments)
- Database migrations must be backward-compatible: both versions need to work with the same database
- Warm-up time: the green environment needs to be fully operational before the switch
Database migrations with blue/green: this is the hard problem. Blue/green deployments require that your database schema supports both versions simultaneously during the transition period.
The expand/contract pattern handles this:
- Expand: add the new column (nullable, with default) — both v1 and v2 work
- Deploy v2
- Migrate: fill the new column, remove old column dependency
- Contract: remove old column (after v1 is decommissioned)
When to use it: services where instant rollback is critical, services that can tolerate the infrastructure cost, services with backward-compatible database changes.
Canary Deployments: Risk-Controlled Rollout
Canary deployments route a small percentage of traffic to the new version while the majority continues to the stable version.
v1 (stable): [v1][v1][v1][v1] ← 95% of traffic
v2 (canary): [v2] ← 5% of traffic
After validation:
v1 → v2 (full rollout)
In Kubernetes with an ingress controller or service mesh:
# Nginx ingress with canary annotation
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "5" # 5% to canary
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
backend:
service:
name: app-v2
port:
number: 8080
Progressive canary: automate the rollout based on metrics.
def progressive_canary(deployment, target_percentage=100, step=10):
current = 5 # Start at 5%
while current <= target_percentage:
set_canary_weight(deployment, current)
wait(10 * 60) # 10 minutes
metrics = get_metrics(deployment, window="10m")
if metrics.error_rate > 0.01: # >1% error rate
rollback(deployment)
alert("Canary failed at {}%: error rate {}".format(
current, metrics.error_rate))
return
if metrics.p99_latency > 500: # >500ms p99
rollback(deployment)
alert("Canary failed: latency degraded")
return
current += step
promote_to_stable(deployment)
Advantages:
- Limits blast radius: only the canary percentage of users are exposed to a bad release
- Provides real traffic validation: the canary handles real requests, not synthetic tests
- Automated rollback: metrics-based rollout can automatically abort on quality degradation
Disadvantages:
- Observability requirements: you need to measure canary vs stable metrics separately to detect problems
- Longer deployment time: waiting at each traffic percentage step
- Session consistency: users may hit v1 for some requests and v2 for others if not using sticky sessions
- More complex than rolling or blue/green
When to use it: user-facing services where a bad release affects real users, services with measurable quality metrics, teams with mature observability.
Database Migrations and All Three Strategies
The hardest constraint on deployment strategies is the database. Any strategy where old and new code versions run simultaneously (all three) requires that:
Migrations are backward-compatible: the old version must be able to run against the new schema.
The sequence for breaking changes:
- Deploy v2 with old schema support still present
- Run additive migration (add new column/table, don’t remove old)
- Migrate data to new structure (background job)
- Deploy v2-final that uses new structure
- Run removal migration (drop old column/table) only after v1 is fully decommissioned
For column renames:
-- Step 1: Add new column (backward compatible — v1 ignores it, v2 uses both)
ALTER TABLE orders ADD COLUMN customer_reference VARCHAR(255);
-- Step 2: Backfill (background job)
UPDATE orders SET customer_reference = legacy_ref WHERE customer_reference IS NULL;
-- Step 3: After v1 is decommissioned
ALTER TABLE orders DROP COLUMN legacy_ref;
Feature Flags: The Software Switch
Deployment strategies handle infrastructure-level risk. Feature flags handle code-level risk separately.
With feature flags, you can deploy new code to all instances but control who sees the new behavior:
if (featureFlags.isEnabled("new-checkout-flow", user)) {
return newCheckoutService.process(request);
} else {
return legacyCheckoutService.process(request);
}
Feature flags allow:
- Canary behavior without canary infrastructure (by enabling for X% of users in code)
- Instant disable of a feature without deployment
- A/B testing at the application level
- Dark launches (deploy the feature, enable for internal users first)
Combine deployment strategies with feature flags for fine-grained risk control.
Observability Requirements
All deployment strategies require observability to be effective. You need to:
- Distinguish v1 and v2 traffic in your metrics and logs
- Measure error rates and latency per version during the deployment window
- Detect anomalies within minutes of the switch/promotion
- Know when to rollback based on data, not instinct
Without this, “it deployed successfully” means “the process completed without error” — not “users are being served correctly by the new version.”
Choosing
| Rolling | Blue/Green | Canary | |
|---|---|---|---|
| Infrastructure cost | Low | 2× | Low |
| Rollback speed | Slow | Instant | Fast |
| Observability required | Moderate | Low | High |
| Database complexity | Moderate | High | Moderate |
| Deployment complexity | Low | Medium | High |
| Risk reduction | Moderate | Moderate | High |
Start with rolling deployments. They’re good enough for most services. Add blue/green when instant rollback is critical for a specific service. Add canary when you need progressive validation on high-traffic, user-facing services.