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_started on postgres, mysql, redis, kafka, rabbitmq and other slow services
  • RW002 service_healthy on a service with no (or a disabled) healthcheck
  • RW003 no start_period, or timeout longer than interval
  • RW004 service_completed_successfully on a restart: always service

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