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

  1. 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.
  2. 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.
  3. 03Pull the exit code from the `STATUS` column or `docker inspect <container_id> --format '{{.State.ExitCode}}'` to distinguish a clean exit from a crash.
  4. 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.
  5. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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>