dcm - uhop/dotfiles GitHub Wiki
Manage Docker Compose setups. Handles pulling, updating, stopping, and restarting services with automatic AppArmor recovery on Linux. After pulling, reports which images changed and shows a compact service status summary. A sweep over a parent directory refreshes what is running and starts nothing, and dcm up over a parent directory starts every stack (see Sweeps).
Requires docker and jq.
dcm # Update current dir (pull + up)
dcm update ~/servers/monitor # Update specific setup
dcm ~/servers # Sweep: refresh what runs, start nothing
dcm status ~/servers # Status for all setups under ~/servers
dcm prune # Prune unused Docker images globally
dcm down # Bring down current setup
dcm up ~/servers # Start every stack, .dcm-skip ones excepted
| Command | Description |
|---|---|
update, u
|
Pull images, recreate changed containers (default) |
up |
Pull images and start every service of every stack, stopped and downed ones included; over a parent directory, a stack with .dcm-skip is skipped |
down, d
|
Compose down (stop and remove) |
stop |
Compose stop (pause without removing) |
start |
Compose start (resume stopped services) |
restart |
Compose restart |
status, s
|
Compact per-service status (✓ running, ○ stopped, ✗ in trouble); exits non-zero when a service is in trouble |
prune, p
|
Prune unused Docker images globally |
rollback, r
|
Re-tag pre-update image versions and recreate containers |
| Option | Description |
|---|---|
--force |
Pass --force-recreate to docker compose up
|
-n, --dry-run |
Show commands without running |
After docker compose pull, dcm compares image digests before and after the pull and reports which services got new images. Example output:
updated: nginx
unchanged: postgres
unchanged: redis
When no images changed, it prints no images changed.
dcm status and the post-update display share the same compact per-service summary — one line per service, much shorter than docker compose ps's tabular output. Stopped/exited containers are included so a fully-down stack is visible at a glance.
Status: monitor
✓ nginx: Up 2 hours (healthy)
db: running (unhealthy)
○ worker: stopped, Exited (0) 30 seconds ago
✗ queue: restarting, exit 3, 12 restarts
✓ cache: Up 1 day
-
✓(green) — running normally. -
○— stopped. Underrestart: alwaysorunless-stopped, an exited container was stopped by hand, because the daemon restarts a crash on its own. Undernooron-failure, an exit code of 0, 137, or 143 reads as stopped. - No symbol, warning color — running but the healthcheck reports
unhealthy, or the daemon restarted the container within the last 24 hours (restarted 4x in the last 24h). -
✗(red) — in trouble:restartingis a crash loop;crashedis an exited container the daemon would not restart (a non-zero exit undernooron-failure, or an OOM kill). -
(no services)— the stack defines no services, ordocker compose psreturned nothing.
A service in trouble makes dcm status, dcm update, and dcm rollback exit non-zero, so the stack's playbash section fails and the daily mail names it. Under playbash the container is also filed as a container-trouble event with its details.
When the target directory contains multiple compose stacks (or you point dcm status at a parent dir), each stack gets its own Status: <name> heading and the dispatcher prints a final Results: N succeeded, M failed line, with , K skipped appended when a sweep skipped stacks.
dcm update records a snapshot of the current image:tag → image-ID mapping for each service into <dir>/.dcm-rollback.json before running docker compose pull. dcm rollback reads that snapshot, re-tags the recorded image IDs back to the references the compose file uses, then docker compose up -d --force-recreates the stack so it picks up the re-tagged image.
dcm update # pull + up; snapshot saved as .dcm-rollback.json
# ...something broken in the new image...
dcm rollback # tag old IDs back, recreate containers
dcm -n rollback # dry-run (show what would be re-tagged)Behavior:
- Only one step of history is kept — the next
dcm updateoverwrites the snapshot. - The snapshot file is plain JSON; safe to inspect (
jq . .dcm-rollback.json) and to delete by hand. - If any recorded image ID is no longer present locally (e.g. you ran
dcm pruneordocker image prune -asince the update), rollback aborts cleanly and lists the missing images. There's nothing to roll back to in that case — re-pull and restore manually. - A brand-new stack with no images yet (first
dcm updateever) skips the snapshot — there's nothing to remember. - The snapshot file should be git-ignored if your compose tree is in a repo (it's host-local recovery state, not configuration). The same goes for
.dcm-skip.
The --force and -n, --dry-run flags work on rollback the same way as on update.
If no command is given, update is the default. The optional [dir] argument defaults to the current directory. If the directory itself contains a compose file (compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml), that stack is used. Otherwise, dcm scans immediate subdirectories for compose files and operates on each one.
When dcm scans a parent directory (dcm ~/servers, dcms, and the daily / weekly commands the playbooks run), it refreshes what is running and starts nothing:
- Services that are running, restarting, or crashed are pulled and recreated by name (
docker compose up -d --no-deps <services>). A service you stopped inside a running stack stays stopped while its siblings update. A running service behind a Compose profile is updated too, because naming it activates its profile. - A stack with nothing running is skipped, with the reason:
not deployedwhen it has no containers (afterdcm down, or never started), orstopped, Exited (0) 3 days agowhen every container is stopped. - A stack with a
.dcm-skipfile in its directory is skipped whatever its state. Touch the file to freeze a stack against sweeps anddcm upover a parent directory, and remove it to let them back in.
touch ~/servers/stuff/.dcm-skip # sweeps skip it; dcm in the directory still works
rm ~/servers/stuff/.dcm-skip # back in the sweepAn explicit target (dcm inside the stack directory, or dcm update ~/servers/monitor) starts everything, as before. That is how a stopped or downed stack comes back and how a new stack gets its first start. The explicit form ignores .dcm-skip and prints a note that the flag is present.
dcm up is the inverse of dcm down. Over a parent directory it updates every stack as an explicit target, so stopped and downed stacks start too. A stack with .dcm-skip is skipped, which is the way to keep a stack down across dcm up ~/servers; dcm up inside its directory still starts it.
dcm down ~/servers # every stack down, flagged ones included
dcm up ~/servers # every stack up again, flagged ones exceptedA stack with a daily.sh or weekly.sh runs its script even when nothing is running; only .dcm-skip skips it.
The state before the update carries the crash signal. A container that was restarting, crashed, unhealthy, or restarted by the daemon in the last 24 hours is reported as before update: ... and fails the stack's section even when the update fixes it, so one daily mail names it. The state after up -d cannot tell a clean start from a crash a second later, and Compose's --wait is satisfied at once by a service without a healthcheck, so dcm relies on neither.
On Linux, if a Docker operation fails with an AppArmor-related error (permission denied / cannot kill container), dcm automatically runs sudo aa-remove-unknown and retries once. This is the fail-safe path; the eager path is upd/cln detecting an AppArmor upgrade and running aa-remove-unknown immediately, before docker has a chance to fail. Both paths are described under Playbash Server Management.
-
dcms—dcmagainst every setup under~/servers/. Equivalent todcm ~/serverswith the standarddcmdefaults. Skipped silently if~/serversdoes not exist. Used as the docker step insideplaybash-dailyandplaybash-weekly.
Multi-host orchestration of
dcmanddcmsis handled byplaybash— see Playbash Server Management.