← Back to DevBytes

Docker Restart Policies: Production Guide

Understanding Docker Restart Policies

Docker restart policies dictate how the Docker daemon handles container restarts when they exit or when the host system reboots. They are a critical piece of any production container strategy, ensuring that your services recover automatically from failures without manual intervention. Without a well-configured restart policy, a single crash or a routine server reboot can leave your application offline indefinitely.

In this guide, you will learn exactly what restart policies are, the four available modes, how to apply them using both the CLI and Docker Compose, and battle-tested best practices for production deployments.

What Are Docker Restart Policies?

When a container's main process exits, Docker can automatically decide whether to restart it based on the restart policy you've defined. This policy is enforced by the Docker daemon itself, not by an external scheduler. It applies to containers that exit and to containers that were running when the Docker daemon restarts (such as after a host reboot).

Restart policies are fundamentally different from process supervisors like systemd or supervisord inside the container. The restart policy lives at the orchestration layer—Docker itself watches the container and takes action. This means you should never combine an in-container init system with a Docker restart policy unless you have a very specific reason; doing so can mask failures and create confusing behavior.

Why Restart Policies Matter in Production

Production environments demand high availability. Applications crash for countless reasons: out-of-memory errors, unhandled exceptions, temporary resource exhaustion, or transient network glitches that cause a process to exit. A restart policy acts as your first line of defense, bringing the service back online in seconds without requiring an operator to SSH in and run docker start.

Beyond immediate crash recovery, restart policies also handle host-level events. When you apply kernel patches and reboot, or when a cloud VM is migrated and restarted, containers without a proper restart policy stay stopped. Containers with --restart always or --restart unless-stopped come back up automatically, minimizing downtime windows.

For stateful services like databases, restart policies work hand-in-hand with volume mounts and health checks. If PostgreSQL crashes, Docker restarts it, the data volume persists, and the service resumes where it left off. For stateless application servers behind a load balancer, restart policies ensure that capacity is restored immediately after an individual container failure, buying you time to investigate root causes.

The Four Restart Policies Explained

Docker offers four distinct restart policies. Each serves a different use case, and understanding the nuances is essential for production configuration.

1. no — No Automatic Restart

This is the default policy if none is specified. The container exits, and Docker does nothing. The container remains in the exited state until manually removed or restarted. This is appropriate for one-off administrative tasks, batch jobs that should run exactly once, or containers used during debugging.

# Explicitly setting 'no' (the default behavior)
docker run --restart no --name debug-container ubuntu:22.04 echo "Task complete"

After the echo command finishes, the container stops. Docker will not attempt to restart it. Use this for containers you intentionally want to run once and then inspect or discard.

2. always — Relentless Restart

With always, Docker restarts the container whenever it exits, regardless of the exit code. This includes exits with code 0 (success) and exits caused by docker stop when the daemon itself restarts. The only way to prevent a restart is to explicitly docker stop the container while the daemon is running, which sets an internal flag that suppresses the restart.

This policy is ideal for long-running services that must be available at all times: web servers, API gateways, message brokers, and load balancers.

# Run an nginx container that restarts no matter what
docker run -d --restart always --name nginx-prod -p 80:80 nginx:latest

If you issue docker stop nginx-prod, Docker stops the container and respects the manual stop—it won't restart until you explicitly run docker start nginx-prod or the daemon itself restarts (at which point the container starts again because of the always policy).

3. unless-stopped — Restart Unless Explicitly Stopped

This is similar to always but with a crucial difference: if you manually stop the container with docker stop, it stays stopped even after a daemon restart. The container will only restart if it exits unexpectedly (non-zero exit code or crash) or if the daemon restarts and the container was not in a manually-stopped state beforehand.

This policy is the sweet spot for most production workloads. It provides high availability while respecting operator intent. If an admin deliberately stops a container for maintenance, it stays stopped until explicitly started again.

# Run a production API with unless-stopped
docker run -d --restart unless-stopped \
  --name api-service \
  -p 3000:3000 \
  my-api-image:1.2.3

In practice, if the API crashes due to an unhandled exception, Docker restarts it. If an operator runs docker stop api-service to perform a rolling upgrade, the container remains stopped even after the host reboots. This prevents surprise container starts during maintenance windows.

4. on-failure — Restart Only on Non-Zero Exit Codes

The on-failure policy restarts the container only if it exits with a non-zero exit code, indicating an error or crash. It also supports an optional :max-retries parameter to limit the number of consecutive restart attempts. After the maximum retries are exhausted, Docker gives up and leaves the container in the exited state.

This policy shines for batch processing workers, CI/CD pipeline steps, or any workload where a zero exit code means "successful completion" and you don't want the container running again. It prevents infinite restart loops for containers that have finished their legitimate work.

# Restart up to 5 times on failure, then stop trying
docker run -d --restart on-failure:5 \
  --name batch-processor \
  batch-worker:latest

Without the :max-retries suffix, Docker retries indefinitely. Always specify a retry limit in production to avoid runaway resource consumption from a perpetually crashing container. A typical pattern is on-failure:3 or on-failure:5 for critical batch jobs, then relying on external monitoring to alert you when retries are exhausted.

How to Apply Restart Policies

Restart policies can be set at container creation time using the docker run CLI or declared declaratively in Docker Compose files. They cannot be changed after a container is created without removing and recreating the container. Plan your restart strategy before deploying.

Using the Docker CLI

The --restart flag accepts the policy name and optional parameters. Here are all valid forms:

# No restart
docker run --restart no ...

# Always restart
docker run --restart always ...

# Restart unless manually stopped
docker run --restart unless-stopped ...

# Restart only on non-zero exit, with retry limits
docker run --restart on-failure ...
docker run --restart on-failure:10 ...

You can inspect the current restart policy of any container, including running ones, using docker inspect:

# Check the restart policy of an existing container
docker inspect --format '{{ .HostConfig.RestartPolicy }}' nginx-prod

# Output example:
# { "Name": "always", "MaximumRetryCount": 0 }

The MaximumRetryCount field is only meaningful for on-failure policies; it will be zero for all other policy types.

Using Docker Compose

In a Compose file, the restart key is placed at the service level. The syntax mirrors the CLI but uses YAML structure. For on-failure with retries, you use a nested object with the max_attempts property (note: this is distinct from the CLI's colon syntax).

version: '3.8'

services:
  # Always-up web server
  web:
    image: nginx:latest
    restart: always
    ports:
      - "80:80"
    volumes:
      - web-data:/usr/share/nginx/html

  # API that restarts unless deliberately stopped
  api:
    image: my-api:1.2.3
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://db:5432/mydb
    depends_on:
      db:
        condition: service_healthy

  # Batch worker with limited retries
  worker:
    image: batch-worker:latest
    restart: on-failure:5   # CLI-style colon syntax works in some Compose versions
    # Alternatively, use the object form for guaranteed compatibility:
    # restart:
    #   on-failure:
    #     max_attempts: 5
    environment:
      - QUEUE_NAME=processing

  # Database with always restart and health checks
  db:
    image: postgres:15
    restart: always
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  web-data:
  pgdata:

The Compose example above demonstrates a realistic microservices setup. The web and api services use different policies appropriate to their roles. The worker uses on-failure with retries to avoid infinite crash loops. The database combines always restart with a health check so dependent services don't connect before PostgreSQL is ready.

Production Best Practices

Restart policies are powerful but require thoughtful configuration. The following practices have been distilled from real-world production incidents and will help you avoid common pitfalls.

1. Never Use always for Batch or One-Shot Jobs

Containers that complete their work and exit with code 0 should not restart. If you apply always to a job that finishes successfully, it will restart and re-execute—potentially causing duplicate processing, data corruption, or infinite loops. Use on-failure with a retry limit for idempotent batch jobs, or no for strictly one-shot tasks.

# WRONG: A one-shot database migration with 'always' will run repeatedly
docker run --restart always migration-tool:latest

# CORRECT: Run once, restart only on crash with limited attempts
docker run --restart on-failure:3 migration-tool:latest

2. Combine Restart Policies with Health Checks

A restart policy alone only reacts to process exits. If your application hangs but doesn't crash (the process stays alive but is unresponsive), Docker won't restart it. Health checks bridge this gap by defining a liveness probe. In Docker Compose, you can pair a health check with a restart policy for comprehensive coverage:

services:
  app:
    image: my-app:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

If the health check fails three consecutive times, the container is marked as unhealthy. Docker itself does not restart a container solely based on health status, but orchestrators like Docker Swarm or Kubernetes will. In standalone Docker, pair health checks with an external watchdog or use a process manager inside the container that reads health status and exits to trigger the restart policy.

3. Set Retry Limits on on-failure Policies

Without a retry limit, a crashing container restarts forever. If the crash is caused by a persistent condition (bad config, missing dependency, corrupted volume), the infinite restart loop wastes CPU, fills logs, and can mask the real problem. A retry limit ensures eventual failure visibility:

# Explicit retry limit prevents infinite crash loops
docker run --restart on-failure:5 critical-worker:latest

After 5 consecutive failures, Docker stops restarting. Your monitoring system should alert on containers in exited state with a non-zero exit code. This pattern gives transient issues (network blips, temporary resource contention) a chance to self-heal while preventing runaway restarts from permanent failures.

4. Understand Daemon Restart Behavior

When the Docker daemon stops (e.g., during a systemctl restart docker or host reboot), all running containers receive a SIGTERM followed by SIGKILL after the grace period. When the daemon starts again, it evaluates each container's restart policy:

This behavior means that even containers with always that you deliberately stopped with docker stop will restart after a daemon restart. If you want a container to stay stopped across reboots after a manual stop, use unless-stopped. This is a common source of confusion in production: an admin stops a container, the server gets rebooted for patches, and the container unexpectedly comes back. Switching to unless-stopped eliminates this surprise.

5. Avoid Combining In-Container Init Systems with Restart Policies

Some base images include an init system like tini, dumb-init, or even systemd. If the init process catches crashes and restarts child processes internally, Docker sees the container as still running and never triggers its restart policy. This creates two competing restart mechanisms. Choose one approach:

Mixing both is an anti-pattern. It can hide failures because the internal supervisor keeps the container alive while the application is actually broken. Docker's restart policy never gets a chance to act.

6. Use unless-stopped as the Default for Services

For long-running API servers, web applications, databases, caches, and message queues, unless-stopped is the most operator-friendly policy. It ensures high availability after crashes and host reboots while respecting deliberate maintenance stops. Reserve always for containers that absolutely must never be stopped—even during maintenance—which is rare in practice.

7. Monitor Restart Loops Proactively

Even with retry limits, a container that restarts frequently indicates an underlying problem. Set up monitoring that tracks container restart counts over time. You can retrieve this via Docker's API or the docker ps output:

# Check restart count and status
docker ps --format "table {{.Names}}\t{{.RestartCount}}\t{{.Status}}"

# Example output:
# NAMES               RESTART COUNT   STATUS
# nginx-prod          0               Up 2 hours
# api-service         3               Up 15 minutes (unhealthy)
# batch-processor     127             Exited (1) 5 minutes ago

A restart count climbing rapidly signals a crash loop. Integrate this metric into your observability stack (Prometheus, Datadog, CloudWatch) with an alert threshold. For example, alert if any container restarts more than 10 times in a 5-minute window.

8. Plan Restart Policies for Startup Order Dependencies

In complex stacks, services depend on databases, message brokers, or configuration stores being available before they can start successfully. A naïve restart policy can cause a service to crash-loop while waiting for its dependency to be ready. Docker Compose's depends_on with condition: service_healthy helps, but it only applies at initial startup, not during subsequent restarts triggered by the restart policy.

Design your application to handle missing dependencies gracefully: implement retry logic with backoff inside the application code. The restart policy provides the outer safety net; internal resilience handles transient startup ordering:

# In your application code (pseudo-code example)
import time
import psycopg2

def connect_with_retry():
    for attempt in range(10):
        try:
            conn = psycopg2.connect(host="db", ...)
            return conn
        except psycopg2.OperationalError:
            time.sleep(min(30, 2 ** attempt))
    raise Exception("Could not connect to database after 10 attempts")

With this pattern, the container doesn't immediately crash on a temporary dependency unavailability. The restart policy only kicks in if the retries are exhausted and the process exits with an error.

Real-World Production Scenarios

Let's walk through several complete, production-grade configurations that put these practices into action.

Scenario A: Stateless Web Application Behind a Load Balancer

version: '3.8'

services:
  web:
    image: my-web-app:2.1.0
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - NODE_ENV=production
      - API_KEY=${API_KEY}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
      interval: 15s
      timeout: 3s
      retries: 2
      start_period: 30s
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 512M
        reservations:
          cpus: '1'
          memory: 256M
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

This configuration uses unless-stopped because the web app should recover from crashes automatically but stay stopped when an operator deliberately shuts it down for a deployment. The health check ensures the load balancer can detect unresponsive instances. Resource limits prevent a single container from consuming all host resources during a restart loop.

Scenario B: PostgreSQL with Automatic Recovery

version: '3.8'

services:
  postgres:
    image: postgres:15.4-alpine
    restart: always
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: production
    volumes:
      - pgdata:/var/lib/postgresql/data
      - pgwal:/var/lib/postgresql/wal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d production"]
      interval: 5s
      timeout: 5s
      retries: 3
      start_period: 20s
    sysctls:
      - vm.swappiness=1
    shm_size: 256m

volumes:
  pgdata:
    driver: local
  pgwal:
    driver: local

Databases warrant always because they hold critical state. If PostgreSQL crashes due to a transient memory pressure issue, it must restart immediately. The volumes persist data across restarts. The health check confirms the database is accepting connections. Note that in a real production database, you would also configure WAL archiving and backups—the restart policy is one layer of a comprehensive reliability strategy.

Scenario C: Cron-Style Batch Processor with Retry Limits

version: '3.8'

services:
  nightly-report:
    image: report-generator:3.0.1
    restart: on-failure:3
    environment:
      - DB_CONNECTION_STRING=${DB_CONNECTION_STRING}
      - REPORT_DATE=${REPORT_DATE:-$(date +%Y-%m-%d)}
    volumes:
      - reports:/app/output
    # This container runs to completion each night
    # It restarts up to 3 times if it crashes with an error
    # On success (exit code 0), it stays stopped

volumes:
  reports:

This batch processor generates reports and exits. With on-failure:3, transient failures like a temporary database disconnect get three chances before the container gives up. Successful completion (exit 0) does not trigger a restart, so the report runs exactly once per invocation. An external scheduler (like a cron job on the host or a scheduling service) triggers this container nightly.

Common Pitfalls and Troubleshooting

Even experienced teams encounter restart policy issues. Here are the most frequent problems and how to resolve them.

Container Restarts Too Quickly (Restart Loop)

Symptom: The container restarts every few seconds, filling logs and consuming CPU.

Cause: The application crashes immediately on startup due to a configuration error, missing dependency, or corrupt state.

Fix: Inspect the container logs from the previous run:

# View logs from the previous container instance
docker logs --tail 100 api-service

# If the container is restarting too fast, check the exit code
docker inspect --format '{{ .State.ExitCode }}' api-service

If you're using on-failure, the retry limit will eventually stop the restarts. For always or unless-stopped, you may need to temporarily stop the container to break the loop, fix the underlying issue, then restart:

# Break the restart loop
docker stop api-service

# Fix the configuration or environment
# ...

# Start fresh
docker start api-service

Container Doesn't Restart After Docker Daemon Restart

Symptom: After a host reboot or systemctl restart docker, containers remain stopped.

Cause: The containers may have been manually stopped before the daemon restart, or they're using no or on-failure (with exit code 0) policies.

Fix: Verify the restart policy and the container's previous state:

# Check the policy
docker inspect --format '{{ .HostConfig.RestartPolicy.Name }}' my-container

# Check if the container was manually stopped
docker inspect --format '{{ .State.Restarting }} {{ .State.Status }}' my-container

Switch to always or unless-stopped if you need the container to survive daemon restarts unconditionally.

Restart Policy Not Applying to Compose Services

Symptom: You've set restart: always in your Compose file, but containers don't restart after a crash.

Cause: You may have run docker compose up without the --force-recreate flag, and the containers were originally created without a restart policy. Restart policies are set at container creation time and cannot be updated in place.

Fix: Recreate the containers with the new policy:

# Force recreation to apply new restart policies
docker compose up -d --force-recreate

# Or remove containers and start fresh
docker compose down
docker compose up -d

Always use docker compose down followed by docker compose up -d when changing restart policies, or use --force-recreate for an in-place update.

Summary Table of Restart Policies

The following table provides a quick reference for choosing the right policy:

+-------------------+--------------------------------------+---------------------+
| Policy            | Behavior                             | Best For            |
+-------------------+--------------------------------------+---------------------+
| no                | Never restart                        | One-shot tasks,     |
|                   |                                      | debugging           |
+-------------------+--------------------------------------+---------------------+
| always            | Always restart, even after           | Critical services   |
|                   | manual stop if daemon restarts       | that must run       |
|                   |                                      | forever             |
+-------------------+--------------------------------------+---------------------+
| unless-stopped    | Restart unless manually stopped      | Most production     |
|                   | (survives daemon restarts)           | services (default   |
|                   |                                      | choice)             |
+-------------------+--------------------------------------+---------------------+
| on-failure        | Restart only on non-zero exit;       | Batch jobs, CI/CD   |
|                   | optional max retries                 | workers             |
+-------------------+--------------------------------------+---------------------+

Conclusion

Docker restart policies are a foundational element of production container reliability. They are simple to configure—a single flag or a single line in a Compose file—but their impact on uptime and operational clarity is profound. By choosing the appropriate policy for each workload, setting retry limits on batch jobs, combining policies with health checks, and monitoring restart behavior, you create a self-healing infrastructure that recovers from failures automatically while remaining under operator control.

The key takeaway is to move beyond the default no policy for any container that provides a service. Use unless-stopped as your production default for long-running services, on-failure with explicit retry counts for batch workloads, and reserve always for those rare cases where a container must survive every possible stop event. Pair these policies with thorough health checks, resource limits, and observability tooling, and you'll have a robust foundation for any containerized production system.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles