"The App Is Running" Is Not a Status

We built a health check system because we kept finding services that reported green while silently failing to connect to their database, load configuration, or bind to a port. The container was alive. The service was stuck. And we'd only discover it after trying to use it in an environment where debugging takes hours.

Here's what was actually happening: Docker Compose confirms a container has started. It has no way to know whether your API finished booting, whether your migrations ran, whether Redis answered, or whether your secrets loaded correctly. A misconfigured environment variable won't stop the container from running. Neither will a missing database connection. The process sits there, fully operational according to the system, completely broken according to reality.

We fixed it by treating those as separate problems. A real health check endpoint probes what actually matters: can the service reach the database, load its config, connect to the cache, run its startup logic. That endpoint returns 200 only when the answer to all of those is yes. Pair it with structured logging so you see exactly what failed and where, not just that something did.

The payoff shows up immediately. The first time you catch a misconfigured secret before it leaves your laptop instead of surfacing in staging, the pattern pays for itself. We use this approach in everything we deploy now because we stopped accepting "running" as an answer to "is it ready."

https://userig.app/blog/the-app-is-running-is-not-a-status