Docker Compose Restart Policies Explained
The power blipped, the host rebooted, and half the homelab stack came back up on its own — the other half just sat there, exited. Every container had a restart: line; they weren't all the same line.

The power blipped, the host rebooted, and half the homelab stack came back up on its own — the other half just sat there, exited, waiting for a docker compose up that never came. Every container in that stack had a restart: line. They weren't all the same line, and that's exactly the problem this article solves.
TL;DR
- Four values exist:
no(default, never restarts),always,unless-stopped,on-failure[:N]. alwaysrestarts a container even after you manually stopped it, once the Docker daemon itself restarts — that surprises people.unless-stoppedis the safer default for almost everything: survives crashes and reboots, respects an intentional stop.on-failureonly retries on a non-zero exit code — the right choice for one-shot jobs, not long-running services.- The policy that matters is the one attached to each service, not one global default for the whole compose file.
KEY-STAT: 4 — Restart policy values in Docker Compose, and only one of them behaves the way most people assume "always" does
The four restart policies, in one table
| Policy | Restarts on crash | Restarts after host/daemon reboot | Restarts after a manual docker compose stop |
Typical use |
|---|---|---|---|---|
no (default) |
No | No | — (already stopped) | Anything you want to start and stop by hand |
always |
Yes | Yes | Yes, once the daemon restarts | Services that must never stay down |
unless-stopped |
Yes | Yes | No — stays stopped | Most long-running self-hosted services |
on-failure[:N] |
Only on non-zero exit | No (unless it was mid-retry) | — | One-shot jobs, migrations, backup runs |
Docker's own restart-policy documentation covers the mechanics; what it doesn't do is tell you which one to put in front of Jellyfin versus a one-off backup container. That decision is the actual point of this article.
no — the default nobody notices until reboot day
Omit restart: entirely, or write restart: "no", and Compose does exactly nothing when the container exits or the host reboots. This is fine for a container you start deliberately and expect to babysit. It is not fine for a service you assume is "just running" — as covered in our beginner's guide to building a homelab, the single most common first-timer surprise is a service that survived weeks of uptime purely because the host never rebooted, then vanished after the first kernel update.
always — restarts even after you stopped it yourself
restart: always restarts the container on crash, on host reboot, and — the part people miss — on Docker daemon restart, even if you manually ran docker stop on it before the daemon restarted. Docker doesn't remember that you stopped it on purpose; it only remembers the policy. On this site's own homelab, Jellyfin carries this policy because a media server sitting dead after a power blip is the kind of failure a household actually notices within minutes:
services:
jellyfin:
image: jellyfin/jellyfin:latest
restart: always
ports:
- "8096:8096"
volumes:
- jellyfin_config:/config
- jellyfin_media:/media
unless-stopped — the safer default for almost everything
unless-stopped behaves identically to always for crashes and reboots, with one difference that matters in daily operation: if you manually stop the container, it stays stopped even after the Docker daemon restarts. That single distinction is why unless-stopped, not always, is the sensible default for most self-hosted services — you get resilience without losing the ability to intentionally take something offline for maintenance. Both Vaultwarden and n8n run this policy in the same stack:
services:
vaultwarden:
image: vaultwarden/server:latest
restart: unless-stopped
volumes:
- vw_data:/data
n8n:
image: n8nio/n8n:latest
restart: unless-stopped
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
Full setup and hardware notes for those two services are in the Vaultwarden self-hosting guide and the n8n free-tier breakdown.
Tip
If you're not sure which policy to pick, unless-stopped is the right default 80% of the time. Reach for always only when "restarted even after I stopped it" is a feature, not a surprise.
on-failure[:N] — for jobs, not services
on-failure only triggers a restart when the container exits with a non-zero code — a clean exit 0 is treated as success and Compose leaves it stopped. It's the right policy for anything that should run once and finish, not sit listening on a port. Add a retry cap with on-failure:3 so a genuinely broken job doesn't loop forever:
services:
restic-backup:
image: restic/restic:latest
restart: on-failure:3
command: backup /data --repo /backup-repo
volumes:
- app_data:/data:ro
- backup_repo:/backup-repo
Warning
Never put restart: always on a one-shot job container. A backup script that exits 0 on success will be restarted anyway under always, silently re-running the job in a loop — the container doesn't know the difference between "finished" and "crashed," only the policy does.
Decision guide: which policy for which kind of service
| Service type | Recommended policy | Why |
|---|---|---|
| Media server (Jellyfin, Plex) | always |
Downtime is immediately visible to everyone in the household |
| Credential/vault manager (Vaultwarden) | unless-stopped |
Must survive reboots, but you sometimes stop it deliberately for backups |
| Automation/workflow engine (n8n) | unless-stopped |
Same reasoning — resilient by default, stoppable on purpose |
| Reverse proxy | unless-stopped |
Fronts everything else; should come back on its own, but shouldn't fight a deliberate maintenance stop |
| Database (Postgres, MariaDB) | unless-stopped |
Needs to survive reboot; avoid always so a deliberate stop before a disk operation actually holds |
| One-shot job (backup, migration, cron-style task) | on-failure:3 |
Should run to completion and stay stopped, retrying only on real failure |
The reboot test: how to actually verify your policies work
Reading a compose file doesn't prove the policy works — a reboot does. After editing restart: values, run sudo reboot, wait for the host to come back, then check:
docker compose ps
Every service you set to always or unless-stopped should show Up. Anything showing Exited either has no restart policy, was stopped intentionally before the reboot, or — the case that catches people out — Docker itself isn't enabled to start on boot at the systemd level, in which case no container restart policy fires at all, as explained in the Docker vs. virtual machines comparison. Confirm the daemon starts on boot with systemctl is-enabled docker before trusting any restart policy to save you.
Anyone still on plain docker run instead of Compose gets the same four values via the --restart flag; the beginner's guide to your first container covers the command-line equivalent.
The bottom line
Pick unless-stopped as the house default for anything long-running, reserve always for the handful of services where "restart even after a manual stop" is genuinely wanted, and keep on-failure strictly for jobs that finish. Then prove it with a real reboot, not a read-through of the compose file.
Frequently asked questions
What is the default Docker restart policy if you don't set one?
no. The container will not restart automatically on crash, on daemon restart, or on host reboot — you have to start it manually every time.
Does docker compose restart: always restart on a host reboot?
Yes. always restarts the container on crash, on Docker daemon restart, and on host reboot — including cases where you had manually stopped the container before the reboot happened.
What is the difference between always and unless-stopped in Docker?
Both survive crashes and reboots. The difference is a manual stop: always will bring the container back once the daemon restarts even after you stopped it yourself; unless-stopped respects that manual stop and stays down.
How do you limit restart attempts with on-failure?
Add a retry count after a colon, e.g. restart: on-failure:3. Docker stops retrying after three failed attempts instead of retrying indefinitely.
More from Self-Hosting & Privacy

Two containers on the same host could reach each other, and neither one was supposed to. Both had landed on Docker Compose's default bridge network — a network that doesn't ask permission before connecting things.

I ran docker compose down -v to force a clean restart, out of muscle memory from a different project where that flag was harmless. It wasn't harmless here — it deleted a named volume along with the containers.

Gitea and Forgejo run on well under 1GB of RAM; GitLab CE documents a 4GB minimum. This comparison covers real resource footprint, CI runner maturity (Gitea/Forgejo Actions vs GitLab CI), and GitHub migration friction for homelab hardware.
Stay in the loop
Get the latest articles delivered to your inbox. No spam, unsubscribe anytime.
Docker Compose Networking Explained
Two containers on the same host could reach each other, and neither one was supposed to. Both had landed on Docker Compose's default bridge network — a network that doesn't ask permission before connecting things.
Continue Reading