---
title: Docker Compose depends_on Not Waiting? Fix It (2026)
description: 'Docker Compose depends_on not waiting for your service to be ready? Fix it with condition: service_healthy, a proper healthcheck, and start_period in 2026.'
url: https://nerdleveltech.com/docker-compose-depends-on-not-waiting
language: en
date: '2026-07-27'
category: cloud-devops
tags:
  - docker compose
  - depends_on
  - healthcheck
  - service_healthy
  - containers
  - cloud-devops
  - startup order
markdown_url: https://nerdleveltech.com/docker-compose-depends-on-not-waiting.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/docker-compose-depends-on-not-waiting'
terms: https://nerdleveltech.com/llms.txt
---

# Docker Compose depends_on Not Waiting? Fix It (2026)

Plain `depends_on` waits only until the dependency's **container is running**, not until the service inside is **ready** — so your app races the database and crashes. The fix: the long syntax with `condition: service_healthy` plus a real `healthcheck` on the dependency.[^1][^2]

**TL;DR:** `depends_on: [db]` starts `db` before `web` but does not wait for Postgres to accept queries. Switch to the long form (`depends_on: { db: { condition: service_healthy } }`) and give `db` a `healthcheck` such as `pg_isready`. Set `start_period` generously so a slow-booting database isn't marked unhealthy too early. If you see `dependency failed to start: container ... is unhealthy`, the dependency's healthcheck failed or is missing. `depends_on` is honored by `docker compose up` but ignored by Swarm's `docker stack deploy`.

## What you'll learn

- Why `depends_on` doesn't wait for your service to be *ready*
- The difference between `service_started`, `service_healthy`, and `service_completed_successfully`
- How to add a `healthcheck` and gate startup on it (with a working example)
- Why a container gets stuck in `health: starting` and never proceeds
- What `dependency failed to start: container is unhealthy` means and how to fix it
- Which `interval`, `timeout`, `retries`, `start_period`, and `start_interval` values to use
- Whether `depends_on` works with `docker compose run` and `docker stack deploy`
- When to reach for `wait-for-it.sh` or app-level retries instead

## Why isn't docker compose depends_on waiting for my service?

Because "started" is not "ready." The official Docker documentation is explicit: "On startup, Compose does not wait until a container is 'ready', only until it's running."[^2] With the short syntax, Compose creates the dependency first and then immediately starts the dependent — it does not wait for the dependency to become healthy.[^1]

That distinction matters most for stateful services. A Postgres container reports "running" within milliseconds, but the database process needs additional time before it accepts SQL. Your app connects into that gap and dies. This short form is the trap:

```yaml
services:
  web:
    build: .
    depends_on:
      - db     # only waits for the container to START, not to be READY
  db:
    image: postgres:18
```

Compose guarantees `db` is created before `web`, and that's all. As the docs put it, "With short syntax, Compose does not wait for dependency services to be 'healthy' before starting a dependent service."[^1] To wait for readiness you need the long syntax plus a healthcheck.

## What's the difference between service_started, service_healthy, and service_completed_successfully?

They are the three `condition` values of the long `depends_on` syntax, and they define what "satisfied" means for a dependency:[^1][^2]

- **`service_started`** — the dependency's container is running. This is identical to the short syntax: order only, no readiness.
- **`service_healthy`** — the dependency's `healthcheck` has reported **healthy**. Use this for databases, brokers, and caches that need time to warm up.
- **`service_completed_successfully`** — the dependency ran to completion and **exited 0**. Use this for one-shot init/migration jobs that must finish before the app starts.

| Condition | Waits for | Needs a healthcheck? | Typical use |
|---|---|---|---|
| `service_started` | Container running | No | Loosely coupled services |
| `service_healthy` | Healthcheck passes | **Yes** | Databases, brokers, caches |
| `service_completed_successfully` | Exit code 0 | No | Migrations, seeders, init jobs |

Only `service_healthy` waits for actual readiness, and it works **only if the dependency defines a healthcheck**. Without one, there is no health state to satisfy — which is the single most common reason startup gating "does nothing."[^1]

## How do I make depends_on wait until a service is healthy?

Two steps: add a `healthcheck` to the dependency, then reference it with `condition: service_healthy`. This is Docker's own canonical example, with `web` waiting on a healthy `db` and a started `redis`:[^2]

```yaml
services:
  web:
    build: .
    depends_on:
      db:
        condition: service_healthy
        restart: true
      redis:
        condition: service_started
  redis:
    image: redis
  db:
    image: postgres:18
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s
```

Now Compose waits for the `db` healthcheck to pass before it creates `web`.[^2] The `pg_isready` probe returns success only when Postgres is genuinely accepting connections, so the race disappears. The `$$` escapes the variable so Compose passes `$POSTGRES_USER` through to the shell inside the container rather than interpolating it itself.

The optional `restart: true` sub-option (introduced in Docker Compose 2.17.0) means that if `db` is restarted by an explicit Compose operation such as `docker compose restart`, `web` is restarted too, so it re-establishes its connections.[^1] A related sub-option, `required: false` (Docker Compose 2.20.0), downgrades a missing dependency from an error to a warning.[^1] All of this is current as of Docker Compose **v5.3.1**, released July 7, 2026.[^3]

## Why does my container stay stuck in "health: starting"?

A container sits in `health: starting` until its first healthcheck passes (or until `start_period` ends and failures begin to count). If a dependent never starts and the dependency is stuck "starting," the usual causes are:

- **The dependency has no `healthcheck` at all**, but something references it with `condition: service_healthy`. With no probe, it can never reach `healthy`, so the gate waits — or fails — forever.[^1]
- **The healthcheck command is wrong** — the binary isn't in the image, or the test never returns exit 0.
- **`start_period` is too short**, so a database that needs 40 seconds to initialize is marked unhealthy before it finishes.

Docker's health states are `starting`, `healthy`, and `unhealthy`, driven by the probe's exit code: **`0` means healthy, `1` means unhealthy**. Failures that happen *during* `start_period` don't count toward the retry limit, and a success during that window ends the grace period early. Check the current state with `docker inspect --format '{{.State.Health.Status}}' <container>` or watch it live in `docker compose ps`.

## What does "dependency failed to start: container is unhealthy" mean?

It means a `condition: service_healthy` gate failed: the dependency's healthcheck exhausted its `retries` without ever passing (or the dependency has no healthcheck to satisfy the condition).[^4] Modern Compose fails fast here rather than hanging indefinitely. To fix it, work through the dependency itself:

1. **Confirm the dependency actually has a `healthcheck`.** If it's referenced with `service_healthy` but defines no probe, add one.
2. **Run the probe by hand** inside the container: `docker compose exec db pg_isready -U postgres`. If it fails there, your test command is wrong.
3. **Raise `start_period`** for slow starters. A cold Postgres or a JVM service often needs 60s or more before its first successful probe.
4. **Loosen `retries`/`interval`** if the service is healthy but flaps under load during boot.

The error is a symptom of the *dependency's* health, not the dependent's — always debug the service named in the message.

## What interval, retries, and start_period should I use?

Start from Docker Engine's HEALTHCHECK defaults and tune from there. The defaults are `interval` **30s**, `timeout` **30s**, `retries` **3**, and `start_period` **0s**.[^5] A default `start_period` of zero is why databases get flagged unhealthy on cold boots — always set it.

Sensible starting points by service type:

- **Databases (Postgres, MySQL):** `interval: 10s`, `timeout: 5s`, `retries: 5`, `start_period: 30s–60s`.
- **HTTP APIs:** a `curl -f http://localhost:PORT/health` test, `interval: 15s`, `timeout: 5s`, `retries: 3`, `start_period: 20s`.
- **Heavy JVM / cold-cache services:** push `start_period` to 60s–120s.

There's also `start_interval`, which sets how often the probe runs *during* `start_period` — so you can poll every second at boot and back off to the slower `interval` once healthy. It requires **Docker Compose 2.20.2+ and Docker Engine 25.0+**:[^6]

```yaml
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s        # steady-state cadence
      timeout: 5s
      retries: 5
      start_period: 60s    # grace window; failures here don't count
      start_interval: 1s   # poll every 1s during the grace window
```

Prefer the exec form `["CMD", "bin", "arg"]` when you don't need a shell, and `["CMD-SHELL", "..."]` when you need pipes or environment variables. Use `test: ["NONE"]` to disable a healthcheck an image inherited from its base.[^5]

## Does depends_on work with docker compose run and docker stack deploy?

Not everywhere. `depends_on` is a Compose-orchestration feature, and two contexts don't honor it the way `docker compose up` does:

- **`docker stack deploy` (Swarm mode) ignores `depends_on` entirely** — with no warning. Swarm has no concept of ordered startup, so declared dependencies are silently dropped.[^7] In Swarm you must make services resilient to missing dependencies instead.
- **`docker compose run <service>`** starts the depends_on services but does **not** wait for their healthchecks to report healthy, and `--no-deps` skips starting them at all.[^8]

`depends_on` also only orders services **within a single Compose project** — it can't sequence containers you launch with separate `docker run` commands or in another project.

## depends_on vs wait-for-it.sh vs app-level retries

All three solve "wait for readiness," at different layers:

- **`depends_on` + `service_healthy`** is the built-in, declarative option. It's the right default because the readiness check lives with the dependency and every dependent inherits it.[^2]
- **Wrapper scripts** like `wait-for-it.sh`, `wait-for`, or `dockerize` block on an open TCP port before exec'ing your app. They're simple, but port-open is weaker than a real healthcheck: Postgres accepts TCP connections before it accepts queries, so a port probe can pass too early.
- **Application-level retries** are the most robust layer. Docker's own long-standing guidance is to make the app resilient — reconnect with backoff — because startup order alone can't guarantee readiness across mid-run restarts. A [circuit breaker or retry wrapper](https://nerdleveltech.com/nodejs-circuit-breaker-opossum-typescript-tutorial) belongs in production regardless of your Compose config.

Best practice in 2026: use `service_healthy` for clean local and CI startup, and *still* add retry logic in the app so a database failover at 3 a.m. doesn't take the service down.

## FAQ

**Why doesn't depends_on wait for my database to be ready?**
The short syntax (`depends_on: [db]`) only waits for the container to start running, not for the service to accept connections.[^1] Use the long syntax with `condition: service_healthy` plus a healthcheck on the database.

**What's the difference between service_started and service_healthy?**
`service_started` waits only for the container to be running (same as short syntax). `service_healthy` waits until the dependency's healthcheck reports healthy, which requires the dependency to define a `healthcheck`.[^1]

**How do I make Docker Compose wait for a service to be healthy?**
Add a `healthcheck` to the dependency (for Postgres, `pg_isready`), then reference it with `depends_on: { db: { condition: service_healthy } }`. Compose blocks the dependent until the probe passes.[^2]

**Why is my container stuck in "health: starting"?**
Its healthcheck never returns exit 0 — usually a missing healthcheck, a wrong test command, or a `start_period` too short for a slow-booting service. States are `starting`, `healthy`, `unhealthy`; exit `0` is healthy, `1` is unhealthy.[^5]

**How do I fix "dependency failed to start: container is unhealthy"?**
The dependency's healthcheck failed its retries or doesn't exist. Add or fix the healthcheck, run the probe manually to confirm it passes, and raise `start_period` for slow starters.[^4]

**What start_period should I use for Postgres?**
Set `start_period` to 30–60s so initialization failures don't count against `retries`; the Engine default is `0s`, which is why cold databases get flagged too early.[^5]

**Does depends_on work in Docker Swarm?**
No. `docker stack deploy` ignores `depends_on` with no warning.[^7] Make services resilient to unavailable dependencies instead.

## Bottom line

`depends_on` on its own only orders container starts — it never waits for readiness. Move to the long syntax, gate on `condition: service_healthy`, give every stateful dependency a real `healthcheck` with a generous `start_period`, and keep retry logic in the app for the cases Compose can't cover. If you're wiring this into a production stack, pair it with a hardened [Docker Compose HTTPS setup](https://nerdleveltech.com/caddy-reverse-proxy-docker-compose-production-https-tutorial) and treat healthchecks the same way you'd treat [Kubernetes readiness probes in a zero-downtime rollout](https://nerdleveltech.com/kubernetes-zero-downtime-deployments-tutorial). For database dependencies specifically, a resilient [connection-pooling layer](https://nerdleveltech.com/production-postgres-pooling-pgbouncer-supabase-supavisor-tutorial) matters as much as startup order.

[^1]: Docker Docs — "Define services in Compose," `depends_on` (short and long syntax; `restart` introduced in 2.17.0, `required` in 2.20.0). https://docs.docker.com/reference/compose-file/services/#depends_on
[^2]: Docker Docs — "Control startup and shutdown order in Compose" (Compose does not wait until a container is "ready"; `service_started` / `service_healthy` / `service_completed_successfully`; canonical example). https://docs.docker.com/compose/how-tos/startup-order/
[^3]: docker/compose GitHub Releases — v5.3.1, released July 7, 2026 (current stable). https://github.com/docker/compose/releases/tag/v5.3.1
[^4]: docker/compose issue #11474 and community reports — "dependency failed to start: container ... is unhealthy" occurs when a `service_healthy` dependency's healthcheck fails its retries or is missing. https://github.com/docker/compose/issues/11474
[^5]: Docker Docs — Dockerfile `HEALTHCHECK` reference (defaults: interval 30s, timeout 30s, retries 3, start-period 0s; exit 0 = healthy, 1 = unhealthy; `CMD` vs `CMD-SHELL` vs `NONE`). https://docs.docker.com/reference/dockerfile/#healthcheck
[^6]: docker/compose issue #10830 — `start_interval` requires Docker Compose 2.20.2+ and Docker Engine 25.0+. https://github.com/docker/compose/issues/10830
[^7]: moby/moby issue #40364 and docker/compose #13819 — `depends_on` is ignored by `docker stack deploy` in Swarm mode. https://github.com/moby/moby/issues/40364
[^8]: docker/compose issue #7681 — `docker compose run` starts dependencies but does not wait for their healthchecks; `--no-deps` skips them. https://github.com/docker/compose/issues/7681
