Migrating production infrastructure? Get up to ₹10,000 in migration credits. Apply now
voroaBlog
Deploys

A deploy is not finished when the container starts

· · 6 min read

A deploy is not finished when the container starts

The easiest deploy status to build is the one that lies. The container process started, so the platform shows “Live”, and the customer opens their URL to a 502.

Short answer: a running container only proves that a process started and has not exited yet. A deploy is done when the service has stayed up through a crash-loop window and answered a request on the port it actually listens on. That second check, the deployment health check, is the one most platforms skip.

That gap, between a process exists and this service answers, is where most of the support load in a deploy platform comes from. Closing it is not hard. It is just work nobody does, because the optimistic version demos well.

Advertisement

What “running” actually proves

A container’s state tells you the supervisor started a process and the process has not exited yet. It does not tell you:

  • whether the app bound a port, or bound the wrong one
  • whether it finished booting, or is thirty seconds into loading a model
  • whether it crashed and is in a restart loop that happens to be “up” at the instant you looked
  • whether anything in front of it (the proxy, the certificate, DNS) can actually route to it

Each of those produces a healthy-looking container and a broken URL. A status that reports on the first while the customer experiences the fourth is not a status. It is a coin flip with good branding.

Running vs ready vs live

State What it proves What it does not prove
Running A process started and has not exited That it listens anywhere, or will still be up in five seconds
Ready The process answers a request on its port That the public URL routes to it
Live Real traffic reaches the new version through the domain Nothing more is needed for a deploy verdict

Kubernetes users will recognise the split: it is the difference between a container that is running and one that passes its readiness probe. Most simpler platforms collapse all three rows into one green tick.

The restart loop is the honest test

The restart loop deserves special attention, because it is the case where naive status reporting is not merely incomplete but actively backwards.

A container that crashes on boot and restarts every two seconds spends most of its life in the running state. Poll it at a random moment and you will usually catch it running. The more broken it is, the faster it crash-loops, and the more reliably it looks fine. Any check that samples state once will report the worst service on the platform as healthy.

So the check cannot be a sample. It has to be a state that persists: has this service stayed up, and answered, for long enough that “it is up” is a claim about the service rather than about the moment you asked.

What a deployment health check should verify

Four deploy checks in order: build finished, container stayed running, service answered on its port, traffic reaches it

Our rule is that a deploy counts as successful only when something outside the container has confirmed it. A complete verdict checks four things, in order:

  1. The build finished. Not started: finished, with the exit status read, not inferred.
  2. The container reached a running state and stayed there across a window long enough that a crash loop cannot hide inside it.
  3. The service answered on the port it actually listens on, at the health path if one is configured. This is the check that separates “a process exists” from “a service exists”.
  4. Routing resolves. The domain, the certificate and the proxy rule all point at the new container, not the previous one.

Steps 1 to 3 are cheap and belong inside the deploy itself. Step 4 depends on DNS and certificate issuance, which can lag the deploy by minutes, so treat it as a separate signal rather than a reason to hold the deploy open.

Why the previous version should keep serving

A verdict that waits for confirmation needs somewhere for traffic to go while it waits. Keep the old container serving until the new one passes its checks. A deploy that fails its health check then costs the customer nothing but a red line in their history, instead of an outage.

The portless exception, and why it matters

Not every service has a port. A background worker, a cron host and a static site all deploy through the same pipeline, and none of them answers HTTP on a port of its own.

The tempting shortcut is to run the port probe anyway and treat the failure as non-fatal. Do not. A check that is allowed to fail without consequence is not a check. It is a line in a log that teaches everyone to ignore it, and the day it starts failing for a real reason, it has already been trained out of everyone’s attention.

Skip the probe explicitly for service types that have no port, and resolve on the running state instead. The verdict stays honest because the check that was skipped was skipped on purpose, by type, in code you can read, not because it failed and nobody minded.

Saying “I don’t know”

The last piece is the one that takes nerve: when the platform cannot confirm a deploy, it has to say so, rather than rounding up to success.

“Deployed, but we could not confirm it is serving traffic” is an unsatisfying sentence to put in a product. It is also the truth, it is actionable, and it is far better than a green tick above a URL that returns 502. Customers forgive a platform that is honest about uncertainty. They do not forgive one that was confidently wrong, because they spent the afternoon debugging their own code on its say-so.

The general principle, past deploys: a status that cannot be wrong in a way you would notice is not telling you anything.

Frequently asked questions

Why does my app show 502 Bad Gateway when the container is running?

A 502 means the proxy reached your container but got no valid answer. The usual causes are an app listening on a different port than the one the proxy targets, an app bound to 127.0.0.1 instead of 0.0.0.0, or an app that is still booting or stuck in a crash loop. A running container rules out none of these.

What is the difference between a liveness check and a readiness check?

A liveness check asks whether the process is still alive and should be restarted if not. A readiness check asks whether it can serve traffic right now. A deploy verdict needs readiness: a process can be alive for minutes while it loads data, and sending it traffic in that time still fails.

How long should a deploy wait before calling a service healthy?

Long enough to cover your slowest normal boot plus a margin, so a crash loop cannot pass by luck. Most web apps answer within seconds; apps that load large models or run migrations at start may need a minute or more. Make the window configurable per service rather than global.

Do background workers need a health check?

They cannot be probed over HTTP because they have no port. Check that the process reached a running state and stayed there through the startup window. If the worker can write a heartbeat somewhere, alert on a missing heartbeat as a separate signal.

Further reading

Advertisement

Updated October 2, 2026