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
- 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.
On this page
Why Your Docker Container Exits Immediately (and How to Fix It)#
You run docker run my-image, the prompt comes back, and everything looks fine. Then you check:
docker ps -a
CONTAINER ID IMAGE STATUS NAMES
a1b2c3d4e5f6 my-image Exited (0) 2 seconds ago sleepy_hopper
Gone. It ran, it exited, and nothing you did got a chance to happen. This is the most common "my container won't stay up" question, and the confusing part is that Exited (0) means success. Nothing crashed. The container did exactly what you told it to. The problem is that what you told it to do 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 really "why did my container stop?" It's "why did PID 1 finish?" Frame it that way and every case below is obvious.
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, so run everything in the foreground and let it own the lifecycle. If you're reaching for systemd inside a container to work around this, stop and run the process in the foreground instead.
Cause 3: nonzero exit, meaning it crashed#
Exited (1), Exited (127), Exited (137). Now it's not doing its job, it's failing. And the answer is almost never in your head, it's in the logs:
docker logs sleepy_hopper
Read what it printed before it died. A few codes carry meaning on their own:
127is "command not found." Usually a typo in CMD, or a binary that isn't in the image, or a wrongPATH.126is "found it, couldn't execute it." Permission bit missing on your entrypoint script, or it's not actually executable.1and2are generic app errors. Read the logs.137is128 + 9, a SIGKILL. Often the OOM killer. Checkdocker inspect <id>for"OOMKilled": true.
I've watched people rebuild an image five times chasing a 127 that a single docker logs would have named in one line. 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 run the real command by hand and 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. When Docker sends SIGTERM on docker stop, it goes to sh, which doesn't forward it. 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. The exit code tells you which story you're in, the logs tell you the line, and the foreground run lets you watch it happen. For the wider map of Docker failures, our Docker troubleshooting guide covers the rest of the usual suspects.
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.
Get the DevOps Troubleshooting Cheat Sheet
Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.
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 Workload Identity — Projected Tokens and OIDC to Cloud IAM
How pods can talk to AWS, GCP, and Azure with no static keys — using audience-bound projected ServiceAccount tokens and the cluster OIDC issuer.
More from DevOps
Explore more articles in this category
Best Kubernetes IDE and GUI Tools in 2026
kubectl is fine until you're juggling five namespaces across three clusters. These are the tools that make that manageable, compared.
Chef vs Puppet vs Ansible: Configuration Management in 2026
One is agentless and Python-based, the other two run a persistent agent and a domain-specific language. The architecture difference matters more than the syntax.
PagerDuty vs Opsgenie: Choosing an Incident Alerting Tool
Both page the right person at 3am and both integrate with everything. The real differences show up in pricing structure, workflow depth, and who already owns the ecosystem around you.
You might have missed
Evergreen posts worth revisiting.