Docker

Docker Container Healthcheck Stuck in "Starting" State

A container permanently stuck showing "health: starting" rather than transitioning to either "healthy" or "unhealthy" usually means the healthcheck command itself is hanging indefinitely, or the check is somehow never actually completing within the container's lifetime, rather than a straightforward pass/fail evaluation issue.

The Problem

docker ps shows the container remaining in a starting state far longer than expected:

CONTAINER ID   STATUS
a1b2c3d4e5f6   Up 10 minutes (health: starting)

It never transitions to "healthy" or "unhealthy" — normally, after the configured start_period and enough check attempts, Docker should settle into one state or the other, but here it just stays indefinitely in the ambiguous starting phase.

Why It Happens

The "starting" state is meant to be a temporary grace period at the beginning of a container's life, controlled by the start_period setting, during which failed checks don't count against the container's health status yet. Getting permanently stuck here points to a few specific causes:

  • The healthcheck command itself hangs indefinitely rather than completing (success or failure) within its configured timeout, so Docker never gets a definitive result to evaluate at all
  • The start_period is set to an unusually long value (sometimes done intentionally for slow-starting applications, but occasionally set far longer than actually intended by mistake), keeping the container in this grace period far longer than the application genuinely needs to actually start
  • A misconfigured healthcheck command that's syntactically valid but never actually resolves to a proper exit code within the container's environment, such as referencing a tool or path that doesn't exist, causing the command itself to hang rather than fail cleanly
  • Docker's own internal healthcheck scheduling being disrupted by extremely high load on the host, delaying when scheduled checks actually run

The Fix

First, check exactly what start_period, interval, and timeout are currently configured to, since a stuck starting state might simply mean not enough time has passed yet, rather than a genuine hang:

docker inspect your-container --format='{{json .Config.Healthcheck}}'

Compare the configured start_period against how long the container has actually been running — if it's still within that window, "starting" is expected and not actually a problem yet.

If the container has genuinely exceeded the configured start_period and is still stuck, test the exact healthcheck command manually from inside the container to see whether it actually completes or hangs:

docker exec -it your-container sh -c "curl -f http://localhost:3000/health"
echo $?

If this command itself hangs when run manually (never returning a result, requiring you to manually interrupt it), that's the direct cause — the healthcheck command needs its own internal timeout or needs to be replaced with one that reliably completes:

HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
  CMD curl -f --max-time 3 http://localhost:3000/health || exit 1

Adding --max-time 3 to the curl command itself ensures curl gives up and returns a failure after 3 seconds if the endpoint doesn't respond, rather than potentially hanging indefinitely waiting for a response that might never come — this is a separate, more granular safeguard than Docker's own timeout setting on the HEALTHCHECK instruction itself, and using both together provides more reliable behavior.

If the healthcheck command references a tool that might not actually exist in a minimal base image, confirm it's genuinely available rather than assuming:

docker exec -it your-container which curl

A missing tool doesn't always fail cleanly and immediately in every possible healthcheck command construction — depending on exactly how the command is written, a missing binary can sometimes contribute to unusual hanging behavior rather than a clean, fast failure.

Still Not Working?

If the healthcheck command completes quickly and reliably when run manually, but the container still shows stuck in "starting" through Docker's own automated scheduling, check the Docker daemon's own logs for any signs of resource exhaustion or scheduling delays at the host level, since under genuinely extreme host load, Docker's internal healthcheck scheduler can itself experience delays independent of anything wrong with your specific container or its healthcheck command:

journalctl -u docker.service --since "10 minutes ago"