compman — Docker Compose Stack Manager CLI
[ English | 한국어 ]
Project homepage: https://allbegray.github.io/compman/
compman manages Docker or Podman Compose stacks from one CLI: run and inspect
services, back up and restore volumes and images, schedule unattended backups,
deploy releases from S3 or HTTPS, and post a Slack notification when a stack
comes up.
No daemon, no web UI, no port opened — it calls the Docker or Podman CLI you already have, which is why it fits locked-down hosts, jump boxes, and networks where a management platform cannot be installed.
Key features
- Auto-detects Docker Compose, Podman Compose,
podman-compose, anddocker-compose - Profile-based
composeconfig with per-profile env vars and AWS Secrets Manager injection - Project-scoped
psandstats, plusstatus/doctoras text or JSON for CI - Timestamped volume and image backups, gzip by default or Zstandard with
--zstd - Local, S3, or SSH/SCP backup storage, plus scheduled jobs via launchd/systemd/cron/schtasks
- Deploys from an S3 prefix or HTTP(S) archive with SHA-256 pinning and HTTPS header auth
- Slack notification on
stack up/stack updatewith unhealthy-service and volume detail - English and Korean help, shell completion, Windows/macOS/Linux
Requirements
- Python 3.12+ (
--zstdneeds 3.14+, which ships the stdlibcompression.zstd) - Docker Compose or Podman Compose
- For S3: reachable S3-compatible storage and AWS credentials
- For authenticated HTTP deploys: an HTTPS archive URL and a token in an environment variable
CI runs Python 3.12–3.15 on Ubuntu, macOS, and Windows. Wheels publish to PyPI on every tagged release.
Install
uv tool install compman # recommended, self-contained
pipx install compman # equivalent
Or run the bundled installer, which puts the CLI on your PATH:
curl -fsSL https://raw.githubusercontent.com/allbegray/compman/main/install.sh | sh # Linux / macOS
irm https://raw.githubusercontent.com/allbegray/compman/main/install.ps1 | iex # Windows PowerShell
Then compman --version. Use compman upgrade to refresh an installed tool in
place. If the installation is damaged, uninstall and reinstall from the
unpinned upstream source so future upgrades keep working:
uv tool uninstall compman
uv tool install --managed-python git+https://github.com/allbegray/compman.git
For a development checkout, use uv tool install . and run the CLI from the repo.
Quick start
cd my-project
compman init --scaffold # or run `compman init` for an interactive menu
compman stack up
compman status
To deploy into an empty directory, compman deploy scaffolds the config for you:
mkdir my-app && cd my-app
compman deploy --path s3://my-bucket/releases/app.tar.gz --build --tag my-app
compman stack up
my-app/
├── compman.yml
├── docker-compose.yml
└── project/ # source fetched from S3/HTTP
Configuration
Everything lives under the compman key in compman.yml. A JSON Schema is
published at docs/site/compman.schema.json —
add this first line for IDE autocomplete and validation:
# yaml-language-server: $schema=https://allbegray.github.io/compman/compman.schema.json
Worked examples: examples/compman-config/ (index in
examples/README.md).
Compose profiles
compose is required and must be a mapping of profiles. One profile is
enough; add more to switch environment per deployment.
compman:
name: my-stack
compose:
base: docker-compose.yml
dev:
file: docker-compose.dev.yml
env:
DATABASE_URL: dev.db.example.com
prod:
file: docker-compose.prod.yml
env:
DATABASE_URL: prod.db.example.com
compman stack up dev
A profile file is optional: omitted, it falls back to base, then
docker-compose.yml. That lets one Compose file vary only by environment.
Keys
| Key | Purpose |
|---|---|
name |
Stack name; defaults to the config directory name |
folder |
Subdirectory holding the Compose files |
compose |
Required. Profile mapping; base: adds a shared Compose file |
dirs.project |
Managed deployment source tree |
dirs.backup |
Archive destination: local path, s3://bucket/prefix, or ssh://[user@]host[:port]/path |
dirs.volume |
Scratch directory for host↔container volume transfers |
deploy |
Default source for deploy/update: s3://…, an HTTPS archive URL, or a mapping (below) |
secrets |
AWS Secrets Manager entries, referenced by ${secrets:NAME} |
notify.slack |
Slack webhook settings (see Notifications) |
limits.max_archive_mb |
Cap on fetched deploy source size |
limits.max_backups |
Keep only the newest N archives per stack and kind |
Managed paths resolve relative to the config directory and may never escape it.
--path overrides deploy for a single invocation.
Secrets
Declare { arn, key } pairs under secrets, then reference them from a profile
env with ${secrets:NAME}. compman substitutes the value at key when it
builds a compose context and passes the result to the docker compose process
environment, so the Compose file still uses ${VAR}.
compman:
name: my-stack
compose:
default:
file: docker-compose.yml
dev:
file: docker-compose.dev.yml
env:
DATABASE_URL: postgres://${secrets:DB_USER}@db.example.com
secrets:
DB_USER:
arn: arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:db
key: dtx/db/user
- Injected only where a marker appears, never as standalone compose variables. An undeclared marker name fails the command.
keymay be a slash path. Partial interpolation works, and markers can sit beside system-variable references.- A profile
secretsblock wins over the top-level one. Each ARN is fetched once per invocation. compman doctorwarns when credentials or region are missing.
Deploy sources and guarantees
S3 accepts a prefix (recursive, structure preserved) or a archive
(.tar.gz, .tgz, .zip, with a single top-level directory flattened).
Public HTTP/HTTPS accepts archives only, and the path must end in one of those
suffixes. Only the target with the same name is replaced; your own files are kept.
The mapping form adds pinning and auth:
compman:
deploy:
url: s3://my-bucket/releases/app.tar.gz
sha256: <64-hex-digest>
auth: { header: Authorization, value_env: DEPLOY_TOKEN } # HTTPS only
- Transactional build.
--buildcompiles from the temporary source before the managed-tree swap, so a build failure leaves the existing tree and config untouched. A failed swap rolls back. - Integrity pinning.
--sha256 HEXordeploy.sha256is verified after download, before extraction, build, and swap. A mismatch aborts with exit 1 and changes nothing. It applies whenever the deployed URL equals the configureddeployURL, soupdateinherits it. - Token handling. The header value is read at fetch time from
value_env— never stored, never echoed, errors name only the variable. It is sent verbatim, so for Bearer auth store the fullBearer <token>string. - HTTPS required for auth, and the header is dropped on a cross-host redirect so the token cannot leak. Serve from one host if your CDN needs it after redirecting.
compman rollbackrestores the snapshot from the previous successful deploy.
Commands
compman init [--scaffold | --s3 URI | --seed]
compman deploy [--path SOURCE_URI] [--sha256 HEX] [--build] [--tag TAG]
compman update [PROFILE] [-c|--config PATH] [--stack NAME]
compman rollback
compman doctor [--profile PROFILE] [-c|--config PATH] [--json] [--stack NAME]
compman status [--profile PROFILE] [-c|--config PATH] [--json] [--stack NAME]
compman ps [PROFILE] [-a|--all] [--json] [-c|--config PATH] [--stack NAME]
compman stats [PROFILE] [-f|--follow] [--json] [-c|--config PATH] [--stack NAME]
compman history [--limit N] [--json]
compman stacks list [--json]
compman stacks remove NAME
compman clear [--yes]
compman upgrade [--repo URL]
compman lang [ko|en]
compman version
compman completion [powershell|bash|zsh|fish] --install
compman stack up [PROFILE] [--wait] [-c|--config PATH] [--stack NAME]
compman stack update [PROFILE] [--wait] [-c|--config PATH] [--stack NAME]
compman stack down [--profile PROFILE] -c|--config PATH --yes [--stack NAME]
compman stack logs [SERVICE...] [-f] [--tail N] [--profile PROFILE] [-c|--config PATH]
compman service start|stop|restart [SERVICE...] [--profile PROFILE] [-c|--config PATH]
compman service status [--profile PROFILE] [-c|--config PATH]
compman service log [SERVICE] [-f] [-n 50] [--profile PROFILE] [-c|--config PATH]
compman service connect [SERVICE] [--profile PROFILE] [-c|--config PATH]
compman volume backup [-z LEVEL] [--zstd] [--no-stop] [--profile PROFILE] [-c|--config PATH]
compman volume restore [TIMESTAMP] [--no-stop] [--replace] [--profile PROFILE] [-c|--config PATH]
compman volume pull [--profile PROFILE] [-c|--config PATH]
compman volume push [--replace] [--profile PROFILE] [-c|--config PATH]
compman image backup [-z LEVEL] [--zstd] [--source-image] [--profile PROFILE] [-c|--config PATH]
compman image restore [TIMESTAMP] [--profile PROFILE] [-c|--config PATH]
compman schedule add [--every N | --daily HH:MM | --weekly DAY HH:MM | --monthly DD HH:MM] [--no-stop] [-z LEVEL] [--name TEXT] [--scheduler systemd|cron]
compman schedule list [--json]
compman schedule status NAME
compman schedule remove NAME
-c/--config selects the config file, --profile the compose profile, and the
global --stack NAME runs any command against a registered stack from any
directory. View all options for any command with compman <command> --help.
Behaviors worth knowing
updateis a rebuild plus force-recreate — not a zero-downtime rolling deploy. Withoutdeployit runsup -d --buildlocally.stack downon a missing stack is not an error: it exits 0, so scripts stay idempotent.psandstatsare project-scoped by design.docker ps/podman psfor runtime-wide results.service logandconnecttake service names, resolved viacompose ps -q. Zero instances, or a scaled service with several, fails with guidance rather than guessing.connectfalls back tosh.- Streaming commands (
service log -f,stack logs -f,stats -f) stop when you typeqand press Enter on a terminal — exit 0, not a failure. Ctrl-C still works. Piped input (… | grep) keeps Ctrl-C as the only stop, since nobody is there to typeq. volume backup/restorestop the stack for consistency;--no-stopopts out. Restoring while everything is stopped also works — the stack is started temporarily and stopped again.volume restore/push --replaceis a byte-for-byte replace that deletes destination-only files. Destructive by design; the destination must be an absolute container path.image backupcommits container state unless--source-imageis passed. gzip level defaults to 6 (-z);--zstdwrites.tar.zstand needs Python 3.14+ for restore too.- Archives are named
<stack>.{volume,image}.<YYYYMMDD_HHMMSS>[_<microseconds>].tar.gz. clearrunsimage prune -af, which can remove unused images outside this project. Requires--yes.- Long operations time out after 300s; override with
COMPMAN_TIMEOUT=<seconds>. Streaming commands (log -f,connect,stats -f) never time out.
Diagnostics
compman doctor
compman doctor --json
compman status --json
--json emits schema version 1. doctor exits 1 only when a required check
fails; missing AWS credentials, an unpinned deploy, and unset
deploy.auth/notify.slack variables are warnings. status exits 1 when the
stack is missing, and 0 when it exists even if every service is stopped.
Operational failures, including Docker Desktop readiness on Windows, print as concise messages without a Python traceback.
Notifications
Set the webhook and every stack reports on stack up and stack update:
export COMPMAN_SLACK_WEBHOOK_URL='https://hooks.slack.com/services/T000/B000/XXXX'
Or scope it per stack so the secret stays out of the environment of the calling shell:
compman:
notify:
slack:
webhook_env: COMPMAN_SLACK_WEBHOOK_URL # recommended
# webhook: https://hooks.slack.com/services/... # literal URL, keeps the secret in this file
✅ Stack started
*Stack* notify-demo · *Profile* default
*Runtime* docker · *Host* build-host
*Started at* 2026-10-02 20:06 KST · *Duration* 681ms
*Services* — 3 of 3 healthy
────────────────────────────────────────
*Volumes* (2)
🟢 notify-demo_web-data · web:/usr/share/nginx/html · 1.393kB
🟢 notify-demo_pgdata · db:/var/lib/postgresql/data · 40.01MB
compman 1.12.0 · stack up
Resolution order is webhook, then the variable named by webhook_env, then
COMPMAN_SLACK_WEBHOOK_URL — the last only when the file has no notify.slack
block. A stack naming its own variable never falls back to the global one.
Services appear as a healthy count plus only the ones that are not healthy,
each with state, exit code, image tag, and published ports. The headline turns
into ⚠️ … N service(s) need attention as soon as anything is off, and a volume
used by a failing service is marked 🔴 to point at the cause.
Delivery is best-effort and never changes the exit status — the containers are
already running, so an outage, a revoked webhook, or an unset variable warns on
stderr and still exits 0. stack down, backup, and restore do not notify, so
the temporary restarts around a backup stay silent.
Volume sizes come from docker system df -v, the only Docker surface that
reports them. It scans the whole host, so compman runs it only when the compose
files declare a named volume and notifications are enabled; otherwise volumes
are still listed with their mount paths and only the sizes are dropped.
Backups and scheduling
dirs.backup takes a local path, s3://bucket/prefix, or
ssh://[user@]host[:port]/path. Remote stores stage locally, upload, verify, then
remove the staged copy; SSH mode drives scp/ssh with BatchMode=yes and
assumes keys are already provisioned.
compman schedule add --daily 04:30 --no-stop
compman schedule add --every 30m
compman schedule add --monthly 1 05:00
compman schedule status my-stack.volume
compman schedule remove my-stack.volume
Exactly one cadence option is required. The platform mechanism is chosen
automatically: launchd on macOS, schtasks on Windows, and a systemd user timer or
crontab on Linux (--scheduler systemd|cron forces the Linux choice). Cron cannot
express every interval — --every must divide 60 minutes or be whole hours.
Output and per-run records land beside schedules.json under %APPDATA%\compman
when APPDATA is set, otherwise ~/.config/compman; see
docs/site/ for the registry layout.
Runtime and environment
Detection order is docker compose → podman compose → podman-compose →
docker-compose. Force one with CONTAINER_RUNTIME=podman.
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_DEFAULT_REGION=ap-northeast-2
export AWS_ENDPOINT_URL_S3=http://localhost:4566 # Ministack/LocalStack
export COMPMAN_TIMEOUT=600 # seconds; default 300
export COMPMAN_LANG=ko # or --lang ko per invocation
On Windows with Docker, stack up, stack update, and deploy builds check
Docker Desktop readiness and offer to start it. Non-interactive runs never launch
it, and read-only, backup, and down paths skip the check entirely.
Further reading
| Document | Contents |
|---|---|
| examples/compman-config/ | 14 case-by-case compman.yml files |
| CHANGELOG.md | Release history |
| SECURITY.md | Credential model, secret handling, reporting |
| BACKLOG.md | Constraints and open work |
| AGENTS.md | Development, testing, and release rules |
| SOLUTION.md | Debugging and design lessons |
| Homepage | Search-optimized project site |
License
MIT — see LICENSE.
Metadata
Release files for compman 1.13.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| compman-1.13.0-py3-none-any.whl | Python 3 | none | any | Details |
Release files / compman-1.13.0-py3-none-any.whl
| Download URL | compman-1.13.0-py3-none-any.whl |
|---|---|
| Size | 105.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b895789ac904127f3b7ed9821ea0dcc86b2cbab351339a5893a36379a7c27f63
|
|
BLAKE2b-256 checksum How to use checksums |
7833196b107ef56aa3aba5911164b9722a5a27b93d86f00993b126ecc5f551e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log