A container that keeps restarting looks alarming in docker ps — the status flickers between Restarting and Up (about a second) and never settles. The instinct is to blame the restart policy and turn it off. That’s almost never the fix.
A restart policy of always or unless-stopped does exactly one thing: relaunch the container whenever its main process exits. If the process crashes the moment it starts, Docker dutifully restarts it, it crashes again, and you get a loop. The policy is doing its job. The container is failing, and the reason is in the logs.
This guide is the diagnostic path: confirm the loop, read the logs, check the exit code, and fix the actual cause. The same steps work whether you started the container with docker run or with Compose.
Confirm what you’re looking at
First, see the status and how the container is behaving:
docker ps -a
CONTAINER ID IMAGE STATUS NAMES
a1b2c3d4e5f6 my-app:1.0 Restarting (1) 4 seconds ago app
Restarting (1) is the tell. The number in parentheses is the exit code the process returned before Docker restarted it — here, 1. A container that exits and gets relaunched over and over is in a crash loop. If the status is constantly cycling, that’s your confirmation.
Read the logs — this is where the answer lives
The single most useful command. It shows what the process printed before it died, even though the container isn’t running right now:
# Everything the container has logged
docker logs app
# Just the last 50 lines, which is usually where the crash is
docker logs --tail 50 app
# For a Compose stack, by service name
docker compose logs --tail 50 app
Most of the time the answer is sitting right there: a config file it couldn’t parse, a database it couldn’t reach, a missing environment variable, a port it couldn’t bind. Read the last 10–20 lines carefully before you change anything.
Check the exit code
The exit code narrows the cause fast. Pull it with docker inspect:
docker inspect app --format '{{ .State.ExitCode }} {{ .State.Error }}'
Here’s what the common codes mean:
Common container exit codes
| Exit code | Usual meaning |
|---|---|
| 0 | Clean exit. The process finished and isn't a long-running service (check your command). |
| 1 | Generic application error. Read the logs for the specific failure. |
| 125 | Docker itself couldn't run the container (bad run options or flags). |
| 126 | The command was found but isn't executable (permissions or wrong format). |
| 127 | Command not found — wrong path in the entrypoint or command. |
| 137 | Killed by SIGKILL — usually out of memory (OOM) or a stop that timed out. |
| 143 | Terminated by SIGTERM — a normal stop signal. |
Two of these come up constantly. Exit 137 points at memory — the host ran out, or a container memory limit was hit and the kernel killed the process. Exit 0 on a service that’s supposed to stay running usually means the command does its thing and exits, like running an interactive image with no foreground process; the container has nothing to keep alive, so it stops, and the policy restarts it into the same dead end.
The usual root causes
Once you’ve read the logs and noted the exit code, it’s almost always one of these.
Bad or missing configuration
A malformed config file or a missing required environment variable kills many apps on startup. The logs typically name the file or the variable. Fix the value, then recreate:
docker compose up -d # recreates the container with corrected config
If you’re managing config through environment variables, a missing or empty value is a frequent culprit — the environment variables in Compose guide covers why a variable can end up empty inside a container.
A missing or wrong mount
If the app expects a file or directory that isn’t mounted — a config file, a certificate, a data directory — it crashes looking for it. Check that your bind mounts point at paths that exist on the host and land where the app expects inside the container. Confirm the mounts Docker actually applied:
docker inspect app --format '{{ json .Mounts }}'
A port conflict
If the app can’t bind its port because something else already owns it, it may exit on startup. This shows up as a bind error in the logs. The fixes are covered in port already allocated and bind address already in use — usually you change the host-side port or stop whatever’s holding it.
A dependency that isn’t ready
A web app that connects to a database on startup will crash if the database isn’t accepting connections yet. depends_on controls start order, but by default it doesn’t wait for the dependency to be ready. Add a healthcheck to the dependency and use the long-form depends_on so the app waits properly:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: change-me
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
app:
image: my-app:1.0
depends_on:
db:
condition: service_healthy
restart: unless-stopped
Now app doesn’t start until Postgres reports healthy, which removes the “database not ready” crash on first boot.
Out of memory
If docker inspect shows exit code 137 and the logs cut off abruptly, the process was likely OOM-killed. Check host memory with free -h, and if you set a memory limit on the container, confirm it’s high enough for the app. Raising or removing an over-tight limit, or freeing host memory, stops this class of restart.
Stop the loop while you work
A constantly restarting container is noisy and can churn CPU. Pause the loop so you can investigate calmly:
# Switch the policy off and stop it
docker update --restart=no app
docker stop app
# With Compose, set restart: "no" (or remove the line) then:
docker compose up -d
The container now stays stopped. Fix the root cause, set the policy back to unless-stopped, and bring it up again.
Restart-loop diagnostic order
- docker ps -a — confirm the Restarting status and note the exit code
- docker logs --tail 50
— read the real error - docker inspect — check exit code, configured command, and mounts
- Match the cause: config, missing mount, port conflict, dependency, or OOM
- For dependency timing, add a healthcheck and condition: service_healthy
- Fix the cause, then restore restart: unless-stopped
When the logs are empty
Sometimes the container dies before the app prints anything, leaving an empty log. That usually means the entrypoint or command is wrong, or the executable isn’t there (watch for exit codes 126 and 127). Run the image interactively with a shell to poke around without the loop:
docker run --rm -it --entrypoint sh my-app:1.0
# then try to start the app by hand and watch what happens
If the daemon itself is unreachable rather than a single container failing, that’s a different problem — start with fixing cannot connect to the Docker daemon.
A restart loop is rarely mysterious once you stop treating the policy as the villain. Read the logs, read the exit code, fix the cause underneath. Nine times out of ten the container is telling you exactly what’s wrong — you just have to look at what it printed on the way down.