Docker Compose Depends On: Production Guide
What is depends_on?
depends_on is a Docker Compose configuration directive that expresses startup order dependencies between services. It tells Compose which containers must be started first before starting the dependent service. The most common use case is ensuring that a database container is up before a web application container tries to connect to it.
In its simplest form, depends_on waits for the dependent container to start, but does not wait for the application inside that container to be ready to accept connections. This distinction is critical in production environments.
Why It Matters in Production
In development, a quick “start order” is often enough. In production, however, containers may start quickly but their processes take time to initialize — PostgreSQL may be restoring a backup, an API gateway may need to load configurations, or a cache server may still be warming up. Relying solely on start order leads to race conditions: your application container boots, tries to reach the database, and crashes with a connection error.
A production-grade setup must enforce readiness, not just startup order. Docker Compose provides mechanisms to wait for a service to be healthy, or for a short-lived init container to complete successfully, before proceeding. Combined with the --wait flag and proper health checks, depends_on becomes a powerful tool for reliable container orchestration on single-host deployments.
Basic Usage: Ordering Container Startup
Below is a simple docker-compose.yml file that uses depends_on without any readiness check. It ensures that db starts before web, but nothing more.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
web:
build: .
ports:
- "8080:80"
depends_on:
- db
Here, Compose launches db, waits for its container to transition to the “running” state, then starts web. If PostgreSQL takes 10 seconds to initialize inside its container, web will still start immediately after the container process starts — and may hit a “connection refused” error.
The Readiness Gap: Why Start Order Isn’t Enough
Docker Compose’s default depends_on monitors the container lifecycle, not the application lifecycle. A container is considered “started” as soon as its main process (PID 1) is spawned. For a database, that means the PostgreSQL server process exists, but it may not yet be accepting connections. For a message broker, RabbitMQ may be loading its internal schema. For a custom Java service, the JVM may still be initializing.
In production, you need to bridge this gap with health checks and conditional dependencies. Docker Compose supports three conditions that control when a dependent service is considered ready:
service_started— the default; waits only for container start.service_healthy— waits until the container’s health check passes.service_completed_successfully— waits for a short-lived container to exit with status code 0 (great for one‑off initialization jobs).
The latter two are essential for production reliability.
Production-Grade Dependencies with Health Checks and Conditions
Defining Health Checks
A health check instructs Docker to periodically run a command inside the container and verify the application is truly ready. For PostgreSQL, we can use pg_isready. For a custom HTTP service, we can use curl. Health checks are defined at the service level in Compose.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
api:
build: ./api
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/mydb
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 3s
retries: 3
start_period: 30s
ports:
- "3000:3000"
web:
build: ./web
depends_on:
api:
condition: service_healthy
ports:
- "80:80"
In this example:
dbdefines a health check usingpg_isready. It waits up to 10 seconds before starting checks (start_period), then checks every 5 seconds, requiring 5 successful checks before being markedhealthy.apiusesdepends_onwithcondition: service_healthy. Compose will not start the API container until PostgreSQL reports healthy.websimilarly waits for the API to be healthy before starting.
This creates a true readiness chain: database → API → web frontend. The entire stack initializes gracefully, avoiding startup race conditions.
Using condition: service_completed_successfully
Some containers are designed to run once and exit, like database migrations, seed data loaders, or configuration generators. You can express that a long‑running service depends on their successful completion.
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
migrations:
image: myapp-migrations:latest
environment:
DATABASE_URL: postgres://postgres:secret@db:5432/mydb
depends_on:
db:
condition: service_healthy
app:
image: myapp:latest
depends_on:
db:
condition: service_healthy
migrations:
condition: service_completed_successfully
ports:
- "8080:8080"
Here, app waits for the database to be healthy and for the migration container to finish (exit code 0) before starting. If the migration container fails (non‑zero exit), Compose will not start the app, and the entire deployment is blocked — a safe, predictable behavior in production pipelines.
Using docker compose up --wait
Starting from Docker Compose v2 (the plugin version), the up command supports a --wait flag. This instructs Compose to wait for all containers with a defined condition to become ready (healthy or completed) before exiting the command. It’s especially useful in CI/CD pipelines and production deployment scripts where you need to know the stack is fully operational before proceeding.
docker compose up --detach --wait
Without --wait, docker compose up --detach returns immediately after the containers are started, regardless of health status. With --wait, the command blocks until:
- Every service that has
depends_onwith a condition sees its dependency condition satisfied. - All containers with health checks report healthy (if they are referenced via
service_healthy).
Combine --wait with a timeout using the --wait-timeout flag (or COMPOSE_WAIT_TIMEOUT environment variable) to avoid hanging forever if a service never becomes healthy.
Limitations and Swarm Considerations
While depends_on is ideal for Docker Compose on a single host, it has important limitations in production environments that use Docker Swarm (docker stack deploy). Swarm mode ignores depends_on entirely because services may be scheduled on different nodes, and there’s no built‑in orchestration‑level ordering.
If you’re deploying to a Swarm cluster, you must handle dependencies manually — for example, by using application‑level retry logic with backoff, or an init container pattern that waits in a loop until the dependent service is reachable. In Kubernetes, the equivalent is an init container or pod startup probes. For single‑node production with Compose, however, depends_on with conditions is a robust solution.
Best Practices for Production
-
Always pair
depends_onwith health checks — never rely onservice_startedalone in production. Define meaningful health checks that test actual readiness (e.g.,pg_isready, HTTP/healthendpoint, gRPC health check). -
Use
start_periodin health checks to give slow‑starting services (like databases restoring a backup) a grace window before the first check. -
Prefer
condition: service_healthyfor long‑running services, andservice_completed_successfullyfor one‑shot initialization jobs. -
Combine with
--waitin deployment scripts. This ensures your CI/CD pipeline doesn’t proceed until the whole stack is healthy. -
Set a
--wait-timeoutor environment variable to fail fast if a dependency never becomes healthy, rather than hanging indefinitely. -
Use restart policies like
restart: unless-stoppedin combination withdepends_on. If a dependent container crashes after being healthy, it will restart without re‑evaluating the dependency order (which is correct for long‑running services). -
Validate Compose file version — the
conditionoption works with the modern Compose Specification (typically acompose.yamlfile without aversionkey). Legacy versioned files (version: '3') ignore the condition and silently fall back toservice_started. Move to the Compose Spec format for production. -
Plan for external dependencies —
depends_ononly covers services defined in the same Compose file. For external services (e.g., a cloud database), implement application‑level retry logic or use tools likewait-for-it.shinside the container entrypoint.
Conclusion
depends_on is a cornerstone of reliable Docker Compose stacks, but its true power in production comes from moving beyond simple start‑order guarantees. By defining health checks and using condition: service_healthy or condition: service_completed_successfully, you transform it into a readiness‑aware orchestrator that prevents the most common startup failures. When paired with the --wait flag and thoughtful timeout strategies, you can build production deployments on a single host that are predictable, self‑healing, and safe for automated CI/CD workflows. For multi‑node Swarm clusters, plan to replace depends_on with retry mechanisms, but for countless single‑node production workloads, these patterns deliver the reliability you need.