How do you troubleshoot a container that exits immediately after starting?
Quick Answer
Check docker logs <container> first — the application likely printed an error explaining exactly why it exited before you even need to investigate further. Check the exit code via docker inspect or docker ps -a (a clean exit code 0 suggests the main process simply finished and returned, often meaning the CMD/ENTRYPOINT isn't actually a long-running process; a non-zero code suggests an application-level error). If logs are unhelpfully empty, try running the container interactively with an overridden command (docker run -it myapp sh) to poke around before the normal command would run.
Detailed Answer
Step 1: check the logs — the fastest, most direct signal
docker logs my-container
The application itself very often printed a clear, direct explanation of what went wrong before exiting — a missing environment variable, a failed database connection, a syntax error, a missing file. This should always be the very first thing checked, before any deeper investigation.
Step 2: check the exit code
docker ps -a
# STATUS: Exited (0) 2 seconds ago <- clean exit
# or:
# STATUS: Exited (1) 2 seconds ago <- error exit
docker inspect my-container --format='{{.State.ExitCode}}'
- Exit code 0 (clean exit) — often means the container's main process simply ran to completion and returned normally. For a container that's supposed to be a long-running server, this frequently points at a fundamental misunderstanding of what the
CMD/ENTRYPOINTactually runs. For example, it could mean accidentally running a one-shot setup script instead of the actual long-running server process, or using a shell script that doesn't end with a command that blocks or keeps running. - A non-zero exit code — indicates an actual application-level error. The specific code sometimes maps to a recognizable meaning:
137(128+SIGKILL) is often an OOMKill (see the security/lifecycle topics), and1is a generic catch-all application error in most conventions. But the logs (Step 1) usually tell you much more directly than the number alone.
Step 3: if logs are empty or unhelpful, run interactively with an overridden command
docker run -it --entrypoint sh myapp:1.0
Overriding the ENTRYPOINT with an interactive shell lets you explore the image's filesystem before the normal startup command would even run. This is useful for checking that expected files or configuration are actually present, testing whether a command that's supposed to run actually executes correctly when invoked manually, or generally investigating an environment where the real command fails too fast or too silently to diagnose any other way.
Common specific root causes
- Missing or incorrect environment variables the application requires at startup, causing it to fail an early validation check and exit immediately — often with a helpful error message in the logs if the application validates configuration properly, or a much less helpful generic crash if it doesn't.
- A missing dependency file — a config file, certificate, or other resource expected at a specific path that wasn't actually included in the image or mounted correctly.
- Incorrect
CMD/ENTRYPOINT— pointing at a script or binary that doesn't exist at that path inside the image, or has the wrong permissions (not marked executable). - The application genuinely isn't meant to be long-running — e.g., accidentally treating a one-shot script/tool's image as if it should run as a persistent service.
- A crash during the application's own startup sequence — an unhandled exception during initialization. This is often the most common real cause, and it is exactly what Step 1's log check is meant to surface directly.
Verifying the CMD/ENTRYPOINT is actually what you expect
docker inspect myapp:1.0 --format='{{.Config.Cmd}} {{.Config.Entrypoint}}'
Occasionally the actual configured command differs from what you expect. This can happen when a base image's own ENTRYPOINT unexpectedly combines with your own CMD in a way you didn't intend (see the ENTRYPOINT/CMD question). Confirming exactly what's configured to run is a useful check before assuming the problem lies elsewhere. Working through logs, then exit code, then an interactive override, in that order, resolves the large majority of these issues without needing to guess at more exotic causes first.