Docker Compose Service depends_on Starting Before Database Is Actually Ready
Quick answer
Plain depends_on in Docker Compose only guarantees a dependency container has started — not that the actual service inside it (like PostgreSQL) is finished...
Plain depends_on in Docker Compose only guarantees a dependency container has started — not that the actual service inside it (like PostgreSQL) is finished initializing and genuinely ready to accept connections, which is a meaningfully different, later point in time that requires an explicit healthcheck to properly detect.
The Problem
An application container depending on a database starts before the database is actually ready, causing connection failures right at startup:
Error: connect ECONNREFUSED 127.0.0.1:5432
FATAL: the database system is starting up
The docker-compose.yml clearly lists the database as a dependency, which makes the failure confusing — the ordering looks correct in the configuration.
Why It Happens
Plain depends_on (without a healthcheck condition) only controls the order in which Docker starts containers — it waits for the dependency container's process to begin running, not for whatever service inside that container to finish its own internal initialization and become genuinely ready. A database image commonly takes a few extra seconds after its container starts before it's actually ready to accept connections (running its own startup sequence, potentially initializing a fresh data directory on first run) — and a plain depends_on has no visibility into that internal readiness state at all, only into whether the container process has launched.
The Fix
Add an explicit healthcheck to the database service, and use the condition: service_healthy form of depends_on, which waits for that healthcheck to actually pass before starting the dependent service:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
app:
build: .
depends_on:
db:
condition: service_healthy
With this configuration, Compose starts the db container, then actively waits for its healthcheck to report success (meaning pg_isready confirms PostgreSQL is genuinely accepting connections) before starting the app service at all — this is a meaningfully stronger guarantee than plain depends_on, which only waits for the container process itself to launch.
For other common services, use the appropriate healthcheck command specific to that service:
# MySQL
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 5
# Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
Even with a correctly configured healthcheck ensuring proper startup ordering, don't rely on this as the sole safeguard — add retry logic within the application itself for its initial connection attempt, since a database that becomes briefly unavailable later (a restart, a brief network blip) isn't covered by startup-time ordering at all, only by the application's own resilience to transient connection failures during its normal ongoing operation:
# Application-level retry, in addition to (not instead of) proper Compose ordering
for attempt in range(10):
try:
connect_to_database()
break
except ConnectionError:
time.sleep(2)
If you're using an older Compose file format version that doesn't support the condition syntax for depends_on, either upgrade to a newer Compose file version, or implement a wait manually using a wrapper script that polls until the dependency is genuinely ready before launching the actual application process:
# entrypoint.sh - manual wait pattern for older Compose versions
until pg_isready -h db -U postgres; do
echo "Waiting for database..."
sleep 2
done
exec "$@"
Still Not Working?
If you've added a healthcheck and confirmed condition: service_healthy is working correctly (verify with docker compose ps, which shows healthcheck status directly), but the application still occasionally fails on startup, check whether the healthcheck itself is passing prematurely — some database images report as accepting connections slightly before they're fully ready for all types of operations (particularly relevant for databases performing additional first-run initialization, like creating extensions or running seed migrations), in which case a brief application-level retry on the very first connection attempt, even with a correctly configured Compose healthcheck already in place, adds a useful additional layer of resilience against this specific, narrower timing edge case.