A container stuck in a restart loop usually fails in the first second. Find the real error with logs, exit codes and these five common causes.
What a restart loop actually is
A container "stuck restarting" is not mysterious: the main process exits (usually within the first second or two), and the restart policy dutifully relaunches it, forever. The container is doing exactly what it was told; your job is to find out why the process dies. The answer is almost always sitting in the logs of the failed run, and the debugging discipline is to read the evidence before changing anything.
docker ps -a # see the status and restart count
docker logs --tail 100 # the error from the last run
docker inspect \
--format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
Decode the exit code
The exit code narrows the cause before you read a single stack trace:
- 1 (or app-specific nonzero): the application errored, read the stack trace; usually config or a missing dependency
- 137: the process was SIGKILLed, either the kernel OOM killer (check OOMKilled in inspect) or a stop timeout; if OOMKilled is true, raise the memory limit or fix the leak
- 126: the entrypoint exists but is not executable, typically a missing chmod +x or a Windows line-ending problem in a shell script
- 127: command not found, a CMD typo, or the binary does not exist in your slim base image (bash on alpine is the classic)
- 139: segmentation fault, very often a native module compiled for the wrong architecture (x86 module in an ARM container or vice versa)
The five usual suspects
Across thousands of restart loops, the same five causes dominate:
Missing environment variable: the app’s config validation throws on boot; compare docker exec env expectations against what the service actually defines
Database not ready: the app connects once at startup, Postgres is still initializing, the connection fails and the process exits, add retry-with-backoff in the app or a health-gated depends_on
Wrong bind address: the app listens on 127.0.0.1 inside the container, so nothing can reach it and a healthcheck kills it; always bind 0.0.0.0 in containers
Memory limit below reality: a Node app that needs 600 MB in a 512 MB container will OOM on schedule; watch docker stats during startup
Bad healthcheck: the check curls the wrong port or path, marks a healthy app unhealthy, and the platform restarts it, verify the check command by running it manually with docker exec
Reproduce it interactively
When the logs are too thin (some apps crash before configuring their logger), bypass the loop entirely: start a shell in the same image with the same environment, then launch the process by hand and watch it fail in slow motion:
docker run -it --rm --entrypoint sh \
--env-file <(docker inspect --format \
'{{range .Config.Env}}{{println .}}{{end}}') \
# inside: run the original CMD manually
Prevent the next one
Three habits eliminate most restart loops before they ship: validate configuration at boot and fail with a clear message naming the missing variable; add startup dependency retries so ordering never matters; and test the image locally with docker run using production-shaped environment variables before deploying. Platforms help too, Peon streams the failing container’s logs in the dashboard, so the stack trace is one click away rather than an SSH session.
Top comments (0)