Your server reboots after a kernel update, Docker comes back up, but the containers you had running yesterday aren't running anymore. By default, when the Docker daemon stops, containers stop, and nothing brings them back unless you've told Docker to do so.
For containers to survive a reboot, the Docker daemon must start at boot and each container must have a restart policy that survives a daemon restart. This article covers both, plus the case where you'd reach for a systemd unit file instead of the built-in restart policy. If you're looking for how to restart a running container rather than how to make it survive a reboot, see how to restart a Docker container instead.
The fix
For a single container, two commands cover the whole problem. First make sure the Docker daemon is enabled at boot, then start the container with a restart policy:
12sudo systemctl enable docker.service containerd.servicedocker run -d --restart unless-stopped --name web nginx:latest
On Debian and Ubuntu, the Docker package enables the daemon automatically, but it doesn't hurt to run the enable command anyway since it's idempotent. On other distributions, including RHEL, Fedora, and Arch, you have to do this yourself; see the Docker Linux post-installation guide for details. Forgetting it is the single most common reason "my restart policy doesn't work after a reboot."
Verify the daemon is enabled at boot:
1systemctl is-enabled docker.service
1enabled
Then check the container's restart policy:
1docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' web
1unless-stopped
Reboot the server, and docker ps should show the container running.
Choosing the right restart policy
Docker provides four restart policies:
no(default). The container never restarts after exiting, for any reason. Use this for one-shot containers like migrations, debug shells, and batch jobs.on-failure[:max-retries]. Restarts only when the container exits with a non-zero exit code. A cleanexit 0does not trigger a restart. The optional:max-retriessuffix (e.g.on-failure:5) caps the number of attempts. Without it, a container with a persistent config error will restart forever.always. Restarts on any exit and after Docker daemon restarts, even if the container was manually stopped before the daemon restart. This last part is surprising: if youdocker stopanalwayscontainer and then reboot the host, the container starts again.unless-stopped. Restarts on any exit and after Docker daemon restarts, but does not restart if the container was manually stopped before the daemon restart. This is the policy that behaves like a traditional service manager.
For almost every long-running service, unless-stopped is the right default. always makes sense when the container has no legitimate reason to ever be down, like a foundational database or a system-wide proxy. on-failure:N is the right choice for batch jobs that should retry a small number of times before failing visibly.
Updating existing containers
If you've already created a container without a restart policy, you don't need to recreate it. Change the policy in place:
1docker update --restart unless-stopped web
This works for always, on-failure:N, no, and unless-stopped. Worth knowing because rebuilding a container can mean losing in-container state, breaking links, or interrupting connections that you'd rather not.
Docker Compose
For Compose stacks, set the restart directive on each service:
12345678910111213141516171819202122232425services:web:image: nginx:alpinerestart: unless-stoppedports:- "80:80"api:build: ./apirestart: unless-stoppeddepends_on:- postgrespostgres:image: postgres:18restart: alwaysvolumes:- pgdata:/var/lib/postgresql/datamigrator:build: ./migrationsrestart: on-failure:3volumes:pgdata:
The values are identical to the CLI flags. One nuance: docker compose down removes the containers entirely, so the restart policy is irrelevant since the containers no longer exist. If you want to stop containers while preserving them, use docker compose stop, and bring them back with docker compose start.
When to use a systemd unit file instead
Docker's restart policies are enough for most setups. You'll reach for a systemd unit file when you need things the daemon's policies don't give you:
- Dependency ordering on resources outside Docker, like an NFS mount or a VPN tunnel that must come up before the container.
- Strict ordering between containers that must start in a specific sequence. Docker brings up all containers in parallel after a reboot.
- Unified logging through journald, so container logs end up alongside your other service logs.
- Graceful shutdown coordination when the host is being shut down rather than just rebooted.
A minimal unit file at /etc/systemd/system/myapp.service looks like this:
123456789101112[Unit]Description=MyApp ContainerRequires=docker.serviceAfter=docker.service[Service]Restart=alwaysExecStart=/usr/bin/docker start -a myappExecStop=/usr/bin/docker stop -t 10 myapp[Install]WantedBy=multi-user.target
The -a flag attaches docker start to STDOUT and STDERR so systemd can capture logs and signal handling works correctly. The -t 10 gives the container 10 seconds to shut down gracefully before getting killed. The container itself should be created with docker create (not docker run) so it exists but isn't started, and the unit file's ExecStart brings it up.
Enable and start the unit:
123sudo systemctl daemon-reloadsudo systemctl enable myapp.servicesudo systemctl start myapp.service
Don't combine a Docker restart policy with a systemd unit file. Both will try to restart the container and you'll get confusing behavior where the container restarts in ways neither layer fully explains. Set the container's restart policy to no when it's managed by systemd.
Common pitfalls
The 10-second rule. A restart policy only takes effect after the container has been running successfully for at least 10 seconds. If your container crashes in the first 10 seconds because it can't find a config file or fails to bind a port, Docker will not restart it. This is intentional behavior, since it prevents broken containers from spinning in a crash-restart loop and consuming the host. The symptom looks like "my restart policy isn't working." Check the logs for a crash in those first 10 seconds before assuming the policy is broken. If your container takes longer than 10 seconds to be genuinely ready, configure a Docker health check rather than relying on the restart policy alone.
always after docker stop. If you docker stop a container running with --restart always and then reboot the host or restart the Docker daemon, the container comes back up. The always policy ignores the fact that you manually stopped it. If you want a container to stay stopped across reboots, use unless-stopped.
Manual stop suspends the policy. When you run docker stop on a container with any restart policy, that policy is ignored for the current daemon session. So a manually stopped unless-stopped container won't come back until either the daemon restarts (and it stays stopped, per unless-stopped semantics) or you docker start it explicitly. This is mostly what you want, but it catches people who expect the policy to immediately restart the container after a stop.
on-failure without max-retries. A bare on-failure retries forever. If you have a container that fails because of a missing environment variable, it will keep restarting and filling logs and metrics with noise. Cap it with on-failure:5 or similar for any batch job.
Forgetting systemctl enable docker. On distributions that don't enable Docker by default, systemctl is-enabled docker will print disabled. The first reboot won't bring Docker up, and your restart policies become irrelevant because the daemon isn't running. This is the gotcha that produces hours of debugging because everything looks right when you check the container config.
Restart policies don't apply to Swarm services. If you're running Docker Swarm, the --restart flag has no effect on services. Use --restart-condition and --restart-max-attempts on docker service create instead.
Final thoughts
Restart policies recover containers automatically, but they don't tell you why a container needed to restart. A container restarting every four minutes for the last hour is technically "up," but something is clearly broken. A restart loop that catches itself is good. A restart loop you don't know about is technical debt.
Dash0's infrastructure monitoring tracks container restarts alongside resource usage and live container logs, so a flapping container shows up as an alert rather than as a midnight outage. The same view ties container restarts back to the request traces and logs from the workload that crashed, so you can move from "the container restarted" to "this is why" without leaving the same screen.
Start a free trial to see your container health, logs, and traces in one view. No credit card required.