Docker Compose Profiles, Overrides and Watch
Three features that turn one Compose file into a workflow: optional services behind profiles, per-machine overrides that merge automatically, and live file sync that updates a running container without a rebuild. Including the profile teardown trap that leaves a network behind.
Docker Compose Guide 28 of 46 Advanced
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- Docker Compose5.4.0
- Architectureamd64
- TimeAbout 15 min
- Reviewed21 August 2026
Tested on the versions above. Build output and timings vary. The merge results and the teardown behaviour are what to match.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- Comfortable with a multi-service project - guide 25 in this path.
- Variable substitution and
compose config- guide 27 in this path.
-
Profiles: services that stay out of the way
A service with a
profiles:key is not started by ordinaryup. It exists in the file, documented and ready, but it only runs when you ask for its profile. This is where debug tooling, seed jobs and admin consoles belong - in the file, out of the default path.bash cat compose.yaml debug: image: alpine:${TAG} profiles: ["debug"]docker compose up -ddocker compose ps --servicesappExpected resultOnly
app- the profiled service was skipped entirely.Success condition
debugis absent despite being in the file. That is the feature, not a mistake. -
Opt in when you need it
--profileactivates the profile for that command. The already-running services are left alone; only the newly enabled one is created.bash docker compose --profile debug up -d Container cg-cfg-debug-1 Created Container cg-cfg-debug-1 Starteddocker compose ps --servicesappdebugExpected resultBoth services now listed.
Success conditionThe profiled service started without disturbing the others.
COMPOSE_PROFILES=debugin the environment does the same thing persistently. -
The teardown trap
Here is the part that bites.
downwithout the profile does not know about the profiled service, so it removes what it can and then fails to remove the network - because something it is not managing is still attached. The message says the resource is in use and does not mention profiles at all.bash # down without --profile leaves the profiled container behinddocker compose down Container cg-cfg-app-1 Removed Network cg-cfg_default Removing Network cg-cfg_default Resource is still in useExpected resultThe network removal failing, and the profiled container still present.
Verify it worked
bash Example session docker compose ps -a --format "table {{.Service}}\t{{.Status}}"SERVICE STATUSdebug Exited (0) 48 seconds agoSuccess conditionYou have reproduced the leak. The orphan is the reason - not a Docker bug.
-
Tear it down properly
Pass the same profile to
downthat you passed toup. Anything that turns a service on must also turn it off, which is a good argument for putting the profile inCOMPOSE_PROFILESrather than remembering a flag on every command.bash docker compose --profile debug down Container cg-cfg-debug-1 Removing Container cg-cfg-debug-1 Removed Network cg-cfg_default Removing Network cg-cfg_default RemovedExpected resultThe network removed cleanly this time.
Verify it worked
bash docker compose ps -a --services# empty - nothing leftSuccess conditionNothing is left.
docker compose down --remove-orphansis the escape hatch when you have already lost track. -
Override files merge automatically
If a
compose.override.yamlsits besidecompose.yaml, Compose reads both and merges them, with the override winning. Nothing needs to be passed on the command line. This is how a shared base file survives contact with per-developer differences - the base is committed, the override is ignored by git.bash cat compose.override.yamlservices: app: environment: GREETING: overridden-locallydocker compose up -d && docker compose logs appapp-1 | app started Network cg-pw_default Creating Network cg-pw_default Creating Network cg-pw_default Created Network cg-pw_default Created Container cg-pw-app-1 Creating Container cg-pw-app-1 Created Container cg-pw-app-1 Starting Container cg-pw-app-1 StartedExpected resultThe override's value winning over the base file's.
Success condition
overridden-locallyappears without any extra flags. Note the unrelatedextravalue is untouched - the merge is per key, not wholesale replacement. -
Opt out of the override when you need the base
Naming files explicitly with
-freplaces the automatic discovery, so-f compose.yamlalone reads the base and ignores the override. This is what CI should do - otherwise a developer's stray override file changes the build.bash docker compose -f compose.yaml up -d --force-recreatedocker compose -f compose.yaml logs appapp-1 | greeting=from-dotenv extra=from-envfileExpected resultThe base value returning.
Success conditionThe override is bypassed. Multiple
-fflags merge left to right, which is how per-environment files are composed deliberately rather than by discovery. -
Watch: sync files into a running container
docker compose watchmonitors paths declared underdevelop.watchand acts on changes. Asyncaction copies the changed file into the running container without a rebuild or a restart;rebuildrebuilds the image instead, for changes that cannot be hot-copied like a dependency manifest.bash cat compose.yaml develop: watch: - action: sync path: ./site target: /usr/share/nginx/htmlExpected resultA watch rule pairing a host path with a container path.
Success conditionThe rule names both sides. A sync action with no
targetcannot work out where to put the file. -
Watch it happen
With
watchrunning in one terminal, editing a file on the host updates the container in place. The log line names how many changes it saw, and the served content changes with no rebuild - the container is the same one that was already running.bash Example session docker compose watch Container cg-pw-app-1 RunningWatch enabledExpected resultA sync message, and the edited content served immediately.
Verify it worked
bash curl -s http://localhost:8099<h1>version two - edited on the host</h1>Success conditionThe new content is served without a rebuild. Watch runs in the foreground - it is a development tool, not something to run in production.
Troubleshooting
Network NAME Resource is still in use during down
Why: Something is still attached that this
downis not managing - most often a profiled service, or a container from an earlier version of the file that Compose now considers an orphan.Fix:Re-run
downwith the same profiles you brought up, or use--remove-orphansto sweep containers the current file no longer describes.bash docker compose --profile debug down --remove-orphansA profiled service will not start
Why: The profile was not activated, or the name does not match. Compose treats an unknown profile as simply having no members - it does not warn you.
Fix:Check the resolved profile list, and remember a service can belong to several profiles.
bash docker compose config --profilesA setting from the override is not applied
Why: Merge semantics differ by type. Mappings merge key by key, but sequences - ports, command arrays, volumes - are REPLACED wholesale by the override.
Fix:Check the merged result rather than reasoning about it. Use
!resetor!overridetags for explicit control over how a key merges.bash docker compose config# this is the file Compose will actually runWatch does not react to changes
Why: The edited path is outside the declared
path, or it is excluded, or the editor writes via a replace-and-rename that the watcher does not attribute to the watched file.Fix:Confirm the path is inside the watched directory, and remember
synconly copies files - a change needing a rebuild needsaction: rebuild.bash docker compose config | grep -A6 develop