Docker Compose in Depth

Docker Intermediate/Advanced
Chapter 3 ยท Docker Compose in Depth

๐Ÿงญ Docker Compose in Depth

docker1's Compose chapter (docker_10) covered running a multi-container app. This chapter covers the features that turn a working Compose setup into a production-ready one: environment-specific config, optional services, real readiness checks, and predictable networking.

Override Files for Environment-Specific Config

Rather than duplicating an entire docker-compose.yml per environment, Compose merges a base file with an override file โ€” docker-compose.override.yml is picked up automatically, or explicit files can be layered with -f.

# docker-compose.yml (base, shared) services: api: image: myapp:latest environment: NODE_ENV: production # docker-compose.override.yml (auto-merged locally) services: api: build: . environment: NODE_ENV: development volumes: - .:/app # explicit layering for a different environment docker compose -f docker-compose.yml -f docker-compose.prod.yml up

Profiles

The profiles key marks a service as optional โ€” it only starts when its profile is explicitly activated, keeping services like a debug tool or a one-off seed job out of the way during normal up runs.

services: seed-data: image: myapp-seed:latest profiles: ["debug"] # normal `docker compose up` skips seed-data entirely # explicitly activating it: docker compose --profile debug up

Healthchecks

A container can be running while the application inside it hasn't actually finished starting up โ€” a healthcheck block tells Docker how to verify genuine readiness, not just process liveness.

services: db: image: mysql:8 healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s retries: 10 start_period: 30s

depends_on With Conditions

Plain depends_on: [db] only waits for the db container to start โ€” not for MySQL inside it to actually be accepting connections. This is one of Compose's most common real bugs: an app container starts, tries to connect to a database that's technically running but not yet ready, and crashes.

services: api: depends_on: db: condition: service_healthy

depends_on Without vs. With a Condition

Plain depends_on

Waits only for the db container process to start โ€” the app can still connect before MySQL is ready, and crash.

depends_on with service_healthy

Waits for the healthcheck (this chapter's mysqladmin ping) to actually pass first โ€” the app only starts once the database is genuinely ready.

Explicitly Named Networks & Volumes

By default, Compose auto-generates network/volume names prefixed with the project directory name. Giving them explicit names makes them predictable to reference from outside Compose, or to share deliberately across multiple Compose files/projects.

networks: backend: name: shop-backend-network volumes: db-data: name: shop-db-data
FeaturePurpose
Override filesLayer environment-specific config on a shared base, without duplicating the whole file
profilesKeep optional services out of normal up runs
healthcheckDistinguish "running" from actually ready
depends_on: condition: service_healthyWait for real readiness, not just container start
Named networks/volumesPredictable references outside Compose or across projects

๐Ÿ’ป Coding Challenges

Challenge 1: Add a Healthcheck

Write a healthcheck block for a Redis service (image redis:7) that runs redis-cli ping every 5 seconds, allowing up to 5 retries.

Goal: Practice writing a healthcheck's test/interval/retries fields for a real service.

โ†’ Solution

Challenge 2: Fix a depends_on Race Condition

A team's api service has depends_on: [db] and intermittently crashes on startup with a "connection refused" error against the database. Diagnose the cause and fix the Compose file.

Goal: Practice recognizing and fixing the classic depends_on-without-a-condition race condition.

โ†’ Solution

Challenge 3: Design an Optional Debug Service

Add a service called adminer (a database admin UI) to a Compose file such that it never starts during a normal docker compose up, but can be started deliberately when needed.

Goal: Practice using profiles to keep an optional tool out of the default startup path.

โ†’ Solution

โš ๏ธ Gotcha: Override Files Don't Merge Every Key the Same Way

It's easy to assume an override file always merges additively with the base file โ€” but that's not uniformly true. Keys like environment genuinely merge (the override's variables are added to/override individual keys from the base). Keys like command or entrypoint, however, typically replace the base's value entirely rather than merging with it โ€” an override file that only intended to tweak one argument can silently discard the base's entire command if it redefines that key at all. Always check Compose's documented merge behavior for the specific key being overridden, rather than assuming every override is purely additive.

๐ŸŽฏ What's Next

The next chapter is Container Networking Beyond Basics โ€” bridge vs. host vs. overlay networks, custom networks for service isolation, and DNS-based service discovery between containers.