api-1 | Error: connect ECONNREFUSED 172.18.0.2:5432
api-1 exited with code 1
db-1 | LOG: database system is ready to accept connections
The app died one line before Postgres was ready. Run docker compose up again and it works. Your compose file says depends_on: [db], so what happened?
depends_on waits for "running", not "ready"
The list form of depends_on means condition: service_started. Compose starts db, sees the container running, starts your app. Meanwhile Postgres is still running initdb and your init scripts. The Docker docs say it directly: Compose waits for running, not ready.
The trap: service_healthy with no healthcheck
So you switch to condition: service_healthy. But the official postgres, mysql, mongo, redis and rabbitmq images define no HEALTHCHECK (on purpose). Without a healthcheck: in compose, startup fails with "has no healthcheck configured" or "container is unhealthy" instead of waiting.
The fix
services:
api:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER:-postgres}"]
interval: 5s
timeout: 5s
retries: 10
start_period: 30s
$$ stops Compose interpolating the variable on your host. start_period keeps a slow first boot from burning your retries. And keep a connection retry in the app anyway: databases restart in production too.
Flag it in the editor
I built ReadyWait, a free VS Code extension that checks every compose file in the workspace:
-
RW001 short-form
depends_on/service_startedon postgres, mysql, redis, kafka, rabbitmq and other slow services -
RW002
service_healthyon a service with no (or a disabled) healthcheck -
RW003 no
start_period, ortimeoutlonger thaninterval -
RW004
service_completed_successfullyon arestart: alwaysservice
One-click fixes convert depends_on to long form and insert pg_isready, mysqladmin ping, redis-cli ping, rabbitmq-diagnostics and similar probes where the tool ships in the official image. Override files and local Dockerfile HEALTHCHECKs count. Fully static: no network, no telemetry, no docker calls. It cannot see a HEALTHCHECK baked into a registry image, and it does not follow YAML anchors or include:.
code --install-extension jaytankdev.readywait
Source (MIT): github.com/jay-tank/readywait
Full write-up (why startup races happen, every healthcheck command, limitations, FAQ): docker compose depends_on does not wait for your database
Top comments (1)
The official images shipping no HEALTHCHECK on purpose is what makes this a trap instead of a missing feature: the correct fix fails in a new way ("has no healthcheck configured") and sends people back to the list form. Did you consider retrying app-side instead? The compose healthcheck proves postgres accepts connections, not that your migrations ran.