Operations 20 min read

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.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Why Do Docker Containers Keep Restarting? A Step‑by‑Step Investigation to Find the Root Cause

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-api

Replace 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.txt

Do 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,args

If 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=true

or 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-api

Sample 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 -ih

Sample output:

oom=true exit=137 memory=536870912 memorySwap=0 pids=0

Memory 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-api

Typical 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-pager

The 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 ls

The 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-pager

Changing 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

RestartCount

is 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerOpsLinuxContainerTroubleshootingRestart
MaGe Linux Operations
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.