Skip to main content
Self-Hosting & Privacy

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.

milanbuha00August 20, 20266 min read
ShareXin
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, 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].
  • always restarts a container even after you manually stopped it, once the Docker daemon itself restarts — that surprises people.
  • unless-stopped is the safer default for almost everything: survives crashes and reboots, respects an intentional stop.
  • on-failure only 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.

Related stories

More from Self-Hosting & Privacy

Stay in the loop

Get the latest articles delivered to your inbox. No spam, unsubscribe anytime.

Read next

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