
Docker Container Keeps Restarting After Reboot – Two Problems, Two Fixes
Server rebooted. Containers didn’t come back up – or they came back up and immediately entered a restart loop.
These look like the same problem. They’re not.
Problem A is a configuration gap: your restart policy is set correctly but Docker itself never started after the reboot. The containers never got a chance to come up.
Problem B is an application problem: the container is starting, crashing, and being restarted over and over. The restart policy is masking the real failure.
Each problem has a completely different fix. Most guides cover one or the other. This one covers both — and tells you which one you’re dealing with in 30 seconds.
⚠️ Run This First: It Tells You Which Problem You Have
Run these two commands immediately after the reboot or as soon as you notice the issue. The output tells you which section of this guide to jump to.
| Command | What the output means |
|---|---|
systemctl is-active docker |
inactive or failed → Problem A. Docker daemon didn’t start. Your containers never had a chance. active → Docker is running. Go to Problem B. |
docker ps -a |
STATUS shows Exited with no restart activity → Problem A. STATUS shows Restarting (N) X seconds ago → Problem B. |
Who this is for: Developers and system administrators self-hosting applications on Docker Compose – n8n, Postgres, Redis, Traefik — who experience containers not surviving a server reboot or containers stuck in a restart loop after one. For n8n-specific environment variable issues, see the Docker Compose .env Not Working guide.
Two Different Problems With the Same Symptom
The phrase “container keeps restarting after reboot” describes two failure modes that share almost no common cause or fix. Running the wrong fix wastes time and occasionally makes things worse for example, adding a stricter restart policy to a container that was never starting in the first place.
| Symptom after reboot | Root cause | Fix lives in |
|---|---|---|
Containers are gone. docker ps is empty. docker ps -a shows them as Exited. |
Docker daemon did not start on boot. Restart policy never triggered. | systemctl – enable the daemon |
Container status shows Restarting (1) 8 seconds ago and climbs. It’s cycling every few seconds. |
Application is crashing. Docker restarts it. It crashes again. The restart policy is hiding the error. | Application logs – find the crash cause |
Both problems can also happen simultaneously after a reboot: Docker starts, some containers come up cleanly, one enters a crash loop because a dependency wasn’t ready. Check each container’s status individually don’t assume they all have the same problem.
Why restart: always in Compose Doesn’t Survive Reboots if Docker Daemon Isn’t Enabled
This is the most common misconception about Docker restart policies. Setting restart: always or restart: unless-stopped in your compose file tells Docker what to do when a container exits. It says nothing about whether Docker itself starts when the server boots.
The full chain that must work for containers to survive a reboot:
- Server boots
- systemd starts the Docker daemon (
docker.service) - Docker daemon reads the restart policy on each known container
- Containers with
alwaysorunless-stoppedare started
If step 2 fails – if the Docker daemon is not enabled as a systemd service — step 3 and 4 never happen. Your restart policy is irrelevant. The containers sit as Exited until someone manually runs docker compose up.
Check whether the Docker daemon is enabled at boot:
systemctl is-enabled docker # Returns: enabled → daemon will start on boot # Returns: disabled → daemon will NOT start on boot — this is your problem
Also check whether Docker is currently running:
systemctl status docker
If the daemon is disabled, your containers will always need a manual start after any reboot — whether it’s a planned maintenance window, a kernel update, a power cut, or a cloud provider host migration. One systemctl enable fixes all of those at once.
How to Enable Docker to Start on Boot (systemctl)?
This is a one-command fix on any Linux system using systemd (Ubuntu 18.04+, Debian, CentOS 7+, RHEL, Fedora, and most modern distributions):
sudo systemctl enable docker
That’s it. This creates a symlink that tells systemd to start docker.service at boot. Confirm it worked:
systemctl is-enabled docker # Should now return: enabled
Then confirm that your containers have the right restart policy set. Check any running or stopped container with:
docker inspect <container_name> --format='{{.HostConfig.RestartPolicy.Name}}'
# Should return: always or unless-stopped
If the policy shows no (the default), update it without recreating the container:
docker update --restart unless-stopped <container_name>
For a Docker Compose stack, set the policy in the compose file and recreate the container once — after that, the policy persists through reboots as long as the daemon is enabled:
services:
n8n:
image: n8nio/n8n:latest
restart: unless-stopped # survives reboots, respects manual docker stop
...
✅ Test it without rebooting
You don’t need to reboot the server to test this. Restart just the Docker daemon and verify your containers come back up automatically: sudo systemctl restart docker then docker ps. If your containers are running within a few seconds, the reboot behaviour will be identical.
Reading Restart Loop Logs to Find the Real Crash
If Docker is running and your container status shows Restarting (N) X seconds ago, the daemon is functioning — your application is crashing. The restart policy is doing exactly what it was told: restart the container. The problem is inside the container, and the answer is in the logs.
The challenge with crash loops is timing. By the time you run docker logs, the container may have already cycled several times, and the most recent logs are from the latest (also failed) start. Look for the first failure, not the most recent one:
# Show the last 50 lines — usually enough to see the crash
docker logs --tail 50 <container_name>
# Show logs with timestamps to distinguish crash cycles
docker logs --timestamps <container_name> 2>&1 | tail -80
# Get the exit code from the last run
docker inspect <container_name> --format='{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}'
What the log patterns tell you:
| Log pattern | What it means | Fix |
|---|---|---|
| Same error repeated every 5–30 seconds, timestamps incrementing | Classic crash loop — application fails immediately on every start | Fix the underlying application error, not the restart policy |
Connection refused or ECONNREFUSED to a DB or cache |
Service dependency race condition — app starts before Postgres/Redis is ready | Add depends_on: condition: service_healthy with a healthcheck on the dependency |
permission denied on a volume path |
Host directory ownership mismatch — container user can’t write to the mounted path | chown -R <uid>:<gid> /host/path matching the container’s user |
OOMKilled: true in inspect output |
Container exceeded memory limit and was killed by the kernel | Raise the memory limit or remove it – see the exit code 137 section |
| Logs are empty no output at all | Container is exiting before it writes anything – usually a missing binary, wrong entrypoint path, or exit code 126/127 | Override the entrypoint: docker run -it --entrypoint sh <image> and inspect manually |
If the logs are cycling so fast you can’t read them, temporarily set the restart policy to no to freeze the container in its failed state and read the logs at rest:
# Stop the crash loop so you can read logs without racing the restarts docker update --restart no <container_name> docker stop <container_name> docker start <container_name> docker logs <container_name>
The on-failure:N Pattern That Stops Infinite Crash Loops From Filling Your Disk
A container stuck in an infinite crash loop with restart: always doesn’t just sit there looking bad — it actively causes problems. Each restart cycle writes to the container’s log. Each failed start attempt can write partial data to volumes. If the crash involves a database write, corrupted data accumulates. Within hours, a crash loop can fill a disk or exhaust inode allocations on a small server.
restart: on-failure:N is the circuit breaker. It restarts the container on non-zero exit codes, but gives up after N attempts. Docker stops retrying and leaves the container in a stopped state where you can actually examine what went wrong.
services:
worker:
image: myapp-worker:latest
restart: on-failure:5 # try 5 times, then stop and wait for human intervention
After 5 failed restarts Docker backs off. The container sits as Exited. You get paged (if you have monitoring), investigate, fix the root cause, then start it again manually.
The tradeoff matrix between policies:
| Policy | Restarts after reboot? | Restarts after crash? | Stops after N failures? | Respects manual stop? |
|---|---|---|---|---|
no |
No | No | — | Yes |
always |
Yes | Yes | No | No — restarts even after manual stop |
unless-stopped |
Yes (if was running at reboot) | Yes | No | Yes |
on-failure:N |
Yes (if was running at reboot) | Yes, up to N times | Yes | Yes |
One critical detail about always that catches people out during maintenance: if you run docker stop <container> on a service with restart: always, Docker restarts it anyway after the daemon restarts — including after a reboot. You stopped the container intentionally, came back after a reboot, and it’s running again. For services where you want planned maintenance to stick across reboots, unless-stopped is the correct policy, not always.
Running n8n? The Correct Restart Policy and Why always vs unless-stopped Matters for Planned Maintenance
n8n in production should always have a restart policy — an n8n instance with no restart policy that goes down at 3am on a weekend stays down until someone manually intervenes. But the choice between always and unless-stopped has a practical consequence most guides don’t explain.
Use unless-stopped, not always, for n8n in production.
Here’s the scenario where always causes problems: you need to update n8n’s image, or change an environment variable, or migrate the database. You run docker compose down to take everything offline cleanly. Your change takes 10 minutes. During that time the server clock rolls into the next hour and a scheduled kernel maintenance task reboots the host (or your cloud provider does a live migration). When the server comes back, every container with restart: always starts up immediately — including n8n, against the old image, against the half-migrated database. With unless-stopped, Docker remembers you stopped the containers intentionally and leaves them stopped.
The correct minimal production n8n compose configuration for reboot survival:
services:
n8n:
image: n8nio/n8n:latest
restart: unless-stopped # survives reboots, respects manual docker stop
env_file:
- .env
depends_on:
postgres:
condition: service_healthy
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
postgres:
image: postgres:15
restart: unless-stopped
env_file:
- .env
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 10
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
n8n_data:
postgres_data:
Two things this configuration does that matter specifically for reboots: the depends_on: condition: service_healthy block prevents n8n from starting until Postgres is fully ready — which eliminates the most common n8n crash loop cause after a reboot (Postgres takes longer to initialize than n8n takes to start). And unless-stopped on both services means a planned docker compose down for maintenance stays down across a reboot, not just until the next systemd restart of the Docker daemon.
✅ Verify both layers before the next reboot
Run these two checks to confirm your n8n stack is fully protected: systemctl is-enabled docker (should return enabled) and docker inspect n8n --format='{{.HostConfig.RestartPolicy.Name}}' (should return unless-stopped). Both must pass. One without the other leaves you with a gap.
Quick Diagnosis: “Which Problem Is This?”
🔍 Start: Run systemctl is-active docker
Returns: inactive or failed
→ Docker daemon did not start after the reboot
→ Fix: sudo systemctl enable docker && sudo systemctl start docker
→ Then verify restart policies: docker inspect <name> --format='{{.HostConfig.RestartPolicy.Name}}'
→ If policy is no: docker update --restart unless-stopped <name>
Returns: active
→ Docker is running — go to the container level
→ Run docker ps -a and check STATUS
STATUS: Exited (N) — not restarting
→ Restart policy is no or the container was manually stopped before the reboot
→ Fix: set restart: unless-stopped in compose and run docker compose up -d
STATUS: Restarting (N) X seconds ago
→ Crash loop — application is crashing repeatedly
→ Run docker update --restart no <name> to freeze it
→ Run docker logs <name> and read the actual error
→ Fix the application error, not the restart policy
Conclusion
Containers not surviving a reboot and containers stuck in a restart loop look identical from the outside — both show up as “containers not running.” But they require completely different fixes. Enabling the Docker daemon and setting a restart policy are both necessary for reboot survival. Neither one does anything about an application crash loop.
- Run
systemctl is-enabled dockerfirst. If it returnsdisabled, that is the entire problem — enable it and the containers will start themselves. - Set
restart: unless-stoppedin your compose file, notalways. The difference matters the moment you need planned maintenance that stays offline across a reboot. - If you have a crash loop, freeze it with
docker update --restart noand read the logs without racing against the restart cycle. - Use
restart: on-failure:5for worker processes and batch jobs — anything where an infinite crash loop can fill a disk or corrupt state should have a hard stop after N attempts. - For n8n with Postgres, the crash loop after reboot is almost always a startup race condition. The
depends_on: condition: service_healthypattern eliminates it permanently.
Both fixes together — daemon enabled at boot, unless-stopped policy on all services – mean your Docker stack survives every reboot: planned, unplanned, and everything in between.


