Docker · beginner
Container starts and exits immediately: read the process lifecycle
A container that returns to the shell seconds after `docker run` almost always means its PID 1 foreground process terminated. Docker is doing its job; the application inside is not. The lifecycle is observable in `docker ps -a`, in `docker logs`, and in the image's configured Entrypoint and Cmd, so diagnosis starts in metadata before it touches code.
The symptoms
- •`docker run` returns control almost immediately and the shell prompt reappears, with the container no longer in `docker ps` but visible in `docker ps -a` with status `Exited (...)`.
- •Compose or orchestrator keeps recreating the container; the new instance also exits within seconds, producing looping restart counts.
- •Logs end abruptly with a stack trace, a single error line, or no output at all, and the container's recorded exit code is nonzero or zero depending on the cause.
- •Interactively running the image with `docker run -it <image> sh` works, but the configured Entrypoint does not, suggesting the issue is in the foreground command rather than the image itself.
Likely causes
- •The configured CMD or ENTRYPOINT references a binary, script, or interpreter that is not present at that path inside the image, causing immediate termination with a "not found" or exec error.
- •The foreground process is a script or one-shot utility with no long-running loop, such as a build step, a migration, or a `tail` of a file that does not exist, so it exits cleanly with code 0.
- •Application-level startup failure: missing environment variables, unparseable config, unreachable database, or a port that is already in use, which causes the process to fail fast and emit a stack trace.
- •Permission or filesystem failure on a bind mount, a target file not executable, or a missing library, producing exec format errors or runtime loader errors at process start.
- •Architecture mismatch, such as invoking an x86_64 binary on an arm64 host, returning an exec format error from the kernel before the program can run.
First ten minutes
- 01Confirm the container is not running and capture its short id with `docker ps -a --filter "status=exited"` so you can correlate logs and inspect output.
- 02Read the last lines of logs with `docker logs <container_id>` and note the very last message, since the terminating line usually sits at the end.
- 03Pull the exit code from the `STATUS` column or `docker inspect <container_id> --format '{{.State.ExitCode}}'` to distinguish a clean exit from a crash.
- 04Inspect the image's configured command with `docker inspect <image> --format '{{json .Config.Cmd}} {{json .Config.Entrypoint}}'` so you know exactly what the container tried to run.
- 05Re-run the image with a shell override, for example `docker run --rm -it <image> sh`, and run the foreground command manually to see the error in your terminal.
Evidence to collect
- •Container exit code from `docker ps -a` STATUS or `docker inspect` State.ExitCode, which determines whether the process exited cleanly or under an error.
- •Full stdout and stderr from the container via `docker logs <container_id>`, including the terminating line(s) and any error codes returned by the program.
- •Image configuration: Cmd, Entrypoint, Env, WorkingDir, and User from `docker inspect <image>`, which together describe exactly what the container started.
- •Host-side evidence on mounted paths: file existence, ownership, and permissions, which determine whether a bind mount can be read or written.
- •Daemon events scoped to the container with `docker events --filter container=<container_id> --since=<timestamp>`, which records start, die, and OOM events.
Where to look
- •Inside the container's writable layer using `docker run --rm -it <image> sh` or `docker run --entrypoint sh <image>`, so you can list the filesystem and confirm paths.
- •In the image's `.Config` section from `docker inspect` to see the resolved Entrypoint, Cmd, and any default environment that the image author set.
- •In the host's filesystem at the bind mount source, comparing file modes and ownership with the user and umask the container will run as.
- •In the Docker daemon logs on the host, where kernel-level exec errors and OOM kills are surfaced when the container terminates at the system level.
Diagnostic steps
- 01Reproduce with a known short command first, for example `docker run --rm <image> /bin/sh -c 'echo ready; exit 7'`, to confirm `docker run` itself works and exit codes are propagated from the container.
- 02Replace the container's command with a shell to inspect the image, for example `docker run --rm -it --entrypoint /bin/sh <image> -c 'ls -l /app; cat /etc/os-release'`, and verify the expected binary or script exists at the configured path.
- 03Diff the expected command against the image's Entrypoint and Cmd by running `docker inspect <image> --format '{{json .Config}}'` and walking the merged command line that Docker will execute.
- 04Run the original command inside that shell, for example `<configured_cmd>`, and observe the same error locally, which proves the failure is inside the application rather than in Docker's lifecycle.
- 05Check that the host architecture matches the image architecture with `docker image inspect <image> --format '{{.Architecture}}'` and `uname -m`, since a mismatch produces an immediate exec error.
- 06Test with a no-op foreground such as `docker run --rm <image> /bin/sh -c 'while true; do sleep 60; done'` to confirm Docker can keep the container alive at all, isolating the issue from your application.
Common mistakes
- •Treating any exit as a crash: an exit code of 0 means the foreground process finished normally, often because it is a script or a one-shot tool, not because something failed.
- •Chasing the wrong layer: changing restart policies, increasing memory, or rebuilding the image when the configured command itself is wrong, which cannot be fixed by Docker knobs.
- •Overriding only CMD when the real problem is ENTRYPOINT, or vice versa, because the two fields are combined and the error message points at the merged string.
- •Trusting "it works on my machine" against the published image without checking architecture, base image, or filesystem layout, which can differ between local and deployed environments.
- •Ignoring the difference between "container exited" and "process exited", and so looking at Docker logs when the meaningful evidence is the application's own log output.
Safe fixes
- •Correct the command path inside the image so the ENTRYPOINT or CMD references a binary that actually exists in the image's layer, and rebuild the image rather than patching it at runtime.
- •Override the entrypoint for debugging only, using `docker run --rm -it --entrypoint /bin/sh <image>`, to reach the filesystem and verify the application layout before changing the image.
- •Provide the missing runtime inputs, such as required environment variables, configuration files, or mounted secrets, by passing them through `--env`, `--env-file`, or `--mount` invocations.
- •Resolve bind mount issues by aligning ownership and permissions on the host directory with the user the container runs as, or by selecting a User in the image that matches the host.
- •After a change, run the container with a finite command like `docker run --rm <image> <cmd>` first, then `docker run --rm <image>` once the foreground is expected to be long-running, to confirm the fix before deploying.
Prove the fix
- 01`docker ps --filter "id=<container_id>"` shows the container as `Up` for at least the expected idle period, and `docker ps -a` shows no rapid restarts after the change.
- 02`docker logs <container_id>` shows the expected startup banner or readiness line for the application, and no terminating error appears at the end of the log.
- 03`docker inspect <container_id> --format '{{.State.Status}} {{.State.ExitCode}}'` returns `running 0` while the container is up, and the recorded exit code, after a controlled stop, matches what the application documents for a graceful shutdown.
- 04Running the original invocation a second time produces the same `Up` status, demonstrating that the fix is reproducible and not a one-off timing artifact.
Prevention and next steps
- •Pin base images and application versions in the Dockerfile so the runtime and the binary paths inside the image are stable across environments and rebuilds.
- •Make the Dockerfile's last line an explicit, absolute ENTRYPOINT or CMD, and avoid relying on PATH-resolved names for the foreground process.
- •Add a HEALTHCHECK in the image so the container's lifecycle is checked by the platform, and use a restart policy that matches the application's startup behavior rather than a default.
- •Cover the startup path with a smoke test that runs the image with the same command and asserts that the container stays up for a defined window before promoting the image.
Safe commands and checks
docker ps -a --filter status=exited --format "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
docker logs <container_id> --tail 200
docker inspect <container_id> --format '{{.State.Status}} {{.State.ExitCode}} {{.State.Error}} {{.State.OOMKilled}}'
docker inspect <image> --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}} {{json .Config.Env}}'
docker run --rm -it --entrypoint /bin/sh <image>
docker run --rm <image> /bin/sh -c 'echo ready; exit 7'
docker image inspect <image> --format '{{.Architecture}} {{.Os}}'
docker events --filter container=<container_id> --since=<timestamp>