What you're seeing
$ docker ps
CONTAINER ID IMAGE STATUS NAMES
abc123def456 myapp:latest Restarting (1) 3 seconds ago myappOr from docker ps -a:
CONTAINER ID STATUS NAMES
abc123def456 Restarting (137) 15 seconds ago myappThe number in parens is the exit code — it's the single most useful piece of information on the screen.
What's causing this error
A container restarts when:
- Its main process exits, and
- The container has
--restart always,--restart on-failure, or--restart unless-stopped
So "container keeps restarting" really means "the process inside the container keeps exiting." The five real reasons:
- App crashes immediately on startup. Missing config, bad env var, syntax error, missing file. The logs almost always say why — most people just don't check them.
- App exits cleanly (exit 0). Some images run a one-shot command and exit. With
--restart always, Docker dutifully restarts the now-completed task forever. - OOM killed. The container exceeds its memory limit. Linux's OOM killer sends SIGKILL → exit 137.
- Missing dependency at runtime. A volume isn't mounted, a network service isn't reachable, a config file isn't where the app expects.
- Healthcheck failing. With Swarm or compose
restart: on-failurepolicies, failing healthchecks trigger restarts even when the process itself is alive.
How to fix it
Step 1: Read the logs
docker logs --tail 100 myapp90% of the time this prints the actual error. Look for stack traces, "permission denied," "connection refused," "no such file or directory," "ENOENT," panic messages.
If logs are empty or <no log>, the process didn't get far enough to write anything — usually means the entrypoint script is broken or the binary doesn't exist.
Step 2: Check the exit code
docker inspect myapp --format='{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'What the common exit codes mean:
| Exit code | Meaning | Likely cause |
|---|---|---|
0 | Clean exit | Process completed (one-shot script / cron-style command being restarted by policy) |
1 | General error | App-level failure; check logs |
125 | Docker itself failed | Bad docker run flags or daemon issue |
126 | Command not executable | Permission issue on entrypoint script |
127 | Command not found | Wrong path in CMD/ENTRYPOINT, missing binary |
137 | SIGKILL (often OOM) | Memory limit exceeded; OOMKilled: true confirms |
139 | SIGSEGV | Segfault — app bug, missing native lib, wrong CPU arch (x86 image on ARM) |
143 | SIGTERM | Container told to stop; usually means docker stop ran during your investigation |
Step 3: Stop the restart loop while you debug
The loop makes diagnosis hell — the container disappears between every docker exec. Disable restart:
docker update --restart=no myapp
docker stop myappNow you can run it interactively to see what happens:
docker run --rm -it --entrypoint sh myapp:latest
# Inside the container, manually run whatever the CMD was:
# /app/start.shThis gives you a shell, runs the failing command, and shows you the actual output.
Step 4: Fix by category
Exit 0 + --restart always — the app is finishing its work and Docker restarts it forever:
docker update --restart=on-failure myapp
# Or remove the restart policy entirely:
docker update --restart=no myappon-failure only restarts on non-zero exits, leaving clean exits alone.
Exit 137 + OOMKilled: true — memory limit too low:
docker update --memory=1g --memory-swap=1g myappOr remove the limit if you set one too aggressively:
docker update --memory=0 myappExit 127 (command not found) — your CMD references a binary that isn't in the image. Usually a copy/paste error in the Dockerfile or wrong path. Run interactively (step 3) and check which <command>.
Healthcheck failing — the process is fine but the healthcheck script is wrong. Check:
docker inspect myapp --format='{{json .State.Health}}' | jqEither fix the healthcheck command in your Dockerfile, or remove it temporarily:
# docker-compose.yml
services:
myapp:
healthcheck:
disable: trueCommon edge cases
| Situation | What's actually wrong |
|---|---|
Status shows Restarting (0) | App is exiting cleanly. --restart always is restarting a successful one-shot command. Switch to --restart on-failure or remove the policy. |
Exit 137 but OOMKilled: false | Not OOM — something else sent SIGKILL. Usually docker stop with a slow shutdown that timed out. Lengthen the stop timeout or fix the app's signal handling. |
| Logs show "permission denied" on a mounted volume | Container user (often UID 1000) doesn't match the volume's file ownership. Either chown -R 1000:1000 the volume contents, or run the container as the matching user. |
| Container works locally, restart loops on the server | Different CPU arch — common when an Apple Silicon Mac builds an ARM image and pushes to an x86 server. Build with --platform linux/amd64. |
| Restarts only when scaled to multiple instances | Port conflict between instances, or stateful storage that doesn't support concurrent writers (SQLite over NFS, etc.) |
| Healthcheck killing perfectly fine container | Healthcheck start_period too short — app takes longer to warm up than the grace period. Increase start_period in Dockerfile or compose. |
| Restarts only at certain times of day | Cron job inside container exits, or scheduled task triggers OOM. Check docker inspect for restart timestamps and correlate. |