dcm - uhop/dotfiles GitHub Wiki

dcm

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.

Usage

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

Commands

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

Options

Option Description
--force Pass --force-recreate to docker compose up
-n, --dry-run Show commands without running

Change detection

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.

Service status

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. Under restart: always or unless-stopped, an exited container was stopped by hand, because the daemon restarts a crash on its own. Under no or on-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: restarting is a crash loop; crashed is an exited container the daemon would not restart (a non-zero exit under no or on-failure, or an OOM kill).
  • (no services) — the stack defines no services, or docker compose ps returned 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.

Rollback

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 update overwrites 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 prune or docker image prune -a since 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 update ever) 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.

Directory scanning

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.

Sweeps

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 deployed when it has no containers (after dcm down, or never started), or stopped, Exited (0) 3 days ago when every container is stopped.
  • A stack with a .dcm-skip file in its directory is skipped whatever its state. Touch the file to freeze a stack against sweeps and dcm up over 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 sweep

An 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 excepted

A 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.

AppArmor recovery

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.

Related

  • dcms — dcm against every setup under ~/servers/. Equivalent to dcm ~/servers with the standard dcm defaults. Skipped silently if ~/servers does not exist. Used as the docker step inside playbash-daily and playbash-weekly.

Multi-host orchestration of dcm and dcms is handled by playbash — see Playbash Server Management.

⚠️ **GitHub.com Fallback** ⚠️