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, 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, which 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 means 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 means the memory limit is 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 happens when 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. The 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. |