← Back to DevBytes

Docker Compose Depends On: Production Guide

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:

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:

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:

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

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles