What are the actual techniques for a zero-downtime deployment?
Zero-downtime deploys rest on a precise mechanical promise — no dropped request, no severed connection — delivered through rolling replacement, health gates, connection draining, and schema compatibility.
LEARN BY DOINGPut your intuition to work3 steps
What are the actual techniques for a zero-downtime deployment?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#The promise, precisely stated
"Zero-downtime deployment" does not mean nobody notices you deployed — it means no request is dropped, no connection is severed mid-flight, and no user sees a 5xx or a blank page because the version behind the load balancer changed while they were using it. That is a narrower and more mechanical claim than it sounds, and it is worth being precise about it in an interview, because the actual techniques are aimed squarely at that claim rather than at some vaguer notion of "smooth deploys."
#Rolling deployment: the default shape
The workhorse technique is a rolling deployment: instead of stopping every instance and starting the new version everywhere at once, you replace instances a few at a time while the load balancer keeps routing traffic to whichever instances are currently healthy. The mechanics that make this safe, in order:
- Take one instance (or a small batch) out of the load balancer's active pool. It stops receiving new connections, but existing ones are not killed.
- Drain in-flight connections. The instance finishes requests it already accepted, up to a bounded grace period, instead of being killed mid-request.
- Stop the old process and start the new version.
- Run readiness checks against the new version before it rejoins the pool — a readiness probe, distinct from a liveness probe, exists specifically to answer "is this instance ready to receive traffic," not just "is the process alive."
- Add the instance back to the pool only once readiness checks pass, and repeat for the next batch.
The two failure modes that show up constantly in real incidents both come from skipping a step. Skip draining and you SIGKILL a process with live connections still open, which the client experiences as a reset connection or a truncated response — the deploy technically succeeded and users still saw errors. Skip the readiness gate (or conflate it with a liveness check) and you route real traffic to an instance that is still warming up caches or hasn't finished its startup sequence, which shows up as a spike of errors that appears to come from nowhere right as a deploy starts.
#Blue-green and canary are not competitors to rolling — they're different risk trade-offs
A blue-green deployment keeps two complete, independent environments and switches traffic between them atomically, usually at the load balancer or DNS layer. Its advantage over a rolling deployment is rollback speed: if the new environment misbehaves, you flip traffic back to the old one instantly, with zero instances left in a half-migrated state. Its cost is that you pay for double the infrastructure during the switch, and any database the two environments share still has to tolerate both versions writing to it simultaneously for at least a brief window.
A canary deployment sends a small, deliberately chosen slice of real traffic (often 1-5%) to the new version while the rest keeps hitting the old one, and only ramps up once dashboards confirm the new version's error rate and latency match the baseline. It is the slowest of the three to reach 100%, but it catches problems that only manifest under real production traffic patterns — the exact thing a rolling deployment, which reaches every user relatively quickly, is bad at catching before it's already fleet-wide.
| Technique | Rollback speed | Infra cost during deploy | Catches traffic-pattern bugs early | Typical complexity |
|---|---|---|---|---|
| Rolling | Moderate — revert and roll forward again | Low (one extra instance at a time) | Poor — reaches everyone quickly | Low |
| Blue-green | Instant — flip traffic back | High (full duplicate environment) | Poor — all-or-nothing traffic | Moderate |
| Canary | Fast — cut the canary's traffic to zero | Low-moderate | Good — small real slice first | High (needs metric comparison) |
#The compatibility problem nobody mentions until it bites
All three techniques share a requirement that is easy to miss because it lives in the database, not the deploy tooling: for as long as old and new instances run side by side, both versions of your code have to work correctly against the same schema. If your new code needs a column that doesn't exist yet, or your old code breaks the moment a column it relies on disappears, "zero downtime" at the infrastructure layer is undone by a five-second window of 500s at the data layer.
The standard answer is the expand-contract pattern: add the new column as nullable (or with a default) and deploy code that writes to both the old and new columns — this is the "expand" step, and it's safe because old code, which doesn't know the new column exists, keeps working unmodified. Once every instance is running code that writes both, backfill historical rows. Once backfill is complete and monitoring confirms nothing is still reading the old column, deploy code that reads and writes only the new column. Only after that deploy is fully rolled out do you drop the old column — the "contract" step. Skipping straight from "add column" to "drop old column" in one deploy is exactly what breaks zero-downtime deploys in practice, because during the rollout window some instances are still running the old code that expects the old column to exist.
🧠 Check your understanding
0/2 · 0/2 answered1. What is the specific purpose of a readiness probe, as distinct from a liveness probe, during a rolling deployment?
2. In the expand-contract pattern for schema changes, why is the old column kept (not dropped) immediately after adding the new one?