Docker Container Keeps Restarting After Reboot – Two Problems, Two Fixes

Fix Docker Container Keeps Restarting After Reboot

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:

  1. Server boots
  2. systemd starts the Docker daemon (docker.service)
  3. Docker daemon reads the restart policy on each known container
  4. Containers with always or unless-stopped are 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.

  1. Run systemctl is-enabled docker first. If it returns disabled, that is the entire problem — enable it and the containers will start themselves.
  2. Set restart: unless-stopped in your compose file, not always. The difference matters the moment you need planned maintenance that stays offline across a reboot.
  3. If you have a crash loop, freeze it with docker update --restart no and read the logs without racing against the restart cycle.
  4. Use restart: on-failure:5 for 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.
  5. For n8n with Postgres, the crash loop after reboot is almost always a startup race condition. The depends_on: condition: service_healthy pattern 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.

Leave a Comment

Your email address will not be published. Required fields are marked *