Why Your Docker Container Exits Immediately (and How to Fix It)
You run the container, check docker ps, and it's already gone. Nine times out of ten it's PID 1 doing its job. Here's how to read it.
Key takeaways
- A Docker container runs only as long as its PID 1 process.
- If it exits immediately, your CMD finished, daemonized itself, or crashed.
- Run docker ps -a to read the exit code (0 finished, 127 command not found, 137 killed), then docker logs for the reason, and keep the main process in the foreground.
On this page
A Docker container exits immediately because its PID 1 process ended: the command finished, forked into the background, or crashed. Read the exit code with docker ps -a, read the reason with docker logs, and keep your main process in the foreground (for nginx, daemon off;).
You run docker run my-image, the prompt comes back, then you check:
docker ps -a
CONTAINER ID IMAGE STATUS NAMES
a1b2c3d4e5f6 my-image Exited (0) 2 seconds ago sleepy_hopper
Gone. The confusing part is that Exited (0) means success. Nothing crashed. The container did exactly what you told it to; that just took two seconds.
The one rule that explains all of this
A container lives exactly as long as its PID 1. That's it. When you start a container, Docker runs your CMD (or ENTRYPOINT) as process ID 1 inside the namespace. When that process exits, the container stops. There is no background. There is no "keep it warm." PID 1 returns, the container is dead, exit code and all.
So the question is never "why did my container stop?" It's "why did PID 1 finish?"
Cause 1: your main process actually finished
This is Exited (0) land. Your CMD ran a command that completes and returns.
CMD ["python", "setup_db.py"]
That script sets up a schema and exits. Container's done its job, so it stops. Same thing if your CMD is echo hello or ls or a one-shot migration. People hit this constantly with base images: docker run ubuntu exits instantly because the default command is bash, and with no TTY attached bash has no input, reaches EOF, and returns.
The fix depends on intent. If it was supposed to be a one-shot job, congratulations, it worked. If you wanted a service, you need a process that stays in the foreground and doesn't return until you kill it:
CMD ["python", "app.py"] # app.py runs a server loop, blocks forever
Cause 2: the app daemonizes itself
This one is sneaky because the process is a "server," but it forks a background copy and the foreground one exits. PID 1 returned, so the container dies even though the daemon technically started.
nginx is the classic. By default it backgrounds itself. You have to tell it not to:
CMD ["nginx", "-g", "daemon off;"]
Apache wants httpd -D FOREGROUND. Anything you'd normally run with a -d flag or a start subcommand is suspect. The rule: never let a process inside a container background itself. Docker is your process manager now; don't reach for systemd inside a container to work around this.
Cause 3: nonzero exit, meaning it crashed
Exited (1), Exited (127), Exited (137). Now it's failing, and the answer is in the logs:
docker logs sleepy_hopper
docker inspect --format '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' sleepy_hopper
A few codes carry meaning on their own. Docker's run reference (checked October 2026) defines 125, 126, and 127; any other code is your process's own:
| Exit code | Meaning | First thing to check |
|---|---|---|
| 0 | PID 1 finished successfully | One-shot CMD or a daemonizing process |
| 1, 2 | Generic application error | docker logs |
| 125 | Error in the Docker daemon or docker run itself | Bad flag, name clash, invalid option |
| 126 | Command found but can't be invoked | Missing execute bit on the entrypoint script |
| 127 | Command not found | Typo in CMD, binary not in image, wrong PATH |
| 137 | 128 + 9, SIGKILL | OOMKilled: true, or a docker stop timeout |
| 139 | 128 + 11, SIGSEGV | Native crash, often an architecture or libc mismatch |
We've watched people rebuild an image five times chasing a 127 that one docker logs would have named. Read the logs first.
Cause 4: wrong CMD or ENTRYPOINT, or missing a TTY
Sometimes the image is fine and the invocation is wrong. If you want an interactive shell, you have to actually attach one:
docker run -it ubuntu bash # -i keeps stdin open, -t gives a tty
Without -it, bash sees no input and exits immediately. That Exited (0) from docker run ubuntu earlier was exactly this.
When you're not sure what the image even tries to run, override the entrypoint and poke around from the inside:
docker run -it --entrypoint sh my-image
# now you're in the container's filesystem, run the CMD by hand and watch it fail
This is the highest-value move on the list: you see the actual error instead of guessing at a container that's already gone.
Cause 5: shell form vs exec form, and signals
This one doesn't kill the container on startup, it bites you later, but it belongs here because it's the same PID 1 story. Compare:
CMD python app.py # shell form: runs `/bin/sh -c "python app.py"`
CMD ["python", "app.py"] # exec form: python is PID 1 directly
In shell form, PID 1 is sh, and your app is a child. Docker's Dockerfile reference is blunt: shell form runs as a subcommand of /bin/sh -c, "which does not pass signals." On docker stop, the SIGTERM never reaches your app. Your app never hears the shutdown, gets a hard SIGKILL ten seconds later, and you get ungraceful exits and Exited (137) on shutdown. Use exec form (the JSON array) so your process is PID 1 and receives signals directly. If your app genuinely needs to reap child processes, add a tiny init with docker run --init rather than reaching for a shell wrapper.
The debugging loop, start to finish
When a container won't stay up, this is the order:
docker ps -a # exit code: 0 = finished, nonzero = crashed
docker logs <container> # what it printed before dying
docker run --rm my-image # run in the foreground, no -d, watch it live
docker run -it --entrypoint sh my-image # go inside, run CMD by hand
Ninety percent get solved at step two. For the wider map of Docker failures, our Docker troubleshooting guide covers the rest of the usual suspects.
Questions people ask
Why does a Docker container stop when its main process exits?
Because the container is that process. Docker starts your ENTRYPOINT or CMD as PID 1 in a new namespace, and when PID 1 returns, the namespace has nothing left to run, so the container stops with that process's exit code. Background children don't count. Keep the main process in the foreground and the container stays up.
Why does docker run ubuntu exit immediately?
The ubuntu image's default command is bash. Started without a terminal, bash reads empty stdin, hits end-of-file, and exits with code 0, so the container stops at once. Run docker run -it ubuntu bash for an interactive shell, or give it a long-running command such as sleep infinity if you only need the container alive to docker exec into.
How do I keep a Docker container running?
Run a process that never returns in the foreground: your server with its foreground flag (nginx -g 'daemon off;', httpd -D FOREGROUND), declared in exec form so it is PID 1. Avoid tail -f /dev/null hacks in production images; they keep the container alive while the real service is dead, which hides failures from restart policies and health checks.
The call we'd make
Before touching the Dockerfile, run docker ps -a and docker logs. Read the exit code as a sentence: 0 means your process finished, nonzero means it broke, 137 means something killed it. If it's a service that dies on 0, you have a backgrounding or one-shot-command problem, so pin the process to the foreground. Use exec-form CMD so signals land where they should. And keep docker run -it --entrypoint sh in your muscle memory, because a container you can walk around inside is worth ten you can only stare at in ps -a.
Sources
GitHub Actions Best Practices in 2026: Workflows You Can Trust
A production-focused GitHub Actions guide: reusable workflows, least-privilege permissions, keyless OIDC to the cloud, SHA-pinned actions, environments with approvals, concurrency-safe deploys, and CodeQL/Dependabot gates — with copy-paste examples.
Kubernetes FailedScheduling — Taints, Affinity, and Resources
A pod sits Pending and the scheduler quietly logs why it rejected every node. Learn to read that one line and fix the real cause instead of guessing.
More from DevOps
Explore more articles in this category
GitHub Copilot CLI Sandboxing Is GA: What Actually Changed
GitHub made local sandboxing generally available for Copilot CLI on October 7, so the default now restricts the agent instead of trusting it.
The Tensorlake npm Compromise Targeted Your AI Tool Configs
A compromised npm release went after cloud credentials as expected, and local AI coding-assistant config files, which most teams never treated as secrets.
GitLab's New Rate Limits: What to Fix Before Oct 19
GitLab is capping unauthenticated API calls at 60 an hour starting October 19, and the preview windows land before most teams will have noticed.
You might have missed
Evergreen posts worth revisiting.