## Intro

Docker restart policies are a core piece of container reliability, but they are also a common source of confusion. Setting the wrong policy can leave a production service down after a reboot, mask application bugs by restarting a failing container indefinitely, or cause a container to restart at the worst possible moment during maintenance. This guide explains how Docker restart policies actually work, walks through common configuration mistakes with concrete examples, and shows you how to validate, roll back, and troubleshoot restart behavior safely.

We'll cover the four policy types, what happens in different failure scenarios (daemon restarts, manual stops, exit codes), and how to choose correctly. You'll see real commands and expected output for inspecting and changing policies, plus a practical testing workflow you can run locally before deploying to production.

## Version and Environment Inventory

Before changing any restart policy, confirm your environment and capture the current state. This prevents making changes to the wrong container or assuming behavior from a different Docker version.

### Check your Docker version and runtime

Restart policies have been stable for years, but syntax and defaults can vary slightly across Docker Engine, Docker Desktop, and rootless installations. Run:

```bash
docker version --format '{{.Server.Version}}'
```

Expected output example:

```
24.0.7
```

Also check the container runtime (e.g., containerd) and storage driver if you are troubleshooting filesystem persistence after restarts:

```bash
docker info --format '{{.Driver}} {{.CgroupVersion}}'
```

### Capture current restart policies

List all containers and their restart policies using a custom format:

```bash
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Label "com.docker.compose.project"}}'
```

To see the exact restart policy of a specific container, use `docker inspect`:

```bash
docker inspect --format '{{.HostConfig.RestartPolicy.Name}} (max retries: {{.HostConfig.RestartPolicy.MaximumRetryCount}})' my-app
```

Example output:

```
always (max retries: 0)
```

For Compose projects, the policy is defined in `docker-compose.yml` under the `restart:` key. View all services and their current container state with:

```bash
docker compose ps
```

### Confirm data persistence before restart tests

If your container writes stateful data, verify that the data lives in a volume or bind mount, not in the container's writable layer. Otherwise, a restart may lose data.

Inspect mounts for a running container:

```bash
docker inspect --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' my-app
```

Example output:

```
volume app_data -> /var/lib/app
```

If you see no mounts and the application writes to `/var/lib/app` inside the container, that data will be lost when the container is removed (but not when it merely restarts). Always design stateful containers to use volumes or bind mounts.

## Understanding Restart Policies

Docker provides four restart policy values:

| Policy          | Behavior when container exits | Behavior on daemon restart | Typical use case |
|-----------------|-------------------------------|----------------------------|------------------|
| `no`            | Never restart automatically   | Never restarted            | One-off jobs, debugging |
| `on-failure`    | Restart only if exit code != 0, up to max retries | Restarted only if last exit was failure | Services that should stop on repeated failure |
| `always`        | Always restart regardless of exit code | Always restarted, even if manually stopped before daemon restart | Core production services (web, API) |
| `unless-stopped`| Always restart unless the container was manually stopped by user | Restarted unless user had stopped it before daemon restart | Most production services; safer than `always` |

Key differences:

- `always` will restart a container even if you manually stopped it before the Docker daemon restarts. This can surprise you during maintenance windows.
- `unless-stopped` remembers that you manually stopped the container and will not restart it after a daemon restart. It is the recommended choice for most production workloads.
- `on-failure` allows a maximum retry count. Once exceeded, the container stops. This helps avoid infinite restart loops for broken applications.

### What triggers a restart?

A restart policy kicks in when the container's main process exits. Docker does not restart a container that is running but unhealthy (unless you use a health check with autoheal, which is separate). The restart policy also does not apply if the container is removed or if the daemon is stopped while the container is still running (the container is stopped normally and then restarted according to policy when daemon starts).

### Default policies

- `docker run` without `--restart` defaults to `no`.
- Docker Compose defaults to `no` (unless overridden).
- Some orchestrators (e.g., Kubernetes) handle restarts themselves; this article focuses on plain Docker and Compose.

## Safe Configuration Path

Now that you understand the policies, follow a controlled process to change one.

### Step 1: Identify the target and current policy

Choose a single container and record its current policy using `docker inspect` as shown earlier. For Compose, note the service name and the project directory.

### Step 2: Choose the new policy with justification

Ask:

- Is this a long-running service that should always be up? Use `unless-stopped` (preferred) or `always`.
- Is it a batch job or a short-lived worker? Use `no` or `on-failure` with a small retry count.
- Do I need to cap restart attempts to avoid crash loops? Use `on-failure:5` (retry 5 times, then stop).

Example: a web server should restart unless an operator deliberately stopped it for maintenance. So:

```yaml
services:
  web:
    image: nginx:1.25
    restart: unless-stopped
```

### Step 3: Apply the change

For a running container, you cannot change the restart policy via `docker update`. Actually, `docker update` does support `--restart` since Docker 1.11. So you can update a running container without recreating:

```bash
docker update --restart unless-stopped my-app
```

For a container created with `docker run`, specify the policy at creation:

```bash
docker run -d --name my-app --restart unless-stopped nginx:1.25
```

For Compose, edit `docker-compose.yml` and run:

```bash
docker compose up -d
```

This recreates the service with the new policy if the configuration changed.

### Step 4: Verify the new policy

Immediately check that the policy is set correctly:

```bash
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' my-app
```

Expected output: `unless-stopped`.

### Step 5: Run a controlled restart test

Do not just trust the setting; simulate the failure condition. For `unless-stopped`, stop the container manually and confirm it does not restart on its own:

```bash
docker stop my-app
sleep 5
docker ps -a --filter name=my-app --format '{{.Status}}'
```

Expected output shows the container as `Exited (0)` and not running. That is correct for a manual stop with `unless-stopped`. To test automatic restart on failure, kill the main process with a nonzero exit code:

```bash
docker kill --signal=SIGKILL my-app
sleep 2
docker ps --filter name=my-app --format '{{.Status}}'
```

You should see the container `Restarting` or `Up` (with a new start time).

### Rollback plan

If the new policy causes unexpected behavior (e.g., a restart loop), revert to the previous policy immediately:

- For `docker run` containers, use `docker update --restart <old-policy> my-app`.
- For Compose, change `restart:` in the YAML and run `docker compose up -d` again.
- If the container is stuck in a restart loop, you may need to stop it first: `docker stop my-app` (this works even with restart policy unless it's `always` and the daemon restarts? Actually stopping a container overrides restart policy until daemon restart for `always`; for `unless-stopped` it persists).

## Verification and Diagnostics

After changing a restart policy, verify both the configuration and the actual behavior under failure.

### Inspect restart count and last exit

Use `docker inspect` to see how many times the container has restarted:

```bash
docker inspect --format '{{.RestartCount}}' my-app
```

If the count is climbing rapidly, the container is crash-looping. Investigate the exit code:

```bash
docker inspect --format '{{.State.ExitCode}}' my-app
```

Exit code 0 means clean exit; nonzero indicates error. For `on-failure`, a high retry count may exhaust the maximum and stop the container permanently.

### Check logs for the cause of failure

```bash
docker logs my-app --tail 100
```

Look for stack traces, missing files, or port conflicts. If the application needs a health check, combine restart policy with a Docker healthcheck to avoid restarting a container that is running but unhealthy:

```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost/ || exit 1
```

Then use `unless-stopped` so Docker restarts only on process failure, not on health check failure (unless you use an orchestrator feature).

### Test daemon restart behavior

A common mistake is assuming a container with `always` will start after a host reboot. Always test by restarting the Docker daemon or the host in a staging environment.

On a Linux server with systemd:

```bash
sudo systemctl restart docker
sleep 10
docker ps --filter name=my-app
```

If the container does not appear, check `docker ps -a` to see if it is stopped. For `unless-stopped`, if you had manually stopped it before the daemon restart, it will remain stopped. That is expected.

## Failure Modes and Recovery

Several failure modes arise from restart policy misconfiguration. Here are common ones and how to recover.

### 1. Setting `always` when `unless-stopped` is intended

**Symptom:** You stop a container for maintenance, reboot the server, and the container starts again unexpectedly. This can cause split-brain or resource contention if the service should remain down.

**Why:** `always` ignores manual stops across daemon restarts. Docker assumes you want the container running no matter what.

**Recovery:** Change policy to `unless-stopped`:

```bash
docker update --restart unless-stopped my-app
```

Then if you need it stopped, stop it before reboot; it will stay stopped.

### 2. Using `on-failure` without a limit on a broken app

**Symptom:** Container constantly restarts, exhausting CPU and filling logs.

**Why:** `on-failure` without `MaximumRetryCount` (or `on-failure:0`) means unlimited retries. The app may exit with code 1 repeatedly.

**Recovery:** Set a finite retry count, e.g., 5:

```bash
docker update --restart on-failure:5 my-app
```

Then diagnose the root cause by checking logs while the container is stopped.

### 3. Forgetting to set a restart policy at all

**Symptom:** After a host reboot, your service is down.

**Why:** Default policy is `no`. The container never restarts automatically.

**Recovery:** Add `--restart unless-stopped` (or `always`) to `docker run`, or `restart: unless-stopped` in Compose. Recreate the container.

### 4. Applying a restart policy to a one-off job

**Symptom:** A data migration or batch job runs repeatedly after completion because the container exits with code 0 and the policy is `always` or `unless-stopped`.

**Why:** These policies restart regardless of exit code. A job that finishes successfully should not be restarted.

**Recovery:** Use `--restart no` or `on-failure` with retries for jobs. If already running, stop and remove the container, then run a new one with the correct policy.

### 5. Misunderstanding `on-failure` and exit code 0

**Symptom:** You expect `on-failure` to restart when the app crashes, but it does not restart when the app exits with code 0 after a fatal error (some apps exit 0 even on failure).

**Why:** `on-failure` only triggers on nonzero exit codes. If the app interprets a crash as a clean shutdown (exit 0), Docker will not restart it.

**Recovery:** Fix the application to exit nonzero on fatal errors, or use a wrapper script that maps errors to exit 1. Then use `on-failure` or `unless-stopped`.

### 6. Restart policy conflicts with Compose `depends_on`

**Symptom:** In Compose, service A (with `restart: always`) restarts continuously, causing service B (which depends on A) to also restart because `depends_on` makes B recreate when A restarts. This creates a cascade.

**Why:** Compose's default `depends_on` condition is `service_started`, not `service_healthy`. If A crashes, B may start fine but A restarts, and Compose may restart both.

**Recovery:** Use `depends_on` with `condition: service_healthy` and define healthchecks. Limit restart policy on A to `on-failure` with a finite retry count to prevent infinite loops.

### 7. Losing data because of volume misconfiguration

**Symptom:** Container restarts fine but data is missing after a restart (e.g., database files gone).

**Why:** The data was written to the container's writable layer instead of a volume. A simple restart preserves the writable layer, but if the container is recreated (e.g., by Compose `up` after a policy change), all data is lost.

**Recovery:** Redesign with a volume or bind mount. If data is critical, recover from backups. Test persistence by recreating the container deliberately before production.

## Operations Checklist

Use this checklist before and after modifying restart policies in production. Assign a single owner for the change and a review date.

- [ ] **Owner:** Priya Shah, Engineering Lead. **Review date:** after every change and quarterly audit.
- [ ] Confirm Docker version and runtime are known (`docker version`, `docker info`).
- [ ] Capture current restart policy of target container: `docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' <container>`.
- [ ] Check if container has volumes or bind mounts and that data is persisted outside the container.
- [ ] Choose new policy with documented justification. Example: change `no` to `unless-stopped` for web service because it must survive host reboots unless deliberately stopped.
- [ ] Apply change via `docker update --restart` or Compose file edit + `docker compose up -d`.
- [ ] Verify new policy with `docker inspect`.
- [ ] Run controlled restart tests:
  - Manual stop: container should remain stopped for `unless-stopped`; should restart for `always` only after daemon restart? Actually, manual stop overrides `always` until daemon restart. Test daemon restart separately.
  - Simulate failure: `docker kill` the main process and confirm restart.
  - Check restart count: `docker inspect --format '{{.RestartCount}}'` should increment.
- [ ] Check logs for error messages: `docker logs --tail 100`.
- [ ] Confirm rollback path: know how to revert policy quickly.
- [ ] Document the change in the runbook with timestamp and owner.
- [ ] Schedule a quarterly review of all container restart policies to ensure they still match service requirements.

## Common Pitfalls and How to Avoid Them

Beyond the failure modes above, these pitfalls occur frequently:

1. **Assuming `always` is safer than `unless-stopped`.** In reality, `always` can cause containers to start after a reboot even when you intended to keep them down for maintenance. Use `unless-stopped` for most services.

2. **Not testing daemon restart behavior.** Many developers only test killing the container process, not rebooting the Docker daemon. Always test both in a staging environment.

3. **Ignoring exit codes.** An application that exits 0 on fatal error defeats `on-failure`. Ensure your app follows Unix conventions: nonzero for errors.

4. **Using `on-failure` with too many retries.** If the app fails permanently, a high retry count delays detection. Use a low number (3-5) and monitor logs.

5. **Changing policy without checking data persistence.** If you recreate a container to apply a policy change (e.g., Compose `up` recreates), data in the container's writable layer is lost. Always use volumes for stateful services.

6. **Not documenting the policy choice.** Teams may not know why a policy was set, leading to accidental changes. Add comments in Compose files:

```yaml
services:
  db:
    image: postgres:16
    restart: unless-stopped  # stay up unless manually stopped for maintenance
```

## Conclusion

Docker restart policies are simple in concept but easy to get wrong in production. By understanding the exact behavior of `no`, `on-failure`, `always`, and `unless-stopped`, and by following a careful change process with verification and rollback, you can ensure your containers are available when they should be and down when you need them down.

Start with a low-risk container in a staging environment. Capture its current policy, apply a change, run the restart tests described here, and document the results. Then extend the process to production. Regularly audit your restart policies as part of operational hygiene, and you will avoid the most common pitfalls that lead to unexpected downtime or uncontrolled restarts.