Compose in Depth: Services, depends_on and Healthchecks

Intermediate
13 min

Compose in Depth: Services, depends_on and Healthchecks

The introductory Compose lesson showed a working stack. This lesson explains the service keys you will actually configure in every project, then focuses on the problem that breaks most first Compose files: the API starts before the database is ready. After it you will use depends_on with conditions, write healthchecks that mean "ready" rather than "running", and know the day-to-day command set.

The Compose File

Compose reads compose.yaml (the older docker-compose.yml still works) from the current directory. The top-level version: key is obsolete and can be removed; name: sets the project name that prefixes containers, networks and volumes. The important service keys are:

| Key | Purpose | |-----|---------| | image / build | Use an existing image, or build one (context, dockerfile, args, target) | | ports | Publish "host:container"; long form adds protocol and host_ip | | environment / env_file | Variables as a map, or loaded from files | | volumes | Named volumes, bind mounts (./src:/app/src:ro) and anonymous volumes | | networks | Attach to named networks; omit to use the project default | | command / entrypoint | Override CMD and ENTRYPOINT | | restart | no, always, on-failure, unless-stopped | | deploy.resources.limits | cpus and memory caps, honoured by docker compose too |

Compose creates a user-defined network for the project, so services reach each other by service name; db:5432 in the sample resolves to the database container.

depends_on and Start Order

The short form of depends_on only controls start order: Compose starts db before api, but "started" means the container process exists, not that PostgreSQL is accepting connections. The long form adds a condition:

yaml
depends_on: db: condition: service_healthy # wait for the healthcheck to pass migrate: condition: service_completed_successfully # wait for a one-off job to exit 0 cache: condition: service_started # the short-form behaviour required: false # do not fail if cache is missing from this run

service_completed_successfully is the clean way to run database migrations: a migrate service runs once, exits 0, and only then does api start. When you docker compose up api, Compose also starts everything api depends on, and docker compose down stops them in reverse order.

Healthchecks in Compose

A healthcheck block overrides or adds to the image's HEALTHCHECK. The test must exist in the container's image:

yaml
services: db: image: postgres:17 healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d shop"] interval: 5s timeout: 3s retries: 10 start_period: 10s cache: image: redis:7-alpine healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s api: build: ./api healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"] interval: 15s start_period: 20s

CMD runs the command directly; CMD-SHELL runs it through sh -c, which allows pipes and ||. Set start_period generously for services that need time to initialise, and keep interval short in development so service_healthy resolves quickly. docker compose ps shows healthy or unhealthy per service.

Even with a passing healthcheck, applications should still retry their first connection: a database can restart later, and a resilient client handles that on its own.

The Daily Command Set

bash
docker compose up -d --build # build if needed, start in the background docker compose ps # status and health of every service docker compose logs -f api # follow one service's logs docker compose exec api sh # shell into a running service docker compose run --rm api npm test # one-off container with the service config docker compose restart api docker compose down # stop and remove containers and networks docker compose down -v # also remove named volumes (data loss) docker compose config # print the fully resolved file

up recreates only services whose configuration or image changed; add --force-recreate to recreate everything. docker compose config is the fastest way to debug interpolation and merge problems because it prints exactly what Compose will run.

Common Mistakes

  • Using short-form depends_on and adding sleep 10 to the API's command instead of a healthcheck condition.
  • Writing test: "curl ..." for an image that does not contain curl; the check fails forever.
  • Running docker compose down -v casually and deleting the database volume.
Quick Quiz
Question 1 of 3

What does short-form `depends_on: [db]` guarantee?

Key Takeaways

  • compose.yaml needs no version:; name: sets the project prefix and services share a default network.
  • Short-form depends_on orders start-up only; the long form with condition: waits for health or completion.
  • Healthchecks must use tools present in the image; start_period prevents false failures during boot.
  • service_completed_successfully runs migrations before the API starts.
  • up -d --build, ps, logs -f, exec, run --rm, down and config cover daily work; down -v deletes data.

Next lesson: Compose Profiles, Override Files and Multiple Environments — run the same stack differently in development, testing and production.

Compose in Depth: Services, depends_on and Healthchecks - Docker | CodeYourCraft | CodeYourCraft