You run docker run -d --name app my-image, the container appears in docker ps -a as "Exited," and nothing else happened. This is one of the most common "what just happened?" moments in Docker. The cause is almost always one of three things: the container has no foreground process, the main process crashed, or it got OOM-killed. Each leaves a different signature.
First step: read the logs and the exit code
docker logs CONTAINER_NAME
docker inspect --format '{{.State.ExitCode}}' CONTAINER_NAME
docker inspect --format '{{.State.OOMKilled}}' CONTAINER_NAMEThe exit code is the smoking gun:
| Exit code | What it means |
|---|---|
0 | The process completed normally — it ran, did its thing, and exited. Usually means the container had no foreground service. |
1–125 | Application error. The process started but errored out. docker logs shows the message. |
126 | Command found but not executable. Usually a missing executable bit on a script. |
127 | Command not found. Often a typo or a missing binary on alpine. |
137 | SIGKILL (128 + 9). On --memory limits, this is OOMKilled. docker inspect's OOMKilled field confirms. |
143 | SIGTERM (128 + 15). From docker stop or similar. Usually expected. |
Case 1: exit code 0 — no foreground process
The most common cause. The container's main process ran, did nothing useful, exited cleanly. Three flavors:
Flavor A: you ran a non-interactive command.
docker run -d ubuntu:24.04
# Exits immediatelyubuntu:24.04's default CMD is bash. Without -it, bash has no terminal, no input, no work to do — it exits. Solution: -it for interactive shells, or pass a long-running command (docker run -d ubuntu:24.04 sleep 3600 for a placeholder).
Flavor B: the image expects a command and you didn't give one.
Some images ship with a CMD ["bash"] or similar that only makes sense interactively. Without arguments and -it, they exit.
Flavor C: a server that doesn't actually start as a server.
You wrote CMD ["./my-server"] in your Dockerfile and ./my-server exits because it can't find its config, or the port it wanted is taken, or it's actually a CLI tool you meant to use differently.
docker logs is what tells you which:
docker logs CONTAINER_NAMEIf the logs are empty and exit code is 0, you have flavor A or B. If the logs show errors, you have flavor C.
Case 2: exit code 1-125 — application crash
docker logs CONTAINER_NAMEThe error message is in the logs. Common ones:
Error: ENOENT: no such file or directory— your CMD references a path that doesn't exist in the image. Check that the build COPY put the file where the CMD expects.MODULE_NOT_FOUND(Node) —node_modulesis missing or your bind mount overlaid it with empty / wrong-arch content.- Database connection refused — service is starting before the database is ready. Add a healthcheck with
depends_on: condition: service_healthy. See Restart Policies and Health Checks. bind: address already in use— port conflict. See Port is Already Allocated.- PHP fatal errors, Python tracebacks — code bug. Same as it would be outside Docker.
If the logs are empty and the exit code is non-zero, the process crashed before producing output. Try running with -it and the entrypoint overridden:
docker run --rm -it --entrypoint sh my-image
# Then inside the container, manually run the command:
# ./my-server
# See what happens.Case 3: exit code 137 — OOMKilled
docker inspect --format '{{.State.OOMKilled}}' CONTAINER_NAMEIf that returns true, the Linux OOM-killer killed your process because the container exceeded its memory limit. Either:
- The container has a
--memorylimit and the process exceeded it. Raise the limit, or fix the memory leak. - The host ran out of memory and the kernel killed Docker containers preferentially.
docker stats shows live memory usage:
docker stats CONTAINER_NAMEFor exited containers, docker inspect shows the memory limit that was set:
docker inspect --format '{{.HostConfig.Memory}}' CONTAINER_NAMECommon in Compose stacks where someone set mem_limit: 256m and the app actually needs 1 GB.
Case 4: exit code 143 — clean SIGTERM
docker inspect --format '{{.State.ExitCode}}' CONTAINER_NAME
# 143The process received SIGTERM (typically from docker stop) and shut down. Usually expected. If it's happening without you stopping the container, something else is sending the signal — Compose's down, a parent script, or an external process supervisor.
Run the container in the foreground to see what's happening
The fastest way to diagnose a container that exits is to run it in the foreground:
docker run --rm -it my-imageWithout -d, the output streams to your terminal and you can see what's happening in real time. With --rm, the container is gone the moment you ctrl-C — no cleanup needed.
If your image expects -d for some reason (a service that demonizes), override the CMD with a shell:
docker run --rm -it --entrypoint sh my-image
# Then run the original command manuallyThe "keep it running so I can debug" trick
Sometimes you need a container to stay alive even though its main process has exited, so you can docker exec into it. Override the CMD with tail -f /dev/null:
docker run -d --name dbg --entrypoint tail my-image -f /dev/null
docker exec -it dbg sh
# Now you're inside a running container.
# Manually run the original command, watch what happens.tail -f /dev/null blocks forever doing nothing — perfect placeholder. (sleep infinity works on coreutils-based images; not on alpine's BusyBox.)
CMD shell form vs exec form
Subtle case worth knowing: your container exits with code 0 even though the process inside is "running." Often caused by shell-form CMD:
CMD npm startThis runs /bin/sh -c "npm start". The shell starts npm, npm starts node — but if there's any signal handling weirdness or if npm's "exit when the script exits" semantics fire wrong, the container can exit despite node being alive.
Use exec form for actual servers:
CMD ["node", "server.js"]That runs node directly as PID 1, no shell wrapper.
Common pitfalls
-don a container that needs-it. Run interactively first to see what the image actually expects.- No logs and exit code 0. Image probably has a CMD that exits cleanly (like
bashwith no input). Try-itor a different command. - Bind mount erased something the image needs. Mounting
./local-dist:/app/distover an image that had files in/app/distmakes the container behave as if those files were gone — because they are, from the container's perspective. - Wrong architecture image on Apple Silicon. "exec format error" then exit. See exec format error.
USERrunning as a user that can't read the working files. Permission errors that look like crashes.
What to do next
- docker logs: View Container Output — read the output of an exiting container.
- docker exec: Run Commands Inside a Running Container — once you have the container kept alive with the tail trick.
- Docker Restart Policies and Health Checks — make a flaky service stop crashing or restart on failure.
- "Port is Already Allocated" — if the crash is a port conflict.
FAQ
Sources
Authoritative references this article was fact-checked against.
- docker inspect — official referencedocs.docker.com

