Coding Agents & Terminal · recorded 2026-08-31 · applies to 1 tool(s) ✓ Verified

Problem: docker compose stack fails to start — dependency not ready (Connection refused / unhealthy / depends_on ignored) —— Trove problem library

Verified 2026-08-31depends_on only waits for container start, not readiness — add healthchecks to dependencies and use the long-form depends_on with condition.

Quick fix

# Add a healthcheck to the dependency; app uses the long-form depends_on:
services:
  db:
    image: your-db-image
    healthcheck:
      test: ["CMD", "your-healthcheck-cmd"]   # must exist in the image!
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s      # startup failures do not count toward retries (required for slow services)
  app:
    image: your-app-image
    depends_on:
      db:
        condition: service_healthy   # start app only after db is healthy
docker compose up -d
docker compose up --wait --wait-timeout 60   # acceptance: fail unless everything is healthy

What you will run into

After docker compose up, the dependent service (app) reports Connection refused (cannot reach the database/upstream), dependency failed to start: container xxx is unhealthy, or docker compose ps keeps showing unhealthy.

You wrote depends_on exactly like the tutorial says and it still fails — plain depends_on only guarantees start order, not readiness: the db container being up does not mean its port is connectable.

Why this happens

compose depends_on has two semantics: start order (short form depends_on: [db]: create/start db first, do not wait for readiness) and readiness condition (long form depends_on: db: condition: service_healthy: start app only after db's healthcheck passes).

Databases/middleware usually take seconds from "process started" to "port connectable" (locally reproduced: a python http.server stand-in for db needed a 4s sleep), so connections during that window are refused.

The readiness condition relies on a healthcheck: compose runs the test command every interval, marks the service unhealthy after retries consecutive failures, healthy on success.

up --wait waits for every service to become healthy (bounded by --wait-timeout).

Three high-frequency healthcheck pitfalls, all reproduced locally: ① test uses a binary that is not in the image (e.g.

alpine has no busybox httpd applet) → container exits 127, compose reports dependency failed to start ... exited (127);

② slow-starting service without start_period → startup failures count toward retries and it gets misjudged unhealthy (reproduced: service ready at 4s, interval 1s × retries 3 already killed it at ~3s);

③ the check command itself is wrong/always fails → ps shows Up x seconds (unhealthy) and up --wait times out.

Deterministic takeaway: on unhealthy, read docker inspect <container> --format "{{json .State.Health.Log}}" — the last entry's Output is the check command's real error; on exited (127), verify the test command exists in the image first.

Step by step

1. Classify the error first — Must do

  • Connection refused: app started too early, dependency not ready → add healthcheck + condition: service_healthy.
  • dependency failed to start ... is unhealthy: healthcheck failing → go to step 2.
  • dependency failed to start ... exited (127): healthcheck command missing from the image → change command or image.

2. Find out why the healthcheck fails — Must do

docker compose ps                      # which service is unhealthy
docker inspect <container> --format "{{json .State.Health.Log}}" | tail -c 800

The last log entry's Output is the real error from the check command (locally we saw the urllib ConnectionRefused traceback).

3. Verify the test command exists in the image — Must do

docker run --rm <image> sh -c "which <command>"    # not found -> switch command

Counter-example reproduced: alpine has no busybox httpd (httpd: applet not found, exit 127).

4. Add start_period for slow-starting services — By diagnosis

If the service needs a few seconds to become ready, start_period must exceed that time; otherwise startup failures count toward retries and it gets misjudged unhealthy (reproduced: a 4s-ready service was killed at ~3s without start_period).

5. Accept with up --wait — Must do

docker compose up -d
docker compose up --wait --wait-timeout 60    # fail on timeout, never wait forever

How to confirm it works

docker compose up --wait --wait-timeout 60 exits 0; docker compose ps shows Up x seconds (healthy) for every service;

the app log no longer shows Connection refused and business calls succeed (reproduced: app printed APP_OK right after db became healthy).

Other environments

SetupBehavior
depends_on: [db]Start order only; app may start before db is ready → Connection refused
depends_on: db: condition: service_healthyApp starts after db healthcheck passes (recommended)
Healthcheck command missingContainer exits 127 → dependency failed to start ... exited (127)
Slow start + no start_periodStartup failures count toward retries → misjudged unhealthy
Healthcheck always failingps shows unhealthy; up --wait times out with dependency failed to start ... is unhealthy

Symptom

app-1  | wget: can't connect to remote host (172.18.0.3): Connection refused
app-1  | APP_CONN_REFUSED
db-1  | httpd: applet not found
db-1 exited with code 127
dependency failed to start: container depstest-db-1 exited (127)
Container depstest-db-1  Waiting
Container depstest-db-1  Error
dependency failed to start: container depstest-db-1 is unhealthy

Signature: you wrote depends_on and still get connection refused; or ps shows unhealthy; or the container exits 127.

Cause

Plain depends_on guarantees start order only, not readiness. Readiness requires a healthcheck plus condition: service_healthy. Healthcheck failures come from three sources: command missing from the image (exit 127), slow start without start_period (misjudged unhealthy), or a broken check command itself (permanently unhealthy).

Solution

  1. Add a healthcheck to the dependency (test command must exist in the image; add start_period for slow starts);
  2. Switch app to depends_on: db: condition: service_healthy;
  3. docker compose up -d then accept with docker compose up --wait --wait-timeout 60;
  4. On unhealthy, docker inspect <container> --format "{{json .State.Health.Log}}" to see the check command's real error.
Verification Evidence
  • Local reproduction (2026-08-31, custom prebuilt WSL, docker 29.1.3 + compose 2.40.3):
  • Phase 1: plain depends_on → app starts immediately → APP_CONN_REFUSED (db sleeps 4s);
  • Phase 2: healthcheck + condition: service_healthy + start_period 6s → app starts after db healthy → APP_OK;
  • Variant A: no start_period → 3 startup failures → dependency failed to start ... is unhealthy (service ready at 4s);
  • Variant B: db used busybox httpd (applet not in alpine) → httpd: applet not found, exit 127;
  • Phase 3: healthcheck ["CMD","false"] → ps shows Up 7 seconds (unhealthy), up --wait errors unhealthy.

Evidence details kept locally (2026-08-31)

Related canonicals

Read in order: precondition -> same root / same domain / related:

  • Related (similar errors, different cause):
  • Docker daemon unreachable (the daemon must be alive first; connection errors surface before compose semantics)

Applicable tools (1)

This problem is linked from the tool pages below — reachable from either side.

docker

Source & evidence

Recorded Verified ✓ VerifiedApplies to 1 tool(s)
Entries contain locally measured or reviewable-source solutions; unmeasured items are explicitly labeled.

← Coding Agents & Terminal domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk