← Blog

What makes a deploy actually zero-downtime

Railyard

March 18, 2026

“Zero-downtime deploy” gets used loosely enough that it’s worth being precise about what it actually requires mechanically, not just what it promises. It’s not one feature — it’s five steps that all have to hold, in order, every time.

The five steps

  1. Build, or pull. With a repository, the new image builds on a dedicated build server if the team has one, otherwise on the app’s own server or a temporary one Railyard starts for the job. An image:// source skips building entirely and just pulls.
  2. Release command. If the app has one, it runs once against the new image, before anything takes traffic — this is where migrations belong. If it fails, the deploy stops right here and the app keeps running its old version. Nothing about the live app changed yet.
  3. Health check. The new container starts next to the one already running, not in place of it. If the app has a health check path configured, the new container has to actually answer it within the configured timeout before anything else happens.
  4. Traffic switch. Only once the new container passes its health check does traffic move to it — all at once for a standard deploy, or a share at a time for a canary deploy, watched against the rest of the app’s error rate before it ramps further.
  5. Old version stops. The previous container keeps running until the new one is confirmed, and only then gets removed.

That ordering is the entire mechanism. There is never a point between steps where the app has zero working containers — the old one doesn’t go away until the new one has already proven itself.

What happens when something fails

This is the part that actually matters more than the happy path: if the build, the release command, or the health check fails, Railyard leaves the previous version running exactly as it was and marks the deploy failed. A bad deploy doesn’t take the app down — it just doesn’t go out. The deploy page names the exact cause and links to the fix — see why a deploy actually fails, reason by reason for what each one means.

Rolling back is the same mechanism, not a different one

A rollback isn’t a special-cased escape hatch — it’s a deploy of an earlier release’s commit or image, which means it goes through the exact same build-or-pull, release command, health check and traffic switch as any other deploy. That’s deliberate: a rollback that skipped the health check would be the one deploy path that could actually cause the downtime the rest of the system is built to avoid.

railyard releases my-app          # list releases to roll back to
railyard rollback my-app <version> [--code-only]

--code-only restores the code but keeps today’s environment variables — useful when a release itself was fine but a variable changed since then and you don’t want to undo that too.

Why the ordering can’t be skipped

Every one of these steps exists because of a specific failure mode it closes off. Skip the health check and a crash-looping new version takes traffic anyway. Skip keeping the old container alive during the check and there’s a gap with nothing answering. Run the release command after the switch instead of before and a pending migration hits live traffic mid-run. None of it is exotic engineering — it’s just not skipping steps under pressure to ship faster.

Full detail, including canary deploys and deploy windows: Deploys and rollbacks.

See a deploy run, step by step.

Every deploy goes through the same build, release, health check and traffic switch — nothing skipped, nothing manual.

Free plan, no card, and 14 days of Pro for every new team. One flat price per team.