Deploying Without Downtime
Zero-downtime deployment is mostly about two things: having more than one of everything, and making database changes backward compatible.
Deploying without downtime is not a single technique. It is a small set of requirements that, once met, make several deployment strategies possible.
The prerequisites
- More than one instance, so one can be replaced while the other serves.
- A load balancer that can remove and return instances.
- Meaningful health checks, so the balancer knows when a new instance is genuinely ready.
- Connection draining, so in-flight requests finish before an instance is removed.
- Stateless application servers, so any instance can serve any request.
- Backward-compatible database changes, so both versions can run simultaneously.
The last one is where most zero-downtime attempts actually fail. Everything else is configuration; that one is discipline.
Rolling deployment, step by step
- Remove instance A from the load balancer.
- Wait for connection draining — typically 30 to 60 seconds.
- Deploy the new version to A.
- Wait for health checks to pass.
- Return A to the load balancer.
- Watch error rates for a minute.
- Repeat for instance B.
During this, both versions are serving traffic. That is fine for stateless code and requires care for anything sharing state — which is why the database rule exists.
Database migrations that do not break
The expand and contract pattern, spread over separate deployments:
- Expand. Add the new column as nullable, or add the new table. The old code ignores it.
- Deploy code that writes to both old and new locations, and reads from the old.
- Backfill existing data into the new location.
- Deploy code that reads from the new location and still writes to both.
- Deploy code that only uses the new location.
- Contract. Drop the old column, once nothing references it.
That is six steps for what feels like renaming a column. It is also the difference between a routine deploy and an outage, and each step is individually safe and reversible.
Never combine a destructive migration with a code deploy. Rolling back the code will not roll back the dropped column.
Health checks that mean ready
- Distinguish liveness — the process is alive — from readiness, which is whether it can serve traffic.
- Readiness should verify dependencies: database connection, cache, any required service.
- Return unhealthy during startup until initialisation completes, including cache warming.
- On shutdown, fail readiness first, then finish in-flight work, then exit.
- Handle SIGTERM properly. A process that ignores it is killed mid-request.
Graceful shutdown handling is a small amount of code and it is what makes draining actually work.
Blue-green when rollback speed matters
Run two complete environments. Deploy to the idle one, verify it thoroughly, then switch traffic at the load balancer or DNS. Rollback is switching back, which takes seconds.
The cost is double the resources during the transition, and the shared database still requires backward-compatible migrations — the two environments talk to the same data.
Feature flags: deploy and release separately
The most useful technique on this list. Ship code with the new behaviour disabled, then enable it independently of deployment.
- Deployments become boring, because they change no behaviour.
- Rollback is a flag toggle, not a redeploy.
- Gradual rollout to a percentage of users becomes trivial.
- Testing in production with internal users becomes safe.
The discipline required is removing flags once a feature is fully released. Accumulated stale flags become their own maintenance problem.
Common questions
Can I deploy without downtime on one server?
Not truly, but you can get close — run two copies of the application on different ports behind a local reverse proxy and switch between them. Real redundancy still needs two machines.
How long should draining be?
Slightly longer than your slowest normal request. Thirty to sixty seconds covers most web applications; longer for file uploads or long-polling connections.
What about long-running background jobs?
Make them idempotent and resumable, checkpoint progress, and let workers finish the current job before exiting. Do not rely on a job completing within the drain window.
Do managed platforms handle this?
Cloud Run, App Service, App Platform and similar do rolling deployment with health checks automatically. You still own the database migration discipline.
Where to go next
See CI/CD pipelines and load balancing basics.