Intro
Docker restart policies control whether containers automatically restart after they exit. They are critical for keeping services running and recovering from failures. But restart policies alone do not guarantee application availability. You also need to think about capacity: how many containers, how much CPU and memory, how many restart attempts, and how to avoid restart loops that waste resources.
This article combines restart policy configuration with capacity planning. You will see concrete commands, configuration snippets, and worked examples. The goal is operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, verify the result, and document how to recover if the expected state is not reached.
This guide is for developers, DevOps engineers, and technical startup teams who run Docker in production or production-like environments. By the end, you will be able to configure restart policies that match your workload, set resource limits to prevent noisy neighbors, and plan for failure without over-provisioning.
Version and Environment Inventory
Before touching restart policies or capacity, establish a baseline. Know what Docker version you are running, what containers exist, and what resources they use. This prevents surprises and gives you a rollback point.
Start with read-only commands:
docker version
Expected output shows client and server versions. Note the API version; restart policy behavior is stable across recent versions, but swarm mode and compose file versions matter.
List running containers:
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
The status column shows restart counts, for example "Up 2 hours (restarting 3 times)". That is a red flag.
Inspect a specific container to see its current restart policy and resource limits:
docker inspect <container> --format '{{.HostConfig.RestartPolicy}} {{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}'
Expected output: {no 0 0} if no policy, or {always 1073741824 500000000} meaning always restart, 1 GB memory, 0.5 CPU.
For Docker Compose projects, use:
docker compose ps
docker compose logs -f <service>
docker compose exec <service> sh
Also check where files are stored. Named volumes (e.g., app_data:/var/lib/app) are managed by Docker and easier to reuse across container rebuilds. Bind mounts (e.g., ./data:/var/lib/app) map directly to a host path, useful for development but can cause permission and portability problems. Confirm data persistence with a restart test: stop the container, recreate it, and ensure the application still sees expected files. If data disappears, the service was writing to the container filesystem rather than a volume or mount.
Run a small production-like local test to verify persistence before rolling changes to staging or production.
Safe Configuration Path
Changing restart policies and resource limits requires a deliberate path. First, capture the current state. Then make one scoped change, verify it, and document recovery.
Choosing a Restart Policy
Docker supports four restart policy values:
no: Do not automatically restart the container (default).on-failure[:max-retries]: Restart only if the container exits with a non-zero exit code. Optionally limit the number of restart attempts. For example,on-failure:5restarts at most 5 times.always: Always restart the container regardless of exit code. If stopped manually, it restarts only when the Docker daemon restarts or the container is manually started.unless-stopped: Similar toalways, but if the container was stopped manually before the daemon restarted, it will not be restarted.
Use unless-stopped for most long-running services. It avoids unexpectedly restarting containers that were intentionally stopped for maintenance. Use on-failure for batch jobs or one-off tasks where infinite restarts could loop. Avoid always unless you have a specific reason, because it can mask configuration errors by repeatedly restarting a broken container.
Example: run a web server with unless-stopped and limit restarts:
docker run -d --name web \
--restart unless-stopped \
--memory 512m \
--cpus 0.5 \
nginx:alpine
For a database that should not auto-restart on data corruption, use on-failure:3:
docker run -d --name db \
--restart on-failure:3 \
-e POSTGRES_PASSWORD=secret \
postgres:15
In Docker Compose, declare policies under the service:
services:
web:
image: nginx:alpine
restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
Note: restart is ignored in swarm mode; use deploy.restart_policy instead.
Capacity Planning Basics
Capacity planning is not just about restart policies; it is about ensuring containers have enough resources to run and that the host can handle the load. Set memory and CPU limits on every container to prevent a single container from exhausting the host.
Memory limits: Use --memory and --memory-swap. If a container exceeds its memory limit, the kernel may kill the process (OOM). Example:
docker run -d --name app --memory 1g --memory-swap 2g myapp
CPU limits: Use --cpus to limit the number of CPU cores. Example: --cpus 1.5 allows 1.5 cores. You can also use CPU shares for relative weighting.
Monitor actual usage with docker stats:
docker stats --no-stream
Output shows CPU %, memory usage, and limit. Use this to right-size limits.
Consider restart loops: if a container repeatedly exits and restarts, it may consume CPU cycles. Configure on-failure with a maximum retry count and combine with a healthcheck to avoid infinite loops.
Implementing Changes Safely
When changing policies, do it in this order:
- Record current state:
docker inspect <container> > before.json - Make the change: recreate container or update compose file and
docker compose up -d - Verify: check restart policy and resource limits with
docker inspect - Test failure: manually stop the container and see if it restarts as expected
- Rollback: if needed, use the saved
before.jsonto recreate the original configuration
Never edit a running container directly. Always recreate from a known configuration (Dockerfile, compose file, or run command).
Verification and Diagnostics
After configuring restart policies and resource limits, verify that they work as intended. Use a combination of commands to observe behavior.
Simulating a Failure
To test a restart policy, stop the container and observe:
docker stop web
docker ps -a
If the policy is unless-stopped, the container will not restart immediately because it was manually stopped. To test failure restart, kill the main process instead:
docker exec web kill 1
Then check docker ps after a few seconds: the container should be running again.
For on-failure, simulate a non-zero exit by running a container that exits with error:
docker run --name failtest --restart on-failure:2 alpine sh -c 'exit 1'
Check the restart count:
docker inspect failtest --format '{{.RestartCount}}'
It should be 2 (initial run plus one restart) and then stop retrying.
Diagnosing Restart Loops
A container stuck in a restart loop shows a rapidly increasing restart count. Common causes:
- Broken configuration (e.g., wrong environment variable)
- Missing dependencies (database not ready)
- OOM kills because memory limit too low
- Application crashes due to bug
Use logs to find the reason:
docker logs web --tail 100
Check docker inspect for OOM kills:
docker inspect web --format '{{.State.OOMKilled}}'
If true, increase memory limit or fix memory leak.
Use healthchecks to prevent routing traffic to an unhealthy container. Example Dockerfile:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
In Compose:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 3s
retries: 3
A healthy container with a restart policy is more resilient.
Observing Resource Usage
To plan capacity, monitor actual usage over time. Use docker stats periodically or integrate with monitoring tools like Prometheus and cAdvisor. For a quick snapshot:
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
Note the memory percentage relative to the limit. If consistently above 80%, consider raising the limit or optimizing the application.
Failure Modes and Recovery
Even with good restart policies, failures occur. Understand common failure modes and how to recover.
Restart Policy Pitfalls
- Infinite restart loops: Without a max retry count, a broken container can restart forever, consuming CPU and filling logs. Use
on-failurewith a limit for tasks that should eventually give up. - Masking configuration errors:
alwaysorunless-stoppedmay keep a container running but in a bad state. Monitor logs and healthchecks to catch these. - Stateful containers: Restarting a database container without proper data persistence can lead to data loss. Always use volumes and test persistence.
- Dependency ordering: If a container restarts before its dependency is ready, it may fail again. Use
depends_onwith condition in Compose (in newer versions) or an init container.
Recovery Procedures
If a container is in a restart loop:
- Stop the container to break the loop:
docker stop <container> - Inspect logs to find the root cause:
docker logs <container> - Fix the issue: update configuration, increase memory, fix code, or wait for dependency
- Start the container manually:
docker start <container> - Verify it stays up and healthy.
If the host is out of resources (OOM), identify the culprit with docker stats and docker events --filter event=oom. Reduce limits on other containers or add capacity.
For stateful services, practice recovery: simulate a container loss and restore from backup. Ensure backups are tested regularly.
Operations Checklist
Use this checklist before and after making changes to restart policies and capacity settings. Assign an owner to each item and review at least monthly.
| Item | Owner | Frequency |
|---|---|---|
| Review restart policies for all production containers | DevOps lead (e.g., Priya Shah) | Monthly |
| Check restart counts and identify loops | On-call engineer | Daily via monitoring |
| Validate memory and CPU limits against actual usage | Capacity planner | Weekly |
| Test data persistence and backup restoration | Database administrator | Quarterly |
| Simulate container failure and verify auto-restart | QA engineer | After each release |
| Update runbooks with recovery steps | Tech writer or DevOps lead | When changes occur |
For each item, document the current state, the expected state, and the command to verify. Keep a log of changes with timestamps and owners.
Common Pitfalls and How to Avoid Them
Pitfalls often arise from misconfiguration or misunderstanding of restart policies and resource limits.
1. Ignoring restart loops
Why it happens: Developers set always without a backoff or monitoring. A container crashes on startup due to a missing config, and it loops forever, consuming CPU and filling logs.
How to avoid: Use on-failure with a max retry count for non-critical services. For critical services, use healthchecks and alerting on restart counts. Set log rotation to prevent disk exhaustion.
2. Not limiting memory and CPU
Why it happens: Teams forget to set limits, or assume the host has enough resources. One container with a memory leak can cause the kernel to kill other processes.
How to avoid: Always set --memory and --cpus for production containers. Use docker stats to monitor usage and adjust limits. Consider using a container orchestration platform (Kubernetes, Swarm) that enforces resource quotas.
3. Misunderstanding always vs unless-stopped
Why it happens: Users think always is appropriate for all services, but it restarts containers even after manual stop. This can interfere with maintenance windows.
How to avoid: Use unless-stopped for services that should run until intentionally stopped. Use always only when you always want the container running, even after a daemon restart and regardless of manual stops.
4. Getting stuck with on-failure retries
Why it happens: Setting on-failure:10 on a container that has a persistent error results in 10 restarts and then the container stops. If no one notices, the service remains down.
How to avoid: Combine on-failure with alerting. Monitor container status and send notifications when a container enters a stopped state after retries. Consider using unless-stopped with a healthcheck for self-healing.
Conclusion
Docker restart policies are a key part of container resilience, but they must be configured with capacity planning in mind. Without limits, containers can consume excessive resources and cause host instability. Without proper policies, a broken container may loop indefinitely or fail silently.
By following the practices in this article—establishing a baseline, choosing the right restart policy, setting resource limits, verifying behavior, and planning for recovery—you can maintain a stable and efficient container environment.
As a next step, pick one container in your environment, review its restart policy and resource limits, and run a failure simulation. Document the results and share with your team. Make this a regular review process to catch issues before they cause downtime.
Remember: a reliable workflow makes failure visible, protects sensitive values, limits changes, and defines recovery verification ahead of time.
Now it is your turn: use the checklists, commands, and examples to harden your containers.