23.11. Rollbacks
Table of Contents
Why Rollbacks Matter
Even with careful testing, some deployments will break things. A rollback is how you safely go back to a previous, known good state.
Typical reasons you need a rollback:
- New version crashes or fails to start
- Critical bug appears in production
- Performance suddenly degrades
- Security issue in new release
- Deployment partially succeeds and leaves system in a weird state
A good deployment strategy is incomplete without a clear, practiced rollback plan. If you cannot revert quickly, you do not really control your production system.
In this chapter you will see practical rollback patterns that fit modern Docker and CI/CD based backends.
Rollback Principles
Separate “deploying” from “releasing”
A useful mental model is:
- Deploy: Put new code or containers on servers.
- Release: Start sending real traffic to that new code.
If you separate these two, rollbacks are easier:
- Deployment failed: nothing changes for users.
- Release caused problems: you switch traffic back to the old version.
This is why strategies like blue green and canary are so powerful.
Make rollbacks fast and predictable
A rollback should be:
- Rehearsed: you have done it in staging or test environments.
- Documented: step by step, with exact commands or scripts.
- Automated: ideally a single command or pipeline job.
Examples of “bad” rollback situations:
- “We do not know what the previous version was.”
- “The rollback instructions are in someone’s head.”
- “We can rollback the app, but the database schema is now incompatible.”
Treat rollback as a first-class use case
When you design deployment scripts, Docker images, and CI/CD pipelines, always ask:
- “How do we roll this back if it goes wrong at step N?”
- “Is this change backwards compatible?”
For every new deployment feature, add a rollback step to your mental checklist.
Versioning and Immutable Artifacts
Why versions matter for rollback
You can only rollback to what you can uniquely identify and reproduce. That means you must have clear versioning of:
- Docker images
- Application code
- Database migrations
- Configuration
Good versioning allows you to say: “Production is currently running app:1.4.2. We want to rollback to app:1.4.1.”
Common versioning strategies
| Item | Example version | Notes |
|---|---|---|
| Docker image tag | myapp:1.4.2, myapp:2024-08-18 | Avoid using only latest in production. |
| Git commit | a1b2c3d | CI can attach commit SHA to image tag. |
| Release name | v1.4.2 | Semantic versioning is popular. |
| Migration batch | 202408181230_add_orders_table | Timestamp-based migration ids. |
Rule: Never deploy something you cannot identify and reproduce.
Each production deployment must be tied to a specific, immutable artifact (image + commit + migrations).
Immutable artifacts
An immutable artifact is built once and never changed. For Docker-based apps:
- Build image in CI:
myapp:1.4.2 - Push it to a registry
- Use the same image for:
- Staging deployment
- Manual tests
- Production deployment
- If you need a fix, you build
1.4.3, not change1.4.2.
This gives you:
- Consistency across environments
- Guaranteed rollback target
- Clear audit trail of what ran in production
Example Docker tags in a registry:
| Tag | Use case |
|---|---|
myapp:1.4.2 | Immutable release version |
myapp:1.4.2-a1b2c3d | Version + commit |
myapp:staging | Moving tag pointing at current staging |
myapp:prod | Moving tag pointing at current prod |
Rollback then becomes:
- Point
prodfrom1.4.2back to1.4.1and redeploy - Or restart containers with
1.4.1image
Deployment Strategies and Rollbacks
Your deployment strategy strongly affects how easy a rollback is.
In-place deployments
In-place means you update the same servers or containers in place:
- Stop service
- Replace code or container image
- Start service again
This is simple but has weak rollback support:
- If something fails, you must deploy the previous version again.
- Users may experience more downtime while you fix it.
Use in-place with great care for:
- Very small systems
- Internal tools where downtime is acceptable
If you have to use in-place, keep a “previous version” deployment script ready:
# Bad: only deploys "latest"
docker pull myapp:latest
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp myapp:latestBetter:
# Good: explicit versions
# rollout
docker pull myapp:1.4.2
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp myapp:1.4.2
# rollback script (prepared ahead of time)
docker pull myapp:1.4.1
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp myapp:1.4.1Blue green deployment
With blue green, you have two identical environments:
- Blue: current production
- Green: new version candidate
Process:
- Deploy new version to green.
- Run tests and checks on green.
- Switch traffic from blue to green, usually by:
- Changing load balancer config
- Updating a reverse proxy (Nginx, Traefik)
- Keep blue running as backup for some time.
Rollback is easy:
- If green has problems after the switch, route traffic back to blue.
Example concept with Nginx upstreams:
upstream app_backend {
server blue.example.internal; # old version
# server green.example.internal; # new version
}To switch:
upstream app_backend {
# server blue.example.internal; # old version
server green.example.internal; # new version
}Rolling back is just switching back the commented line and reloading Nginx.
Advantages:
- Very fast rollback
- Minimal downtime
- Easy comparison between old and new
Trade-offs:
- Requires extra infrastructure (two environments)
- Must keep data compatible between both environments
Canary releases
Canary deployment sends a small percentage of traffic to the new version first.
Example steps:
- Deploy new version to a subset of servers or a separate service.
- Send 1% of traffic to new version, 99% to old.
- Monitor errors and performance.
- If things look good, increase to 10%, then 50%, then 100%.
Rollback strategy:
- Reduce traffic to 0% on the new version and 100% on the old one.
This is very useful when:
- You are unsure about the impact of changes.
- You want real production data but lower risk.
Implementation often uses:
- A smart load balancer or API gateway
- Feature flag systems
You do not need all that complexity on day one, but remember the concept.
Rolling updates
Rolling update replaces instances one by one:
- You have N instances of your app.
- Deployment system stops instance 1, updates it, brings it back.
- Repeats for instances 2, 3, ..., N.
This is common in systems like Kubernetes.
Rolling rollback typically:
- Stops further updates
- Starts replacing new instances with the old version
If you use a rolling strategy, make sure your platform supports:
- Direct rollback to a previous deployment
- Or clean redeployment of the previous version
Database Changes and Rollbacks
Application rollbacks are usually easy compared to database rollbacks.
The big risk: incompatible schemas
Example scenario:
- Old version expects column
users.username. - New version:
- Renames
usernametouser_name. - Deploys new code that uses
user_name. - Migration drops
usernamecolumn.
If you deploy new code and migration together, then realize there is a bug:
- You roll back app version to the old one.
- Old code still expects
username. - But the column is gone.
You now have an application and database mismatch.
Rule: Design migrations to be backward compatible when possible.
The old application version must still work with the new schema during and after deployment.
Safe migration patterns
General safe strategy:
- Add, do not remove:
- Add new columns or tables.
- Keep old columns for now.
- Deploy code that uses the new structure:
- Or code that supports both old and new.
- Migrate data:
- Backfill new columns from old ones.
- Wait:
- Make sure the new version is stable.
- Remove old structures in a later deployment:
- Only after you are sure you will not roll back to old code.
Example for renaming a column:
- Migration 1:
- Add
user_namecolumn. - Deploy version 1.4.0:
- Write both
usernameanduser_name. - Read
user_nameif present, otherwiseusername. - Run a background job to copy
usernametouser_namefor all users. - After some time and a few releases:
- Deploy version 1.5.0 that only reads
user_name. - Migration 2:
- Drop
usernamecolumn.
Now if you rollback from 1.5.0 to 1.4.0:
- 1.4.0 still works, because schema is compatible.
- There is no dropped column that code depends on.
Migration tools and rollback
Most backends use a migration tool, for example:
- Alembic (Python / SQLAlchemy)
- Django migrations
- Flyway, Liquibase, etc.
These tools track:
- Current migration version
- Which migrations ran
You can usually:
- Move database forward:
upgrade head - Move database backwards:
downgrade <previous_version>
But downgrades are not always safe, especially if:
- Migrations drop tables or columns
- Data has changed in ways that are not reversible
Practical advice:
- Write migrations so that downgrade is possible, but:
- Prefer forward-only fixes over database downgrades in production.
Forward-only fix example:
- Broken migration or release in production
- Instead of downgrading database, you:
- Write a new migration that fixes the schema or data
- Deploy a hotfix release
Rollback Procedures
You need explicit procedures that your team can follow, step by step.
Simple Docker-based rollback
Imagine a single-node Docker deployment with Nginx as reverse proxy. Production currently runs myapp:1.4.1.
Deployment to 1.4.2:
# Pull new image
docker pull myapp:1.4.2
# Stop old container
docker stop myapp || true
docker rm myapp || true
# Run new version
docker run -d \
--name myapp \
--network my_network \
-e APP_ENV=production \
myapp:1.4.2Rollback to 1.4.1:
docker pull myapp:1.4.1
docker stop myapp || true
docker rm myapp || true
docker run -d \
--name myapp \
--network my_network \
-e APP_ENV=production \
myapp:1.4.1To make this safer:
- Script both deploy and rollback
- Log each action
- Make sure environment variables and volumes are the same for both
Docker Compose rollback
With docker-compose.yml, you might manage versions via image tags.
Example docker-compose.yml snippet:
services:
app:
image: myapp:1.4.2 # current
environment:
- APP_ENV=production
depends_on:
- db
To rollback to 1.4.1:
- Edit the file:
services:
app:
image: myapp:1.4.1 # rollback target- Run:
docker compose pull app
docker compose up -d appSome teams keep:
- A
docker-compose.prod.ymlfor the current version - A git history that clearly shows previous versions
The rollback procedure is then:
git revertto the commit with previous workingdocker-compose.prod.ymldocker compose up -d
Kubernetes style rollback (conceptual)
If you deploy to Kubernetes, you typically use a Deployment resource. Kubernetes automatically tracks rollout history.
Deployment excerpt:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: myapp:1.4.2Upgrade and rollback:
# Apply new deployment (1.4.2)
kubectl apply -f deployment.yaml
# If something goes wrong:
kubectl rollout undo deployment/myapp
Kubernetes tracks the last few ReplicaSet versions. rollout undo switches back to the previous one.
Even if you do not use Kubernetes yet, it is useful to understand this pattern, because many modern systems work similarly.
CI/CD pipeline rollbacks
Your CI/CD system can provide “promote” and “rollback” jobs.
Example conceptual pipeline steps:
| Step | Description |
|---|---|
build | Build Docker image, run tests. |
deploy_staging | Deploy to staging. |
deploy_production | Deploy image myapp:1.4.2 to prod. |
rollback_production | Deploy previous tag myapp:1.4.1 to prod. |
Rollback job might:
- Read “previous production version” from a file or environment
- Or let you choose from recent versions
Key ideas:
- Treat rollback as a first-class pipeline job
- Do not rely only on manual “ssh and fix things” steps
Testing and Practicing Rollbacks
A rollback strategy only works if it is:
- Correct
- Documented
- Practiced
Test rollbacks in non-production environments
You can regularly practice:
- Deploy version A to staging.
- Deploy version B to staging.
- Intentionally rollback to version A in staging.
- Verify:
- App runs correctly.
- Database is consistent.
- Logs and monitoring look normal.
This reveals:
- Migration issues
- Incomplete automation
- Missing permissions
- Misbehaving background workers
Run through failure scenarios
Design some simple “what if” scenarios and verify that your rollback procedure works:
| Scenario | Check |
|---|---|
| New version fails to start | Can you quickly restart old version? |
| Increased error rate after 5 minutes | Can you switch traffic back fast? |
| Background worker starts corrupting data | Can you stop workers and rollback safely? |
| Bad migration drops an important column | Do you have backups and restore procedures? |
Document clearly:
- How to detect the problem (metrics, alerts, logs)
- Exact commands to rollback
- How to verify success after rollback
Post-rollback actions
After you rollback a broken release:
- Freeze new deployments temporarily.
- Collect:
- Logs
- Metrics
- Error reports
- Create an incident report:
- What went wrong?
- How did we detect it?
- What made rollback hard or easy?
- Improve:
- Tests
- Monitoring
- Deployment / rollback scripts
- Migration patterns
The goal is that each rollback makes your system safer for the next deployment.
Summary
Key ideas from this chapter:
- Rollbacks must be planned, not improvised.
- Use versioned, immutable artifacts so you know exactly what to roll back to.
- Choose deployment strategies that make rollback easy:
- Blue green
- Canary
- Rolling updates
- Treat database migrations as a special risk:
- Prefer backward compatible schema changes.
- Avoid destructive migrations in the same step as new code.
- Create explicit rollback procedures:
- Scripts for Docker or Docker Compose
- Platform-specific rollback commands (like Kubernetes rollouts)
- CI/CD jobs for quick rollback.
- Regularly practice rollbacks in staging so they work when production is on fire.
With a solid rollback strategy, you can deploy more often and with more confidence, because if something goes wrong you know exactly how to go back.
Views: 7
KAHIBARO