Rollback Strategies for Containerized React Apps
A rollback is a controlled reversal of a deployment when a new version causes issues. Containers make rollbacks fast and reliable: instead of manually reverting code and rebuilding, you restart the old image version (30–60 seconds). In 2026, 91% of DevOps teams practice regular rollbacks, and teams with sub-5-minute rollback procedures have 40% fewer post-deploy incidents (State of DevOps Report, 2026). This article covers three production strategies: blue-green (instant switchover), canary (gradual rollout), and feature flags (software kill switches). Combined, they let you deploy hourly with confidence.
I've seen teams deploy with no rollback plan, resulting in 6-hour outages. Proper rollback infrastructure lets you ship 100 times per day and sleep at night.
The anatomy of a bad deployment
A React app deploys at 3 PM with a bug in the payment form. By 3:05 PM, users report errors. By 3:15 PM, revenue is down 50%. By 3:30 PM, the team has reverted the database migration, restarted the app, and things are partially working. By 4:30 PM, the site is fully recovered. Total impact: 1.5 hours, revenue loss: $50K+.
With a proper rollback:
- 3:00 PM: Deploy new version (v2.1).
- 3:05 PM: Monitoring detects error rate spike.
- 3:06 PM: Automatic rollback to v2.0 triggered.
- 3:07 PM: Site fully functional again.
- Total impact: 7 minutes, revenue loss: ~$5K.
The difference is automation and rehearsal.
Strategy 1: Blue-Green Deployment
Blue-green is the gold standard: two identical production environments (blue and green). At any time, one is live; the other is idle. Deploy to the idle environment, run smoke tests, then switch traffic to it. If issues appear, switch back instantly.
Blue-green with Kubernetes
Kubernetes services make blue-green trivial:
# deployment-green.yaml (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: react-app-green
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: react-app
version: green
template:
metadata:
labels:
app: react-app
version: green
spec:
containers:
- name: react-app
image: ghcr.io/myorg/react-app:2.1.0 # NEW VERSION
ports:
- containerPort: 3000
---
# deployment-blue.yaml (old version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: react-app-blue
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: react-app
version: blue
template:
metadata:
labels:
app: react-app
version: blue
spec:
containers:
- name: react-app
image: ghcr.io/myorg/react-app:2.0.0 # OLD VERSION (currently live)
ports:
- containerPort: 3000
---
# service.yaml (shared by both deployments)
apiVersion: v1
kind: Service
metadata:
name: react-app-service
namespace: production
spec:
selector:
app: react-app
version: blue # CURRENTLY POINTS TO BLUE
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
Workflow:
# 1. Deploy green (v2.1) alongside blue (v2.0, currently live)
kubectl apply -f deployment-green.yaml
# 2. Wait for green to be ready (3 pods running, healthy)
kubectl rollout status deployment/react-app-green
# 3. Run smoke tests against green
curl -I http://react-app-green-service:3000/
# If tests pass:
# 4. Switch traffic from blue to green
kubectl patch service react-app-service \
-p '{"spec":{"selector":{"version":"green"}}}'
# Traffic now flows to green. Monitor for 10 minutes.
# 5. If issues appear, rollback instantly
kubectl patch service react-app-service \
-p '{"spec":{"selector":{"version":"blue"}}}'
# Traffic returns to blue. Green can be debugged or deleted.
Advantages:
- Instant rollback (sub-second traffic switch).
- Test new version in production without traffic (zero risk).
- Easy to debug issues (keep old version running).
Disadvantages:
- Doubles infrastructure cost (two full deployments running).
- Database schema changes risk (old version can't handle new schema; see mitigation below).
- State sharing (if blue and green share a database, synchronization is critical).
Handling database changes in blue-green
If v2.1 adds a new column to the users table, v2.0 (blue) doesn't know about it. When green tries to write to the new column, blue's queries might fail. Solutions:
Option 1: Backward-compatible migrations
- Migration 1: Add new column with default value.
- Deploy v2.1 (uses new column).
- Wait 1 week, confirm v2.0 no longer in use.
- Migration 2: Remove default, make column required.
Option 2: Feature flags
- New code in v2.1 is behind a feature flag (
if (FEATURE_NEW_COLUMN) { ... }). - Flag disabled at deploy time; both blue and green use v2.0 code path.
- Gradually enable flag (5% → 25% → 100%) after confirming green is stable.
Strategy 2: Canary Deployment
Canary gradually shifts traffic to a new version: 5% to v2.1, 95% to v2.0. If error rates are normal, gradually increase: 25%, 50%, 100%. If errors spike, rollback and stop at current ratio.
Canary with Kubernetes
Use Flagger (a Kubernetes operator) to automate canary:
# flagger-config.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: react-app
namespace: production
spec:
# Target deployment to canary
targetRef:
apiVersion: apps/v1
kind: Deployment
name: react-app
progressDeadlineSeconds: 300
service:
name: react-app-service
port: 3000
analysis:
interval: 1m
threshold: 5
# Metrics to monitor
metrics:
- name: error-rate
thresholdRange:
max: 1 # Fail if error rate exceeds 1%
interval: 1m
- name: latency
thresholdRange:
max: 500 # Fail if p99 latency exceeds 500ms
interval: 1m
skipAnalysis: false
# Rollout strategy
skipAnalysis: false
stages:
- setWeight: 5 # 5% traffic
duration: 2m
- setWeight: 25 # 25% traffic
duration: 2m
- setWeight: 50 # 50% traffic
duration: 5m
- setWeight: 100 # 100% traffic (fully rolled out)
When you deploy a new version:
# Update the deployment image
kubectl set image deployment/react-app \
react-app=ghcr.io/myorg/react-app:2.1.0
# Flagger watches and automatically shifts traffic:
# T+0m: 5% to v2.1
# T+2m: 25% to v2.1
# T+4m: 50% to v2.1
# T+9m: 100% to v2.1 (fully rolled out)
# Monitor:
kubectl describe canary react-app -n production
# Status: Progressing / Succeeded / Failed
Advantages:
- Gradual rollout catches issues early (only 5% affected initially).
- Automatic rollback if metrics degrade (error rate, latency).
- Single infrastructure (no blue-green duplication).
Disadvantages:
- Slower rollout (9 minutes vs instant blue-green).
- Requires robust metrics (if monitoring is wrong, bad code goes live).
- More complex to implement.
Strategy 3: Feature Flags (Software Kill Switches)
A feature flag is a runtime boolean that gates new code. If a deployment breaks, disable the flag to instantly revert behavior without rolling back.
// src/features/payments.js
const FEATURE_NEW_PAYMENT_FORM = flags.isEnabled('new-payment-form');
export function PaymentForm() {
if (FEATURE_NEW_PAYMENT_FORM) {
return <NewPaymentForm />; // v2.1 code
} else {
return <OldPaymentForm />; // v2.0 code
}
}
Flags are stored in a service (LaunchDarkly, Unleash, custom API):
// Fetch flags from server on app startup
const flags = await fetch('/api/features').then(r => r.json());
// flags = {
// 'new-payment-form': true,
// 'dark-mode': false,
// 'experimental-ai': false
// }
When an issue appears, flip the flag without redeploying:
curl -X POST https://flags.example.com/api/flags/new-payment-form/toggle \
-d '{"enabled": false}'
# The toggle endpoint notifies all running instances via WebSocket or polling
# All users instantly see the old payment form
Advantages:
- Instant rollback (no redeployment).
- Gradual rollout by percentage (5% → 100%).
- A/B testing built-in (50% new, 50% old, measure conversion).
Disadvantages:
- Requires flag management infrastructure.
- Old code path must remain (dead code accumulates).
- Flag-setting mistakes can break production instantly.
Automation: self-healing rollbacks
Combine monitoring + rollback for hands-free recovery:
# kubernetes: rollout config with auto-rollback
apiVersion: apps/v1
kind: Deployment
metadata:
name: react-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
containers:
- name: react-app
image: ghcr.io/myorg/react-app:2.1.0
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
failureThreshold: 3
periodSeconds: 10
Kubernetes automatically:
- Starts a new pod with v2.1.
- Waits 30 seconds for it to become healthy.
- If liveness probe fails 3 times, marks the pod unhealthy.
- Restarts the pod or, if repeated failures, keeps the old pod.
Combined with Prometheus + AlertManager:
# alerting-rule.yaml
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.01
for: 2m
annotations:
summary: "Error rate exceeded 1%"
---
# alertmanager webhook to trigger rollback
- match:
alertname: HighErrorRate
receiver: auto-rollback
webhook_configs:
- url: "http://rollback-service:8080/trigger-rollback"
When error rate spikes above 1% for 2 minutes, AlertManager calls the rollback webhook, which triggers:
kubectl rollout undo deployment/react-app --to-revision=1
Within 5 minutes, the old version is live again. Zero human intervention.
Comparison: blue-green vs canary vs flags
| Aspect | Blue-Green | Canary | Feature Flags |
|---|---|---|---|
| Rollback speed | <1 second | 2–5 minutes | <1 second |
| Infrastructure overhead | 2x | 1x | None |
| Risk to users | Zero (if tests pass) | Minimal (5% at start) | Controlled by flag ratio |
| Complexity | Medium | High | Medium |
| Database schema handling | Risky | Careful | Careful |
| A/B testing capability | No | Yes | Yes |
| Learning from users | No | Yes | Yes |
| Best use case | Critical deployments | Uncertain changes | Frequent deploys |
Practical deployment workflow (combining all three)
#!/bin/bash
# deploy.sh
VERSION=$1 # e.g., 2.1.0
# Step 1: Feature flags - ship new code behind a flag (disabled)
git push origin main
# Step 2: GitHub Actions builds and pushes image
# (waits for image in registry)
# Step 3: Deploy to green (blue-green)
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: react-app-green
spec:
template:
spec:
containers:
- image: ghcr.io/myorg/react-app:$VERSION
EOF
# Step 4: Run smoke tests
kubectl port-forward svc/react-app-green 3000:3000 &
sleep 10
npm run test:smoke
# Step 5: Switch traffic (5% canary, then gradual to 100%)
kubectl patch service react-app-service \
-p '{"spec":{"selector":{"version":"green"}}}'
# Flagger takes over: 5% → 25% → 50% → 100%
# Step 6: Monitor for 15 minutes
sleep 900
# Step 7: If error rate < 1%, promote; otherwise rollback
if kubectl get canary react-app -o jsonpath='{.status.phase}' | grep -q "Succeeded"; then
echo "Deployment succeeded!"
# Clean up blue
kubectl delete deployment react-app-blue
else
echo "Deployment failed, rolling back..."
kubectl rollout undo deployment/react-app
fi
This workflow combines:
- Feature flags for rapid feature toggling.
- Blue-green for safe testing pre-traffic.
- Canary for gradual rollout with monitoring.
- Auto-rollback for hands-free recovery.
Key Takeaways
- Blue-green: two environments, instant switchover, higher cost but safest.
- Canary: gradual traffic shift (5% → 100%), automatic rollback on metrics.
- Feature flags: software kill switches, instant disable without redeployment.
- Combine all three: deploy safely multiple times per day.
- Automate rollbacks: monitor error rates and revert without human intervention.
Frequently Asked Questions
How do I know when to rollback?
Monitor:
- Error rate (spike above baseline).
- Latency (p99 > baseline + 20%).
- Business metrics (conversion rate, revenue, user retention).
- Custom checks (payment form success rate).
Set alerts for these metrics; trigger rollback automatically if any exceed thresholds.
Can I use blue-green without Kubernetes?
Yes, with Docker Swarm or traditional VMs. Run two identical servers, put a load balancer in front, then switch it to the new server. Rollback by switching back. Complexity is higher (manual server management vs Kubernetes automation).
How often should I practice rollbacks?
Monthly. Include rollback drills in your deployment schedule:
- Simulate a bad deployment.
- Trigger a rollback.
- Measure time to recover.
- Document any manual steps.
Teams that practice monthly can rollback in <5 minutes; teams that don't often take 30–60 minutes.
What if a rollback itself fails?
This is rare but possible (old code has a critical bug that new code fixed). Plan for this:
- Keep two old versions available (current and previous).
- If rollback to v2.0 fails, rollback to v1.9.
- Have a "break glass" plan: restart servers with safe defaults, return generic error pages.
Can I rollback part of a deployment (e.g., API but not frontend)?
Yes. Services are independent. Rollback the API deployment while leaving the frontend on the new version. However, this requires careful API versioning (new frontend might not be compatible with old API).