Why “the container started” is not a successful deployment

“The container is running” sounds like good news. Sometimes it is. Often it is only the first clue.
For a managed application host, a successful deployment is not a green process badge. It is an app that answers on the expected address, has the storage it needs, can complete its first-time setup, and stays reachable after the panel walks away.
We learned that distinction the hard way while building Bring Your Own App support.
The failure mode
A customer can paste a public Docker image that starts perfectly. Docker reports a live process. The logs look quiet. Then nothing answers when a browser reaches the configured port.
There are many legitimate reasons:
- the image listens on a different port than its documentation suggests;
- the app needs a writable volume before it can finish booting;
- it is waiting on a database or another service in its Compose stack;
- it has a first-run migration or admin setup step that has not completed;
- it starts a background worker rather than an HTTP server at all.
Calling that deployment “successful” because a process exists creates the worst kind of failure: a customer receives an address, clicks it, and sees nothing.
Our first instinct was too shallow
Early on, it was tempting to use container state as the gate. It is easy to observe and it is better than doing nothing. But it does not answer the question the customer is actually asking: can I use this app?
That gap matters even more when a platform supports unfamiliar public images. A catalog template can encode its ports, volumes, database wiring and first-time credentials. A random image cannot. The platform has to prove the configuration before turning it into a customer-facing service.
What the preflight now checks
Before a Bring Your Own App recipe is offered for deployment, we test it on real isolated infrastructure. The test is deliberately layered:
- Build the deployment recipe. We derive the image, exposed port, persistent storage, environment values and any supported companion services.
- Boot it in a temporary sandbox. This catches missing images, invalid environment values, volume permission problems and Compose wiring issues without touching a customer’s capacity.
- Wait for a real application response. A process must answer HTTP on the configured port within the readiness window. “Running but unreachable” is a failed preflight, not a deployable app.
- Read the useful failure evidence. Recent logs, the selected port, missing settings and resource errors are kept available so a customer can understand what happened.
- Repair narrowly, then retest. Where the failure is a supported configuration issue, the repair workflow changes the recipe and runs the same test again. It does not guess indefinitely, loosen host security, or ship an unverified configuration.
Why this is better than a larger timeout
When an app does not answer, the easy response is “wait longer.” Sometimes an app genuinely needs more startup time. But a longer timeout cannot fix a wrong port, a missing database, or a process that was never meant to serve HTTP.
We use time as one signal, not the definition of health. A readiness check gives us an outcome that maps to the customer experience: reachable or not reachable.
First-time credentials are part of readiness too
Many self-hosted apps create an administrator during their first run. Some accept credentials through environment variables; others show an initial setup page; some generate a password in their logs. A deployment that hides or loses that handoff is not complete just because the app responds.
Catalog templates document the setup path and show the generated credentials where the app supports it. For Bring Your Own Apps, preflight identifies the expected setup behavior when it can and keeps the raw evidence available when it cannot. We would rather say “this needs a browser setup step” than invent a password the image never created.
What customers get from the extra work
The goal is simple: the deploy button should mean something. Catalog apps should be ready to use, and unfamiliar Docker images should be tested before we ask a customer to spend capacity on them.
Not every image belongs on shared managed hosting. Some need host networking, privileged access, a Docker socket, or a protocol other than web traffic. Those are declined with a reason rather than forced through a system that cannot operate them safely.
A running process is an implementation detail. A reachable, usable app is the product.