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 NAMESdocker 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_torvaldsExited (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-tfor 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
--rmonly 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.
- 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. - The service daemonises.
apachectl start,service nginx startorredis-server --daemonize yesfork a background process and return, so PID 1 exits and takes the child with it. - It needs a TTY or stdin. An interactive shell without
-itreads EOF on stdin and exits; some CLIs behave the same way. - 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}}'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>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 myappIn 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 demoThe 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 | tailUp 10 seconds
... application startup log lines ...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.