Ch. 13 · Docker

Docker Container Exits Immediately: How to Debug It

Why a Docker container exits immediately: check the exit code, read docker logs, open a shell and fix a command that only starts a background job.

~5 min readbeginnerupdated Oct 4, 2026

You start a container and the terminal returns instantly. docker ps is empty:

$ docker run myapp
$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
Text

docker ps -a shows what happened:

$ docker ps -a
CONTAINER ID   IMAGE   COMMAND     CREATED         STATUS                     PORTS   NAMES
9f1c2a7b3e4d   myapp   "./start.sh"  5 seconds ago  Exited (0) 4 seconds ago           sharp_torvalds
Text

Exited (0) means the process finished successfully. That is the whole problem: a container is not a machine that keeps running, it is a wrapper around one foreground process, and that process ended.

Quick fix checklist

  • Read the exit code: docker inspect <container> --format '{{.State.ExitCode}}'.
  • Read the output: docker logs <container> (add -t for timestamps).
  • Run a shell instead of the entrypoint: docker run -it --entrypoint sh <image>.
  • Then run the original command by hand and see what it does and whether it returns.
  • A shell that exits because stdin closes needs -i: docker run -i myapp.
  • A process that daemonises and returns needs to run in the foreground (for example nginx -g 'daemon off;').
  • Add --rm only after it works, so the stopped container stays inspectable while you debug.

Before you start

You need the image name, and the stopped container still present (docker ps -a) so you can read its exit code and logs. The examples were checked with Docker Engine 27/28 and the default json-file logging driver. If the container was started with --rm, re-run it without that flag to keep it around.

Why it happens

Docker’s model is one process per container: the command in ENTRYPOINT/CMD becomes PID 1. When PID 1 exits, the container stops, no matter what else is running inside, and any background children are torn down with it. So “the container exited” almost always means “the foreground process exited”, and there are four common reasons.

  1. The image’s command is a one-shot. A script runs a setup step and reaches the end. It was meant for docker run ... bash, not to be the long-lived process.
  2. The service daemonises. apachectl start, service nginx start or redis-server --daemonize yes fork a background process and return, so PID 1 exits and takes the child with it.
  3. It needs a TTY or stdin. An interactive shell without -it reads EOF on stdin and exits; some CLIs behave the same way.
  4. It crashes fast. The exit code is non-zero (1, 127 for command not found, 126 for not executable). This is not “exited immediately” by design but by error, and the logs say why.

Step-by-step walkthrough

Step 1: Read the exit code and state

docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'
Terminal

0 means a clean return (a design problem), 1 a generic failure, 137 a SIGKILL (often OOM), 143 a SIGTERM, 126/127 a command that is not executable or not found. The Error field occasionally carries a start failure such as a bad exec format.

Step 2: Read the logs

docker logs <container>
docker logs -t --tail=50 <container>
Terminal

If the logs are empty, PID 1 produced no output before exiting, which itself is a clue: a wrapper script with no exec line may be swallowing the real process.

Step 3: Get an interactive shell

docker run -it --entrypoint sh myapp
Terminal

In this shell, look around (ls, env, cat the entrypoint), then run the exact command from the image by hand. Seeing it return to the prompt tells you it is a foreground problem, not a crash.

Step 4: Fix the command

  • Run the service in the foreground: nginx -g 'daemon off;', redis-server --daemonize no, apache2 -DFOREGROUND.
  • Keep PID 1 as your app: end entrypoint scripts with exec "$@" so signals and the exit code reach the real process.
  • Give an interactive tool a TTY: docker run -it ....
  • If it genuinely is a one-shot job (a migration, a backup), that is fine; just do not run it as a long-lived service.

Step 5: Confirm it stays up

docker run --name demo myapp
# separate terminal:
docker ps --filter name=demo
docker logs -f demo
Terminal

The container should appear with Up ... and stay there. Press Ctrl+C in the log window, then docker stop demo.

Worked scenario

A team’s web container “starts and stops in a second”. docker ps -a shows Exited (0). The Dockerfile ends with CMD service nginx start. Running the image interactively and executing that command returns immediately, because service nginx start daemonises: it starts nginx in the background and the script exits, so PID 1 exits and Docker stops the container, killing nginx too. Changing the last line to CMD ["nginx", "-g", "daemon off;"] keeps nginx in the foreground, and the container stays Up.

Common mistake

Adding tail -f /dev/null or sleep infinity to the end of an entrypoint “fixes” the symptom and hides the bug. The container now stays up, but the service you actually care about may have failed to start, and the process Docker tracks is tail, not your app, so health checks, logs and graceful shutdown are all wrong. Run the real service as PID 1.

Verify the behavior

The container should report a running process and its own logs:

docker run -d --name demo myapp
docker ps --filter name=demo --format '{{.Status}}'
docker logs demo | tail
Terminal
Up 10 seconds
... application startup log lines ...
Text

The STATUS column starts with Up, and the logs show the application, not an immediate exit.

Interview exercise

“You are handed a Dockerfile: FROM alpine then CMD /app/start.sh. The image builds, but docker run returns in under a second with exit code 0. Walk me through diagnosing and fixing it.”

Answer and reasoning

I would start from the fact that a container lasts exactly as long as PID 1, and exit code 0 says PID 1 finished normally, so nothing crashed: the script returned. First docker ps -a for the code, then docker logs for output, then docker run -it --entrypoint sh <image> and run /app/start.sh by hand. If it starts a service and returns, the fix is to keep the service in the foreground (a daemon off mode, or exec ./my-app at the end of the script) rather than to append a sleep. I would also make sure the script uses exec "$@" so the real process becomes PID 1 and receives signals, which keeps docker stop graceful and the exit code honest.

Continue learning

More in Docker

read ✓Docker · easy

Docker Entrypoint and Command Overrides

Docker Entrypoint and Command Overrides. Learn the reasoning, a practical example, common mistakes and an interview exercise.

~2 min readread →
read ✓Docker · mid

Docker BuildKit and Cache Mounts

Speed up image builds with BuildKit cache mounts, multi-stage builds and dependency-first layer ordering.

~2 min readread →
esc