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
| Setup | Behavior |
|---|---|
depends_on: [db] | Start order only; app may start before db is ready → Connection refused |
depends_on: db: condition: service_healthy | App starts after db healthcheck passes (recommended) |
| Healthcheck command missing | Container exits 127 → dependency failed to start ... exited (127) |
| Slow start + no start_period | Startup failures count toward retries → misjudged unhealthy |
| Healthcheck always failing | ps 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
- Add a
healthcheckto the dependency (test command must exist in the image; addstart_periodfor slow starts); - Switch app to
depends_on: db: condition: service_healthy; docker compose up -dthen accept withdocker compose up --wait --wait-timeout 60;- 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"]→psshowsUp 7 seconds (unhealthy),up --waiterrors unhealthy.
Evidence details kept locally (2026-08-31)
Related canonicals
Read in order: precondition -> same root / same domain / related:
- Precondition (rule out first):
- docker compose / docker compose command not found (same domain: prerequisite of the compose startup chain)
- 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.
dockerSource & evidence
← Coding Agents & Terminal domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk