A deployment that requires a maintenance window is a deployment that happens rarely, at night, under pressure, with several changes bundled together. That combination is what makes releases risky, and the risk is a symptom of the process rather than of the code.
What zero downtime actually requires
The deployment mechanics are the easy part. The hard requirement is that the old version and the new version must be able to run at the same time, because during any rollout both are live. Everything else follows from that.
Database migrations are the real constraint
This is where most zero-downtime attempts fail. A migration that drops a column breaks every old instance still serving traffic. A migration that renames one breaks both directions.
The pattern that works is expand and contract, run as separate deployments:
- Expand. Add the new column, nullable. Deploy code that writes to both the old and the new, and reads from the old.
- Migrate. Backfill existing rows in batches, not in one statement that locks the table.
- Switch. Deploy code that reads from the new column. The old one is still there and still written to.
- Contract. Once nothing reads the old column, a later deployment stops writing it and drops it.
That is four deployments where a single ALTER seemed to do. It is also the difference between a rename that nobody notices and one that returns errors for ninety seconds.
Watch for locks separately. Adding an index without CONCURRENTLY on Postgres takes a lock that blocks writes for the duration, which on a large table is an outage regardless of how good your deployment pipeline is.
Health checks that mean something
A load balancer that removes an instance only after it stops responding will send traffic into a dying process for as long as the check interval. A readiness check that reports whether the application can actually serve, including its dependencies, lets the balancer stop sending traffic before the instance goes away.
The corresponding half is graceful shutdown: on the stop signal, stop accepting new requests, finish the ones in flight, then exit. A process that exits immediately drops whatever it was holding.
Rolling, blue-green and canary
- Rolling replaces instances a few at a time. Simplest, and both versions are live throughout, so the compatibility rule above is mandatory.
- Blue-green runs a second full environment and switches traffic at once. Rollback is instant, which is its great advantage, and you pay for double capacity during the switch.
- Canary sends a small share of traffic to the new version and watches error rates before continuing. Most valuable when you have the monitoring to make the decision automatic.
Most teams should start with rolling deployments and add canary once their metrics are good enough to act on.
What belongs in the pipeline
A GitHub Actions workflow that builds an artifact once, then promotes that same artifact through environments, is more reliable than one that rebuilds per environment. If staging and production build separately, they are not the same thing, and the difference will find you eventually.
Secrets come from the environment, never from the repository. Rollback should be a button rather than a procedure, because the moment you need it is the moment nobody wants to be reading a runbook.
The measure of success
It is not that deployments never fail. It is that a failed deployment is uneventful: caught by a health check, rolled back automatically, and small enough that finding the cause takes minutes. That is what makes it safe to deploy on a Tuesday afternoon, which is the actual goal.
If deploys are currently an event at your organisation, that is the kind of work our cloud and DevOps engagements address.