Chapter 16Lesson 04~125 minutes

Advanced Compose: Profiles, depends_on, Health Conditions, Overrides, Includes, Watch, and Multi-File Design: Diagnostics, Failure Modes, Security, and Performance

Diagnose advanced Compose failures by preserving normalized configuration and first-failure evidence before correcting merge, path, profile, health, watch, or reconciliation mistakes.

DiagnosticsNormalized configHealthWatch safetyEvidence

Learning objectives

  • Preserve the ordered files, profile set, normalized model, IDs, health state, and watch evidence before changing a failing project.
  • Diagnose path-resolution surprises, omitted profile services, and merge mistakes at model-construction time.
  • Distinguish a healthcheck that tests the wrong layer from a healthy dependency whose client still lacks resilience.
  • Detect unsafe Watch rules that sync sensitive or high-churn trees and correct them without broad privilege or destructive cleanup.
  • Apply the least destructive correction and rerun only the smallest scope needed to validate the hypothesis.
Diagnostic rule. Do not “fix” advanced Compose by deleting everything and starting over. A recreate can erase the exact model/runtime evidence that tells you whether the fault was merge, profile selection, path resolution, health logic, watch behavior, image identity, or application state.

1. Evidence-first diagnostic sequence

  1. Preserve first-failure command output, timestamps, ordered -f files, active profiles, and relevant environment inputs.
  2. Capture docker version, docker info, context, and docker compose version.
  3. Run docker compose ... config --quiet and save the normalized model.
  4. Confirm exact image/build identity and project labels.
  5. Inspect container/process/health state.
  6. Inspect mounts, path ownership, networks, and resources only as relevant.
  7. Inspect external dependency evidence if the model/runtime is correct.
  8. Change the smallest causal input and rerun only the affected scope.
docker compose -f compose.yaml -f overlays/compose.dev.yaml config   > evidence/first-failure.normalized.yaml

docker compose -f compose.yaml -f overlays/compose.dev.yaml ps -a   > evidence/first-failure.ps.txt

docker ps -a --filter label=com.docker.compose.project=da-compose16   --no-trunc > evidence/first-failure.engine-containers.txt

2. Intentionally broken example: an override path resolved from the wrong directory

Suppose overlays/compose.dev.yaml contains:

services:
  app:
    build:
      context: ../app   # WRONG assumption: "relative to overlays/"

If the base file is the repository root compose.yaml, ordinary multi-file merge resolves this relative path from the base file. The result may point outside the intended project or fail with a missing build context.

Preserve the failure:

docker compose -f compose.yaml -f overlays/compose.dev.yaml config   > evidence/bad-path.normalized.yaml 2> evidence/bad-path.stderr.txt || true

grep -n "context:" evidence/bad-path.normalized.yaml || true

Interpretation: this is model/path state, not an Engine networking, runtime, or image-store failure. Correct the path to ./app (relative to the base file), normalize again, then build only the affected app if needed.

3. “The service is missing” can be correct profile behavior

A debug service with profiles: [debug] will not appear in ordinary baseline execution. Before assuming Compose ignored the service, compare model views:

docker compose config --services
docker compose --profile debug config --services
docker compose config --profiles

If the service appears only with the profile active, there is no daemon failure. The decision layer is profile selection. If another service references a profile-gated service in a way that makes the model invalid under the active profile set, Compose should report the configuration inconsistency rather than silently inventing the dependency.

4. A healthcheck can be healthy and still test the wrong contract

A common anti-pattern is a database healthcheck that only proves the process exists while the dependent application requires a specific database/schema/authentication path. Conversely, an overly deep healthcheck can create load or require credentials that should not be exposed merely for liveness.

Preserve:

docker inspect <dependency-container>   --format '{{json .State.Health}}' > evidence/dependency-health.json

docker compose logs --timestamps dependency app   > evidence/dependency-app-logs.txt

Then compare what the healthcheck proves with what the app requires. Correct the probe or the application's retry/error handling; do not merely increase retries until the symptom disappears.

5. Watch loops and accidental sensitive synchronization

Watch should target a narrow source tree. Dangerous patterns include watching the entire repository when it contains .env, generated credentials, evidence artifacts, build outputs, package caches, or files that the container itself rewrites into a synchronized path.

Example of a poor rule:

develop:
  watch:
    - action: sync
      path: .
      target: /workspace

Repair it by selecting only the source directory and ignoring generated/high-churn content:

develop:
  watch:
    - action: sync
      path: ./app
      target: /site
      ignore:
        - Dockerfile
        - "*.key"
        - "*.pem"

Do not test this lesson with real secrets. The patterns are illustrative; production secret handling belongs to explicit secret/configuration mechanisms, not Watch sync.

6. YAML looks plausible, normalized model is wrong

Compose merge behavior is field-aware; it is not safe to reason from generic YAML merge intuition. If an override adds ports, volumes, environment keys, or commands, inspect the normalized service:

docker compose -f compose.yaml -f compose.dev.yaml config app
# Or capture all services and compare revisions:
docker compose -f compose.yaml -f compose.dev.yaml config > evidence/model-a.yaml
docker compose -f compose.yaml -f compose.test.yaml config > evidence/model-b.yaml

Fix the source model, not the runtime container. Editing a running container cannot repair the declarative cause.

7. Circular or fragile dependency design

If service A waits for B while B operationally depends on A, no amount of longer startup delay makes the architecture robust. Use dependency graphs to represent genuine startup contracts and redesign circular readiness assumptions. For eventual consistency, let applications retry and converge instead of encoding every relationship as a hard startup gate.

8. Reconciliation evidence: did Compose recreate the object?

docker compose ps -q app > evidence/app-id.before.txt
docker image inspect da-compose16-app:lab --format '{{.Id}}' > evidence/app-image.before.txt

# Apply only the intended corrected model scope:
docker compose -f compose.yaml -f overlays/compose.dev.yaml up -d app

docker compose ps -q app > evidence/app-id.after.txt
docker image inspect da-compose16-app:lab --format '{{.Id}}' > evidence/app-image.after.txt

diff -u evidence/app-id.before.txt evidence/app-id.after.txt || true

A changed container ID proves recreation. An unchanged ID can prove no replacement happened, but still inspect the relevant runtime state before concluding the application is correct.

9. Troubleshooting shortcuts to reject

Shortcut Why it hides the cause Safer alternative
Delete and recreate the whole project immediately Destroys first-failure object/health/log evidence. Capture normalized model and targeted runtime evidence first.
Enable every profile May add unrelated/debug resources and change the failure surface. Activate only the profile relevant to the hypothesis.
Move files until relative paths “work” Creates accidental coupling to working directory. Use documented base-file/include path rules and verify normalized paths.
Broaden Watch to repository root Can sync secrets/generated trees and trigger loops. Narrow path, target, and ignore.
Add privilege for a Watch permission error Expands host/container attack surface. Make target ownership/writability match the configured non-root user.

10. Smallest-scope correction checklist

  • Model error: fix YAML/input, rerun config; do not touch Engine state yet.
  • Profile error: change active profile set and inspect services before up.
  • Health error: correct probe or application retry, then recreate/restart only the affected service if necessary.
  • Watch rule error: stop Watch, narrow the rule, validate, resume only the affected service.
  • Image/build mismatch: record current digest/ID, rebuild only intended service, then compare identity.
Next lesson

Next: Checkpoint Lab

The checkpoint combines these ideas into one base/dev/test model. You will prove the normalized environment views, health-gated startup, profile resource boundaries, Watch behavior, and exact cleanup while preserving an evidence packet.

Knowledge check

A path in an override unexpectedly points outside the repository. Which layer should you diagnose first?

A profile-gated service is absent from ordinary up. Is that automatically a failure?

A dependency reports healthy but the app still cannot authenticate. What does that imply?

Why is syncing the repository root with Watch risky?

Why preserve the container ID before applying a fix?

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.