Advanced Compose: Profiles, depends_on, Health Conditions, Overrides, Includes, Watch, and Multi-File Design: Guided Hands-On Workflow and Core Operations
Build a disposable advanced Compose project with a base model, development override, profile-gated service, health dependency, included sub-model, and Compose Watch while inspecting normalized state first.
Learning objectives
- Create a disposable advanced Compose project with explicit project identity and bounded local-only resources.
- Split baseline and development concerns across ordered files and prove the merged result before starting containers.
-
Import a reusable tools model with
includeand activate its optional service through a profile. - Gate an application on a dependency healthcheck and inspect the difference between created, running, and healthy.
- Exercise Compose Watch on synthetic source and compare sync/rebuild behavior with ordinary project reconciliation.
1. Preflight: prove feature availability before writing runtime state
Create a clean workspace. POSIX commands work in Git Bash/WSL/Linux;
on PowerShell create equivalent files with an editor. Record the
installed versions because include and Watch have
explicit minimum-version histories.
mkdir -p da-compose16/{app,tools,overlays,evidence}
cd da-compose16
docker version > evidence/docker-version.txt
docker info > evidence/docker-info.txt
docker context show > evidence/docker-context.txt
docker compose version > evidence/compose-version.txt
docker buildx version > evidence/buildx-version.txt
# Prove no old lab project is active before reuse:
docker ps -a --filter label=com.docker.compose.project=da-compose16
docker network ls --filter label=com.docker.compose.project=da-compose16
2. Build a tiny writable, non-root development service
Create app/index.html:
<!doctype html>
<html><body><h1>Compose 16 baseline</h1><p>watch target: /site</p></body></html>
Create app/Dockerfile:
# syntax=docker/dockerfile:1
FROM busybox:1.36.1
WORKDIR /site
COPY --chown=65534:65534 index.html /site/index.html
USER 65534:65534
EXPOSE 8080
CMD ["httpd", "-f", "-p", "8080", "-h", "/site"]
This image is intentionally tiny but still includes the BusyBox utilities Watch needs. The sync target is owned by the runtime user; the lab does not solve write failures by adding broad privileges.
3. Create an included tools model with an optional profile
Create tools/compose.yaml:
services:
toolbox:
image: busybox:1.36.1
profiles: [debug]
command: ["sh", "-c", "echo toolbox-ready; exec sleep 3600"]
labels:
devops-academy.lab: compose16
The file is a complete Compose model, not a fragment. Its path
context belongs to tools/. The service is profile-gated
so normal application startup does not include it.
4. Base model: include tools, health-check dependency, and built app
Create compose.yaml:
name: da-compose16
include:
- ./tools/compose.yaml
services:
readiness:
image: busybox:1.36.1
command: ["httpd", "-f", "-p", "8090", "-h", "/www"]
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8090/"]
interval: 2s
timeout: 1s
retries: 10
volumes:
- ./readiness:/www:ro
labels:
devops-academy.lab: compose16
app:
build:
context: ./app
image: da-compose16-app:lab
depends_on:
readiness:
condition: service_healthy
ports:
- "127.0.0.1:18016:8080"
labels:
devops-academy.lab: compose16
Create the readiness document:
mkdir -p readiness
printf '%s
' 'ready' > readiness/index.html
5. Development override: add Watch without changing the baseline model
Create overlays/compose.dev.yaml:
services:
app:
environment:
APP_MODE: development
develop:
watch:
- action: sync
path: ./app
target: /site
initial_sync: true
ignore:
- Dockerfile
- action: rebuild
path: ./app/Dockerfile
-f, its relative paths are interpreted
relative to the base file (compose.yaml), not relative
to the overlays/ directory. That is why
./app is correct here.
6. Normalize before up
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
config --quiet
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
config > evidence/dev.normalized.yaml
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
config --services
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
config --profiles
Expected baseline services are readiness and
app. toolbox is declared but
profile-gated. Save the normalized file before execution so later
reconciliation can be compared with intended input.
7. Predict state changes before applying them
-
Prediction A: ordinary dev
upcreatesreadinessandapp, plus the project network, but nottoolbox. -
Prediction B: Compose creates the dependency
first and waits for its healthcheck before starting
app. -
Prediction C: enabling the
debugprofile addstoolboxwithout changing the source definition of the core services. -
Prediction D: a Watch
syncchanges files in the running app container without rebuilding the image; a Watchrebuildcan replace the app container/image.
8. Apply the dev model and inspect health-gated startup
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
up -d --build
docker compose -f compose.yaml -f overlays/compose.dev.yaml ps -a
docker inspect da-compose16-readiness-1 \
--format 'state={{.State.Status}} health={{if .State.Health}}{{.State.Health.Status}}{{end}}'
docker inspect da-compose16-app-1 \
--format 'id={{.Id}} image={{.Image}} state={{.State.Status}}'
docker ps -a --filter label=com.docker.compose.project=da-compose16
Do not hard-code container names in production automation; this lab uses them only as human-readable evidence with one replica. The project labels are the stronger ownership signal.
9. Activate and observe the optional profile
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
--profile debug \
up -d
docker compose -f compose.yaml -f overlays/compose.dev.yaml --profile debug ps -a
docker inspect da-compose16-toolbox-1 \
--format '{{json .Config.Labels}}' > evidence/toolbox-labels.json
The resource appeared because the profile became active. The normalized core model did not need to be duplicated into a separate project just to add a debugging helper.
10. Exercise Watch with narrow synthetic input
Run Watch in a second terminal:
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
watch app
In the first terminal, change only the synthetic page:
printf '%s
' '<!doctype html><html><body><h1>Compose 16 synced</h1></body></html>' > app/index.html
sleep 2
curl http://127.0.0.1:18016/
docker inspect da-compose16-app-1 --format 'container={{.Id}} image={{.Image}}' > evidence/app-after-sync.txt
A sync should update the target without requiring a new
image. Now change the Dockerfile with a harmless label to trigger
the rebuild rule:
printf '
LABEL devops-academy.watch-rebuild="1"
' >> app/Dockerfile
# Observe the watch terminal, then record the resulting object identity.
docker compose -f compose.yaml -f overlays/compose.dev.yaml ps -a
docker image inspect da-compose16-app:lab --format '{{.Id}}' > evidence/image-after-rebuild.txt
Stop Watch with Ctrl+C. The evidence should show that sync and rebuild are different state transitions.
11. Reconciliation after a model change
Add a harmless label to the app service in the dev override, normalize again, then apply only that service:
# Edit overlays/compose.dev.yaml and add under app:
# labels:
# devops-academy.variant: dev
docker compose -f compose.yaml -f overlays/compose.dev.yaml config > evidence/dev.changed.normalized.yaml
docker compose -f compose.yaml -f overlays/compose.dev.yaml up -d app
docker inspect da-compose16-app-1 --format '{{json .Config.Labels}}' > evidence/app-labels-after-reconcile.json
Compose may recreate a container when configuration changes. Capture the old/new container IDs if you want to prove reconciliation rather than assuming an in-place mutation.
12. Challenge: choose the layer, not a command
The toolbox service is missing, readiness is healthy,
and the app is running. Which layer should you inspect first?
- If the toolbox is absent: inspect profile/model selection.
-
If
./appcannot be found during config: inspect path resolution and file ordering. - If app never starts while readiness is unhealthy: inspect the dependency health layer.
- If the page does not update under Watch: inspect watch path/ignore/target permissions, not the Docker network first.
13. Exact cleanup
docker compose \
-f compose.yaml \
-f overlays/compose.dev.yaml \
--profile debug \
down --remove-orphans
# Verify only the named lab project is gone:
docker ps -a --filter label=com.docker.compose.project=da-compose16
docker network ls --filter label=com.docker.compose.project=da-compose16
# Optional exact local image cleanup after verifying no other container uses it:
docker image rm da-compose16-app:lab 2>/dev/null || true
The lab does not use broad prune commands. Cleanup is scoped to the Compose project and explicitly named image.
Knowledge check
Why did the lab run config before
up?
To prove merge/include/profile/path resolution and catch model errors before any Engine resources were created or reconciled.
Why is ./app correct inside
overlays/compose.dev.yaml?
In ordinary multi-file merge, relative paths are resolved from the base Compose file, not the override file directory.
Why did toolbox not start initially?
It is assigned to the debug profile, and that
profile was not active during the ordinary dev startup.
What does service_healthy change in the
lab?
Compose waits for the dependency healthcheck to report healthy before starting the dependent app; it does not prove the app itself is healthy or resilient forever.
What is the state difference between Watch
sync and rebuild?
Sync changes files in the running container target; rebuild builds a new image and replaces/reconciles the service container.
Official references and version notes
- Docker Docs — Merge Compose files: ordered-file merge rules and base-file path resolution.
- Docker Docs — Include Compose files: per-included-model project-directory semantics and modular application models.
- Docker Docs — Profiles: conditional services, explicit profile activation, and targeted-service behavior.
-
Docker Docs — Startup order:
service_started,service_healthy, andservice_completed_successfully. -
Docker Docs — Compose Watch: prerequisites,
sync,rebuild, andsync+restart. -
Docker Docs —
docker compose config: normalized model, profiles, services, variables, hashes, and quiet validation. -
Compose services reference:
profiles,depends_on, health checks, and service attributes. - Docker Compose v5.5.1 (released 2026-09-03), current upstream baseline checked for this chapter.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.