Chapter 16Lesson 01~105 minutes

Advanced Compose: Profiles, depends_on, Health Conditions, Overrides, Includes, Watch, and Multi-File Design: Concepts, Architecture, and Mental Model

Scale Compose safely by reasoning about ordered files, include boundaries, profiles, dependency health, normalized configuration, and watch-driven reconciliation before changing project resources.

Compose modelProfilesMerge/includeHealth conditionsWatch

Learning objectives

  • Explain how multiple Compose files, include models, profiles, interpolation, and dependency conditions become one normalized application model.
  • Distinguish merge semantics from include semantics, especially their different relative-path rules and ownership boundaries.
  • Explain what profiles activate, what they do not activate, and why targeting a profiled service is not equivalent to enabling the whole profile.
  • Separate container start ordering from dependency readiness and from resilient application-level retry behavior.
  • Use read-only Compose commands to prove active files, profiles, normalized services, paths, health conditions, and project resources before execution.
Chapter 16 principle. Advanced Compose features are model transformations. The safe sequence is always inputs → normalized model → predicted resource changes → reconciliation → runtime/application evidence. If you cannot explain the normalized model, do not trust your intuition about what up will create.

1. Why advanced Compose becomes surprising

A single compose.yaml is easy to reason about. A real development repository often adds an override for local source, an optional database admin tool, test-only services, reusable subprojects, health-gated dependencies, and a watch loop. The complexity is not primarily YAML syntax; it is composition order and state ownership.

Two files can both mention the same service, a path can resolve relative to a different file than expected, a profile can silently omit a service, and a healthy container can still be a poor proxy for end-to-end application readiness. The answer is not more trial-and-error up commands. The answer is to normalize and inspect first.

2. Mental model: sources become one application model before Engine reconciliation

Think of advanced Compose as a compilation pipeline. Ordered files are parsed and merged. Included models are loaded with their own project-directory context. Variables are interpolated. Profiles select service participation. The resulting application model contains dependency conditions and watch rules. Only then does Compose reconcile the model with project-scoped Docker resources.

Advanced Compose model pipeline
flowchart TD
  A[Base compose.yaml] --> M[Model assembly]
  B[Ordered override files] --> M
  C[Included Compose models] --> M
  D[Interpolation inputs] --> M
  E[Active profiles] --> M
  M --> N[docker compose config normalized model]
  N --> G[Dependency graph conditions + health]
  G --> R[Project reconciliation containers networks volumes]
  R --> W[Optional watch loop sync / rebuild / sync+restart]
  R --> O[Runtime evidence ps logs inspect health]
            

The critical causal boundary is between model assembly and runtime reconciliation. docker compose config can validate and render the former without creating containers. That makes it the first diagnostic tool for merge, interpolation, profile, include, and path questions.

3. State inventory: what must be recorded

State What it means Evidence Typical mistake
Ordered Compose files The sequence supplied with -f; later values can override/extend earlier ones. Command line, source revision, config output. Reading only the last YAML file and assuming it is standalone.
Include source A Compose application imported as a model after local file merge. include: declaration and normalized model. Treating include as ordinary YAML merge.
Active profiles Set of profile names participating in service selection. --profile/COMPOSE_PROFILES, config --profiles. Assuming all services in the file always start.
Normalized model What Compose actually resolved after merge, include, interpolation, and profile logic. docker compose ... config. Treating source fragments as final truth.
Dependency condition When a dependent may be created/started relative to its dependency. Normalized depends_on, health state. Equating ordering with application resilience.
Watch action Development reaction to host file changes. develop.watch plus watch event/output. Syncing secrets or generated dependencies unintentionally.
Resolved paths Actual build contexts, bind paths, env files, and watch paths after path rules. Normalized config plus filesystem inspection. Assuming every override path is relative to its own file.
Project resources Concrete Engine objects owned by the Compose project. Project labels, IDs, ps, network/volume inspection. Confusing model validity with successful runtime state.

4. Merge files: order matters, and paths belong to the base file

When multiple files are supplied with -f, Compose merges them in order. Matching scalar-like values are generally overridden by later files while sequences/maps follow Compose-specific merge rules. The important operational rule is simpler: inspect the merged model rather than inferring it from YAML appearance.

Current Docker documentation resolves relative paths in merged files from the base Compose file (the first file supplied). This applies to build contexts, environment files, bind mounts, and similar resources. An override stored in another directory does not automatically make its paths relative to that override.

docker compose \
  -f compose.yaml \
  -f overlays/compose.dev.yaml \
  config --quiet

docker compose \
  -f compose.yaml \
  -f overlays/compose.dev.yaml \
  config > evidence/compose.dev.normalized.yaml

The first command changes no Engine state. It asks Compose to validate model construction. The second records exactly what Compose believes the development model is.

5. Include: compose application models, not fragments

include exists for modular application composition. Each included Compose model has its own project-directory context for relative paths, then its resources are copied into the current model. This solves an ownership problem that ordinary overrides intentionally do not solve.

Use include when a component is maintained as a meaningful Compose sub-model with its own directory and files. Use an override when you are intentionally changing the same project model, such as adding development mounts or changing a port. Compose warns on include resource-name conflicts instead of silently merging conflicting definitions.

6. Profiles select optional services, not arbitrary YAML blocks

A service without profiles is active by default. A service with one or more profiles participates only when a matching profile is enabled—unless that service is explicitly targeted by a command. Targeting a profiled service auto-enables that service's profile context for the target and its declared dependencies; it does not automatically start every sibling service that happens to share the profile.

docker compose config --profiles
# Example activation:
docker compose --profile debug config --services
# Enable all declared profiles for inspection:
docker compose --profile "*" config --services

Profiles are best for optional local tools, debugging, or test helpers. Core services should usually remain unprofiled so a plain docker compose up produces the application's baseline model.

7. depends_on: ordering can be health-aware, but it is not a distributed resilience mechanism

Current Compose supports dependency conditions including service_started, service_healthy, and service_completed_successfully. With service_healthy, Compose waits for the dependency's healthcheck to report healthy before starting the dependent service.

That is useful startup coordination. It does not eliminate the need for application-level retry/backoff when a dependency later restarts, the network changes, credentials rotate, or a remote service becomes unavailable. A robust application should tolerate dependency churn rather than relying on one startup gate.

8. Compose Watch is a development reconciliation loop

Watch monitors local source paths for services built from local source. Current documentation requires Compose 2.22.0+ and documents sync, rebuild, and sync+restart. Watch also has prerequisites inside the service image (stat, mkdir, rmdir) and needs the configured container user to write to sync targets.

Watch is not a deployment controller. It is a developer feedback loop. Its safety depends on narrow source paths, explicit targets, ignore rules, and excluding secret/configuration trees that should not be copied into a running container.

9. Read-only evidence-first workflow

docker version
docker info
docker context show
docker compose version

# Validate and render without creating resources:
docker compose -f compose.yaml -f compose.dev.yaml config --quiet
docker compose -f compose.yaml -f compose.dev.yaml config --services
docker compose -f compose.yaml -f compose.dev.yaml config --profiles
docker compose -f compose.yaml -f compose.dev.yaml config > evidence/model.yaml

# If a project already exists:
docker compose -p da-compose16 ps -a
docker ps -a --filter label=com.docker.compose.project=da-compose16
docker network ls --filter label=com.docker.compose.project=da-compose16
docker volume ls --filter label=com.docker.compose.project=da-compose16

Only after the normalized model matches intent should up be allowed to reconcile Engine resources.

10. Five invariants for the rest of the chapter

  1. Normalize before execute. Treat merged YAML fragments as source, not resolved truth.
  2. Paths have ownership rules. Merged files use the base file; included models can keep their own project directory.
  3. Profiles control optional services. They do not make dependencies magically available.
  4. Health is scoped evidence. It proves only what the healthcheck actually tests.
  5. Watch is a dev loop. Do not let it become an uncontrolled secret/configuration synchronization mechanism.
Next lesson

Next: Guided Hands-On Workflow and Core Operations

Build a disposable base + dev override + included tools model, activate a profile, health-gate a service, and exercise watch while recording the normalized model before every runtime change.

Knowledge check

Why is docker compose config the first tool for a suspected override problem?

Where are relative paths in an ordinary multi-file -f merge resolved?

How does include differ from an override file?

Does service_healthy remove the need for application retry logic?

Why can Compose Watch be dangerous if configured too broadly?

Official references and version notes

Version baseline checked 2026-09-21.

Upstream Compose is v5.5.1. include requires Compose 2.20.3+ in current Docker documentation; Compose Watch requires 2.22.0+. Because installations can lag or be vendor-packaged, every lab records docker compose version and validates features with docker compose config before changing runtime state.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.