Docker container exits immediately or keeps restarting: a debugging checklist
A container that shows "Exited (0)" or "Restarting (1)" seconds after starting has a main process that stopped. This checklist covers logs, exit codes, PID 1, CMD vs ENTRYPOINT, foreground processes, healthchecks and restart policies.
Codestria Engineering
Engineering team
You run docker run -d my-api or docker compose up, and a moment later docker ps shows nothing. docker ps -a reveals Exited (1) 3 seconds ago, or the container cycles through Restarting (1) forever. Sometimes it is even Exited (0), which looks like success but still means your service is not running.
A container runs exactly as long as its main process. Every one of these symptoms means that process stopped. This checklist works through the reasons, from the quickest checks to the less obvious ones.
Why it happens
Docker starts one process, the combination of ENTRYPOINT and CMD, as PID 1 inside the container. When PID 1 exits, the container stops, whatever else is running inside it. The restart policy then decides whether Docker starts it again. That gives four broad causes:
- The process crashes: missing environment variables, a config error, a dependency that is not reachable, a missing file.
- The process finishes: it daemonised into the background, or the command was a one-off task.
- The process never starts: wrong path, missing interpreter, Windows line endings in a script, wrong CPU architecture.
- Something kills it: the memory limit, or a signal from an orchestrator.
Step 1: Get the logs, exit code and OOM status
# Show stopped containers too
docker ps -a
# Output from the container, including the crash
docker logs --tail 100 my-api
# Exit code, OOM flag, error and restart count
docker inspect my-api \
--format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} err={{.State.Error}} restarts={{.RestartCount}}'
# Watch lifecycle events live in another terminal
docker events --filter container=my-api
# Compose equivalents
docker compose ps -a
docker compose logs --tail 100 apiExit codes follow the same conventions as in Kubernetes: 0 means the process completed, 1 is a generic application error, 126 means the command is not executable, 127 means it was not found, 137 is SIGKILL (check OOMKilled), and 139 is a segmentation fault. If docker logs is empty, the process almost certainly failed before writing anything, which points at steps 3 and 4.
Step 2: Make sure the main process runs in the foreground
A frequent cause of Exited (0) is a command that starts a service in the background and returns: service nginx start, pm2 start, gunicorn --daemon, or a shell script ending in my-server &. The launcher exits successfully, PID 1 is gone, and the container stops. Run servers in the foreground: nginx -g 'daemon off;', pm2-runtime instead of pm2, or just node server.js.
Do not paper over it with tail -f /dev/null or sleep infinity. The container stays up, but Docker can no longer tell whether your service is alive, and signals go to the wrong process.
Step 3: Check CMD, ENTRYPOINT and PID 1
ENTRYPOINT defines the executable; CMD provides default arguments to it, or the whole command if there is no entrypoint. Arguments passed to docker run image ... replace CMD, not ENTRYPOINT, which surprises people who expect docker run my-api sh to give them a shell.
Prefer the exec form (JSON array). The shell form, CMD node server.js, wraps your command in /bin/sh -c, so the shell is PID 1 and may not forward SIGTERM to your app. Docker then waits for the stop timeout and kills it with SIGKILL, which shows up as exit code 137 on every deploy. Entrypoint scripts must finish with exec "$@" so the real process replaces the shell and becomes PID 1.
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
# Normalise line endings and make sure the script is executable
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \
&& chmod +x /usr/local/bin/docker-entrypoint.sh
USER node
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "dist/server.js"]
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:3000/healthz').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"#!/bin/sh
set -e
# Fail loudly and early on missing configuration
: "${DATABASE_URL:?DATABASE_URL is required}"
# One-off setup steps can go here
# Replace the shell so the app is PID 1 and receives SIGTERM
exec "$@"If your app spawns child processes or does not handle signals itself, run the container with docker run --init (or init: true in Compose). That adds a minimal init process as PID 1 that forwards signals and reaps zombie processes.
Step 4: Rule out "no such file or directory" traps
exec /docker-entrypoint.sh: no such file or directory is misleading, because the file often exists. The usual causes are Windows CRLF line endings, which make the kernel look for an interpreter called /bin/sh\r, a shebang pointing at bash in an Alpine image that only has sh, or a binary linked against glibc running on musl. exec format error means the image architecture does not match the host, for example an arm64 image on an amd64 server; build with docker buildx build --platform linux/amd64.
To investigate any of these, start a shell in the image with the entrypoint overridden, then run the original command manually: docker run -it --rm --entrypoint sh my-api. You can check the file with ls -la, head -1 and file, inspect env, and see the actual error.
Step 5: Understand restart policies and healthchecks
Restart policies control what Docker does after the process exits: no (the default), on-failure[:max-retries] for non-zero exits only, always, and unless-stopped, which behaves like always except after you stop the container manually. Docker waits progressively longer between restarts, but a crash loop under always can hide the problem for days if nobody checks RestartCount.
A HEALTHCHECK marks the container healthy or unhealthy; it does not restart it in plain Docker. In Compose, use it with depends_on: { db: { condition: service_healthy } } so your app does not start, fail to connect, and exit before the database is ready, which is a very common source of restart loops in local environments.
Prevention tips
- Use exec-form
ENTRYPOINTandCMD, and end entrypoint scripts withexec "$@". - Add a
.gitattributesrule (*.sh text eol=lf) so scripts never get CRLF line endings. - Keep servers in the foreground and log to stdout and stderr.
- Build multi-platform images when developers and servers use different architectures.
- Add healthchecks with a sensible
start-period, and alert on restart counts rather than relying onrestart: always.
Key takeaways
- A container stops when PID 1 exits; find out why that process stopped.
docker logs, the exit code and theOOMKilledflag explain most cases.- Exit code 0 usually means a daemonised or finished process; run the server in the foreground.
- Exec form and
exec "$@"keep your app as PID 1 so it receives signals. - Healthchecks report status; restart policies react to exits. They are not the same mechanism.
If your containers behave differently on every machine, our DevOps and cloud team can standardise your images, Compose setup and deployment pipeline. Get in touch.
Frequently asked questions
Why does my Docker container exit with code 0 right after starting?
Its main process finished successfully. Usually the command started a service in the background (a daemon) and returned, or the image's default command is a shell with nothing to run. The main process must stay in the foreground.
Does Docker restart a container when its healthcheck fails?
Not on its own. Plain Docker only marks the container unhealthy; restart policies react to the process exiting. Swarm, Kubernetes or your own tooling must act on health status.
How do I get a shell in a container that exits immediately?
Override the entrypoint: docker run -it --rm --entrypoint sh your-image. You get a shell in the same filesystem and environment, and can run the original command by hand to see why it fails.
Need help with devops & cloud?
Docker, Kubernetes, CI/CD and AWS: reliable deployments, monitoring and lower cloud bills.