Guide
Zero-downtime deployments: what actually matters
“Zero-downtime deployment” sounds like an infrastructure problem — rolling updates, load balancer health checks, blue/green environments. Those parts matter, but they’re also the easy part. Most platforms that claim zero-downtime deploys and still cause user-visible issues are being let down by something else: the database migration.
The infrastructure layer (the easy part)
At a basic level, this just requires new instances to be healthy before old ones are removed from rotation, and in-flight requests to be allowed to finish rather than being dropped mid-response. Most modern hosting platforms and container orchestrators handle this reasonably well out of the box. It’s necessary, but it’s rarely where things actually break.
The database layer (the part that actually matters)
Deploying new application code and changing the database schema are two different operations happening at two different times, and for a window in between, old code and new code may both be running against the same database. That window is where zero-downtime deployments quietly fail.
A few habits make this safe:
Additive migrations first. Add new columns as nullable, or with a default, before any code depends on them. Never ship a migration that removes or renames something the currently-running code still expects.
Backward-compatible code, deployed before the migration that assumes it. New code should be able to run against the old schema for a short period. Old code should be able to run against the new schema for a short period too. This “expand, migrate, contract” pattern is the actual core of a zero-downtime release — the load balancer configuration is almost incidental by comparison.
Long-running migrations run separately from deploys. A schema change that locks a large table for more than a moment shouldn’t be bundled into a routine deploy. Run it out of band, during a quieter period, with a plan for what happens if it needs to be aborted partway through.
What this looks like in practice
None of this requires exotic tooling. It requires treating each schema change as two smaller, ordered changes instead of one — and resisting the urge to combine “ship the feature” and “change the schema” into a single deploy just because it’s convenient to write that way.
The teams that reliably ship without user-visible incidents aren’t the ones with the most sophisticated deployment pipeline. They’re the ones with the most disciplined migration habits.