Why Do Docker Containers Keep Restarting? A Step‑by‑Step Investigation to Find the Root Cause
The article explains that frequent Docker container restarts are driven by the restart policy, not the underlying issue, and provides a systematic method—collecting container state, logs, events, exit codes, OOM flags, health‑check results, and restart policy details—to pinpoint the true cause before applying targeted fixes.
Phenomenon: confirm it’s a restart, not a brief outage
Gather the container’s current status, restart count, exit code, OOM flag and recent lifecycle timestamps.
docker ps -a --no-trunc --format 'table {{.Names}} {{.Status}} {{.Image}}'
docker inspect --format '{{.Name}} restart={{.RestartCount}} status={{.State.Status}} running={{.State.Running}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} started={{.State.StartedAt}} finished={{.State.FinishedAt}} error={{.State.Error}}' app-apiReplace app-api with the target container name or ID. Use docker ps -a because short‑lived containers may not appear in docker ps.
Information collection: freeze the scene
Save inspect output, recent logs and events to a controlled directory, redacting any sensitive data as required.
mkdir -p /var/tmp/docker-investigation
docker inspect app-api > /var/tmp/docker-investigation/app-api.inspect.json
docker logs --timestamps --tail 500 app-api > /var/tmp/docker-investigation/app-api.log 2>&1
timeout 120 docker events --filter container=app-api > /var/tmp/docker-investigation/app-api.events.txtDo not delete containers, prune the system, restart the Docker daemon, or overwrite deployment files during the early investigation stage.
Read the entry command, restart policy, mounts and health‑check configuration:
docker inspect --format 'restart={{json .HostConfig.RestartPolicy}} path={{.Path}} args={{json .Args}} health={{json .Config.Healthcheck}}' app-api
docker inspect --format '{{range .Mounts}}{{println .Type .Source "->" .Destination "rw=" .RW}}{{end}}' app-api
docker inspect --format 'labels={{json .Config.Labels}}' app-api
docker top app-api -eo pid,ppid,user,stat,etime,argsIf the entry point is a shell script, verify that it ultimately uses exec to start the service; otherwise PID 1 signal handling may be incorrect.
Initial classification: branch by exit category
OOMKilled=trueor kernel OOM‑kill → check cgroup limits, host memory, process growth (cannot conclude memory leak directly).
Log shows parameter or config validation failure → check env, mounts, start command, secrets (cannot conclude Docker malfunction).
Health status unhealthy → examine health‑check command, start period, dependencies (Docker does not automatically restart solely because of unhealthy).
Exit code 143 or stop event → investigate release system, manual operation, daemon event (may indicate application crash).
Multiple containers restart in the same window → inspect Docker daemon, host, disk, kernel (does not imply single image corruption).
Check 1: align logs and events
View events and application logs for the incident window.
docker events --since '2026-07-10T14:00:00' --until '2026-07-10T15:00:00' --filter container=app-api
docker logs --timestamps --since '2026-07-10T14:00:00' --until '2026-07-10T15:00:00' app-apiSample event output:
2026-07-10T14:30:58.981Z container die app-api (exitCode=1)
2026-07-10T14:30:59.114Z container start app-api
2026-07-10T14:31:03.206Z container die app-api (exitCode=1)If the die line is preceded by logs indicating missing config, port conflict, certificate failure or a stack trace, continue verification from that evidence. If logs stop abruptly, investigate OOM, SIGKILL, host reboot or log‑driver issues. The 30‑60 seconds before exit are usually most valuable.
Exit codes provide clues but are not definitive: 0 = normal exit, 1 = startup or runtime failure, 137 = SIGKILL/OOM, 143 = SIGTERM. Correlate with kernel logs, Docker events and application‑specific exit semantics.
Check 2: confirm or rule out OOM
Inspect container state, limits and kernel logs together.
docker inspect --format 'oom={{.State.OOMKilled}} exit={{.State.ExitCode}} memory={{.HostConfig.Memory}} memorySwap={{.HostConfig.MemorySwap}} pids={{.HostConfig.PidsLimit}}' app-api
sudo journalctl -k --since '30 minutes ago' --no-pager | grep -Ei 'out of memory|oom-kill|killed process'
docker stats --no-stream app-api
free -h
vmstat 1 5
df -hT
df -ihSample output:
oom=true exit=137 memory=536870912 memorySwap=0 pids=0Memory is shown in bytes; the example equals 512 MiB. Zero values need interpretation based on Docker version and cgroup mode. Only when kernel logs show a matching killed process can you assert an OOM kill.
Check 3: health‑check and dependency readiness
Read the health‑check definition and its historical results.
docker inspect --format '{{json .Config.Healthcheck}}' app-api
docker inspect --format '{{range .State.Health.Log}}{{println .Start .End .ExitCode .Output}}{{end}}' app-api
docker inspect --format '{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' app-apiTypical problems: missing curl in a minimal image, wrong listening port, HTTP/HTTPS mismatch, or a start period that is too short. Run read‑only diagnostics inside the container:
docker exec app-api sh -c 'ps -ef; ss -ltn'
docker exec app-api sh -c 'getent hosts database.internal'Replace database.internal with the actual dependency name. If the image lacks sh, ps or ss, use a matching diagnostic container instead of installing tools in the production image.
Check 4: who is restarting the container?
Inspect the restart policy and Docker daemon events.
docker inspect --format '{{json .HostConfig.RestartPolicy}}' app-api
systemctl status docker --no-pager
sudo journalctl -u docker --since '30 minutes ago' --no-pager
sudo journalctl -k --since '30 minutes ago' --no-pagerThe policies always, unless-stopped and on-failure[:max-retries] behave differently; the latter only applies to non‑zero exits. Do not use docker update --restart=no as a permanent fix because it may be overridden by Compose or deployment scripts.
When using Docker Compose, resolve the actual deployment manifest first: docker compose -f /srv/app/compose.yaml config This command prints the merged Compose configuration without starting containers, but it may read environment files, so run it in a controlled environment. Avoid commands that delete containers, volumes or networks ( docker rm -f, docker volume rm, docker system prune) before you understand their impact.
Fix: repair the object indicated by the evidence
If the application fails to start, correct environment variables, mounts, command or secret references in the configuration repository rather than editing files inside a running container. For port conflicts, locate the real listener before adjusting mappings. For OOM, determine whether the limit is too low, the host is contended, or the application’s memory usage has grown, then adjust limits, concurrency, heap size or code.
When a dependency is not ready, add controlled retries and back‑off, or increase the health‑check start period; do not replace proper dependency handling with an endless fast‑restart loop.
Before rebuilding, confirm data locations:
docker inspect --format '{{range .Mounts}}{{println .Type .Name .Source .Destination}}{{end}}' app-api
docker volume lsThe container’s writable layer is not a backup. For databases, uploaded files, queue state or certificates, perform a consistent backup at the application or storage layer and verify recoverability.
Verification and rollback
Before applying a fix, ensure traffic can be removed from the affected replica, enough healthy replicas exist, the current manifest is backed up and required data is safely backed up. docker compose config only reduces syntax risk; it does not replace a staged rollout.
Verification must include:
container stays up longer than the previous restart cycle (no increase in RestartCount),
health‑check passes,
critical ports and dependencies are reachable,
logs no longer show the same errors,
host resources (memory, disk, CPU) show no new anomalies.
When using Prometheus, monitor restart counts, cgroup memory, CPU throttling, host OOM events, application error rates and latency.
If the new version fails to start, causes data incompatibility or reduces healthy replicas, stop the rollout, restore the previously verified image and manifest, and recreate the affected replica. Database migrations should only be rolled back if a clear rollback plan and backup exist.
Container logs and log drivers: do not treat “no logs” as “no anomaly”
Docker can only expose logs via docker logs when the application writes to stdout/stderr. Check the driver and its options:
docker inspect --format 'log_driver={{.HostConfig.LogConfig.Type}} log_opts={{json .HostConfig.LogConfig.Config}}' app-api
docker info --format 'default_log_driver={{.LoggingDriver}}'If the driver is json-file and log rotation is not configured, excessive output can fill the host disk and cause secondary failures. Drivers such as journald, fluentd or remote drivers affect visibility and failure modes differently; an empty docker logs output does not prove the application produced no output.
Check host disk and inode usage as well:
df -hT
df -ih
sudo journalctl -u docker --since '30 minutes ago' --no-pagerChanging log rotation or driver settings is high‑risk: it impacts all containers’ observability. Perform the change on a single‑node gray‑scale, verify the logging pipeline, and be ready to roll back by restoring the previous daemon configuration or deployment manifest.
Understanding the lifecycle: correct use of restart count
RestartCountis the cumulative number of restarts for the container’s current lifecycle. Image upgrades, container recreation or docker compose up --force-recreate can reset the count, so it should not be the sole alert metric across releases. Monitoring should also retain container creation time, image digest, release version, Docker events and application health probes. Correlating “when did the restart happen, with which image, and who triggered it” distinguishes release‑induced problems from infrastructure or application crashes.
For long‑running services, include “container exit code, OOM flag, health status, image digest, creation time and application error logs” on a single troubleshooting dashboard. This enables answering “which version started failing, which replica exited first, and whether resources changed” instead of seeing an isolated restart alert.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
