Skip to content

Adopt an Existing Compose Stack — Zero-Downtime Migration

If you already have a compose project running on a host via plain docker compose up, you don’t need to tear it down and redeploy through dockmesh. The stack adopt flow hands over management without touching the running containers — no recreation, no downtime, no data loss.

Adoption is metadata-only. There is no “old version” and “new version” of the stack — there is only the one set of containers that are already running. What changes is who manages them: previously you did, via the compose CLI; afterwards dockmesh does, via its UI and API.

The binding happens through Docker’s own com.docker.compose.project label, which is baked into every container at create time. As long as dockmesh’s stack name matches that label, everything lines up automatically.

Section titled “Option 1 — dmctl stack adopt (recommended)”

This is the CLI flow, and the cleanest path for anything more complex than a single-service image pull. The tool packages your compose.yaml and any supporting files (build contexts, config files referenced by relative bind mounts, cert bundles, etc.) into a tarball, uploads it to the dockmesh server, and binds to the running containers in one step.

  • dmctl installed and authenticated — see the dmctl CLI reference for login options (--user admin for interactive password, --token for API tokens)
  • SSH access to the host where the stack runs, or direct shell access
  • The compose project is currently running on that host (docker compose ps lists the services)

From the directory where your compose.yaml lives:

Terminal window
cd ~/docker/audiobookshelf
dmctl stack adopt .

dmctl will:

  1. Scan the dockmesh server for compose projects running on the target host that are not yet managed
  2. Match your directory’s project name (defaults to the folder basename; override with --name foo) against the discovered list
  3. Package the folder into an in-memory tar.gz (skipping .git, node_modules, IDE junk, and .env by default)
  4. Show you a diff report: detected services, detected images, bundle size, warnings about build contexts or relative paths
  5. Ask for confirmation (unless you pass --yes for CI)
  6. Upload, let the server bind the stack to the running containers, and print the result

Concretely:

Adopting stack 'audiobookshelf' on host mac-mini
Running services detected (5):
audiobookshelf ghcr.io/advplyr/audiobookshelf:latest (running)
audnexus audnexus:local (running)
audnexus-mongo mongo:7 (running)
audnexus-proxy nginx:alpine (running)
audnexus-redis redis:7-alpine (running)
Bundle (21.4 KiB from /Users/marcel/docker/audiobookshelf):
audnexus/certs/ca.pem
audnexus/nginx.conf
audnexus/src/Dockerfile.prod
audnexus/src/…
⚠ Compose uses build: context — works for single-host redeploy once
the source is copied into the stack dir, but for reproducible
multi-host deploys push the image to a registry.
Proceed? [y/N]: y
✓ Adopted 'audiobookshelf'
Host: mac-mini
Bound containers: 5
Warnings: build-context, relative-paths
FlagPurpose
--name fooOverride project name (default: directory basename)
--host mac-miniTarget a specific host when multi-host
--dry-runValidate + preview only, nothing uploaded
--yesSkip confirmation prompt — for CI
--with-envInclude .env in the bundle. Off by default because .env usually contains secrets
--max-size NMax bundle size in bytes (default 100 MiB)
  • Your compose references local files: build: ./src, ./nginx.conf:/etc/nginx/nginx.conf:ro, cert bundles, init-scripts, etc.
  • You want one command instead of copy-paste
  • You’re scripting a migration of many stacks at once
  • You want the adoption to survive git pull refreshes of the stack dir

Option 2 — Web UI adoption (metadata-only)

Section titled “Option 2 — Web UI adoption (metadata-only)”

The web UI surfaces unmanaged compose projects automatically. After login:

  1. Go to Stacks. If the logged-in user has stack.adopt permission and there are discovered projects on the current host, a “N unmanaged compose project(s) detected” banner appears above the stack grid.
  2. Click Adopt on the relevant project.
  3. A modal opens with a pre-filled skeleton showing the detected service names + images. Paste your actual compose.yaml over the top.
  4. Click Adopt.

The web flow is metadata-only: only the compose.yaml you paste is written to stacks/<name>/compose.yaml. Supporting files referenced via relative paths aren’t transferred — your running containers keep working (Docker resolved those bind mounts at container-create time), but the next restart would fail unless you either:

  • Copy the supporting files into the stack directory manually (via SSH to the server)
  • Re-run dmctl stack adopt from the host shell to ship the full bundle

For anything beyond a pure-registry-image stack, prefer the CLI path.

Does:

  • Write your compose.yaml (and optional .env, and optional bundle contents) under <stacks_root>/<name>/ on the dockmesh server
  • Bind the already-running containers to the new managed stack via their com.docker.compose.project label
  • Record the adoption in the hash-chained audit log (who adopted, when, which warnings were acknowledged)

Does not:

  • Run any docker compose up, down, restart, or pull against the containers
  • Modify volumes, networks, or images
  • Change container labels, environment, or runtime config in any way
  • Trigger a health-check restart

After adoption, the stack shows up in the Stacks list as running. You can stream logs, exec into containers, adjust scale, and see live stats — all metadata operations.

The first explicit redeploy (clicking Deploy in the UI or running dmctl stacks deploy <name>) is the only moment where compose compares the on-disk config hash against what’s running. If the hashes differ — which happens whenever the pasted compose is subtly different from what the original docker compose up was given — compose will recreate the affected containers. Volumes are preserved; restart takes seconds to minutes depending on the services.

If you want to defer that decision, just don’t click Deploy. Dockmesh is perfectly happy to manage an adopted stack in read-only-ish mode until you decide to reconcile.

Concrete walkthrough: adopting audiobookshelf + audnexus

Section titled “Concrete walkthrough: adopting audiobookshelf + audnexus”
# compose.yaml in ~/docker/audiobookshelf/
services:
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf:latest
volumes:
- audiobookshelf_config:/config
- audiobookshelf_metadata:/metadata
- ./audnexus/certs/ca.pem:/etc/ssl/certs/audnexus-ca.pem:ro
# …
audnexus:
build: ./audnexus/src
image: audnexus:local
# …
audnexus-mongo:
image: mongo:7
volumes:
- audnexus_mongo_data:/data/db
# …
volumes:
audiobookshelf_config:
external: true
audiobookshelf_metadata:
external: true
audnexus_mongo_data:

From the host shell:

Terminal window
cd ~/docker/audiobookshelf
dmctl stack adopt .

What happens:

  • audiobookshelf_config / audiobookshelf_metadata are external: true — untouched, data intact
  • audnexus_mongo_data is named. Docker’s real volume name under this project is audiobookshelf_audnexus_mongo_data. As long as dockmesh uses the same project name (audiobookshelf), the same volume is referenced → MongoDB data intact
  • ./audnexus/certs/ca.pem and ./audnexus/nginx.conf are relative bind mounts. The bundle ships them into the stack dir, so the relative reference resolves correctly after adoption and future restarts work
  • build: ./audnexus/src is locally built. The source is shipped in the bundle, so rebuilds on this host work. For multi-host deploys you’d push audnexus:local to a registry as a separate cleanup step

Result: zero downtime, zero data loss, full management in dockmesh.

“no running compose project ‘X’ found on the target host” — the compose project name dockmesh is looking for doesn’t match the com.docker.compose.project label on your containers. Check with:

Terminal window
docker inspect <container> --format '{{ index .Config.Labels "com.docker.compose.project" }}'

Pass --name <label> to dmctl if the two don’t match.

“a stack with this name is already managed” — there’s already a stacks/<name>/ directory on the server. Either the stack was previously adopted (check the Stacks list), or the name collides with an unrelated dockmesh stack. Rename the project (requires recreating containers, so more invasive) or pick a different --name.

“bundle exceeds maximum size” — your directory contains something large you probably don’t want to ship (node_modules, build artefacts, a dumped SQLite file, etc.). Either clean it up, or raise the limit with --max-size. The server also enforces a limit, default 100 MiB.

Containers aren’t bound after adoption completes — this happens when the project name in the request doesn’t match what’s actually on the running containers. Adoption still succeeds (the files are written and the stack is registered), but bound_containers comes back as 0. Double-check the com.docker.compose.project label and re-adopt with the correct name.