portmgr
portmgr is a wrapper around docker-compose that allows running typical docker-compose commands recursively. Additionally, it shortens commands to a single letter.
Let's say you have organized your compose files like this, you just add a dckrsub.yml in each parent folder:
docker/ ├── dckrsub.yml ├── reverse-proxy/ │ └── docker-compose.yml ├── storage │ ├── dckrsub.yml │ ├── nextcloud/ │ │ └── docker-compose.yml │ └── immich/ │ └── docker-compose.yml └── scripts
Each dckrsub.yml has a list of subdirectories, which portmgr should decend into.
For example, the dckrsub.yml in docker/ might look like this:
- reverse-proxy
- storage
And the dckrsub.yml in docker/storage/ like this:
- nextcloud
- immich
Now, if you run portmgr u in docker/ it will run docker compose up -d in reverse-proxy/, storage/nextcloud/ and storage/immich/.
portmgr starts from the current directory, so when running it in docker/storage/, it will run docker compose only in nextcloud/ and immich/. You can also use it in a directory with a docker-compose.yml as a shortener for docker-compose commands.
Commands
The following commands are available. The respective docker-compose commands are in brackets.
u Create and start containers (up)
p Pull images (pull)
s Stop services (stop)
d Stop and remove containers (down)
l Show container logs (logs)
a Run shell in container (exec -it <service> sh)
b Build images (build)
c List containers (ps)
t List processes in containers (top)
r Build and push to registry (build, push)
v Scan container images for vulnerabilities
E Encrypt/seal secret file(s) with age (requires portmgr[secrets])
D Decrypt/unseal sealed secret files (requires portmgr[secrets])
S Show secret migration status (requires portmgr[secrets])
M Move literal environment values from the compose file into .env
R Rotate postgres/mariadb passwords and write new values to .env
You combine multiple commands. For example portmgr dul, runs docker compose with down, up and logs, thus stopping, removing and starting all containers and then showing the logs.
Installation
sudo pip install portmgr
Or build it from source (here using the latest commit on master branch)
sudo pip install https://github.com/Craeckie/portmgr.git
Sealing secrets
portmgr can encrypt secret-bearing files (.env, config files) with age so they are safe to commit to git. Decryption happens once at setup time; the plaintext files remain on disk for Docker to use normally.
Requires the secrets extra:
pip install portmgr[secrets]
Move secrets out of the compose file first (run inside a service directory, or above it to go through every stack):
portmgr M
Lists each literal environment: value with secret-looking keys (password, token, key, …) preselected, asks which to move, previews the result and asks for confirmation. Each chosen value is appended to .env (created with mode 600 if missing) and replaced in the compose file with "${NAME}"; nothing else in the compose file changes, comments included. NAME is the key itself, or <SERVICE>_<KEY> when that name is already used with another value, is referenced elsewhere in the file, or is set in your shell (which would override .env). Values that already reference a variable, span several lines, use YAML anchors or tags, or that compose would re-type (True, 0o17, 1.50, …; quote them to move them) are left alone with a note. Then seal .env as below and commit the compose file.
Seal a file (run inside a service directory):
portmgr E .env
This creates .env.age and writes a .migrated marker. Commit the .age file and .migrated; never commit the plaintext — E does not touch .gitignore, so make sure .env is ignored yourself.
Decrypt on a new server (run from any ancestor directory):
portmgr D
Recurses via dckrsub.yml and decrypts every *.age whose plaintext is missing. Pass --force to overwrite existing plaintext from the sealed copy.
Check migration progress:
portmgr S
Reports DONE, PENDING, or CLEAN for each service and prints a summary tally. A .migrated marker always means DONE, even if the compose file still contains what looks like a secret — create it by hand for services that need no sealing. CLEAN only means nothing was detected; portmgr u's footer still counts a CLEAN service as unsealed until it has a .migrated.
Rotate database passwords:
portmgr R
Finds postgres and mariadb services in each compose stack, generates a new random password, runs ALTER USER in the running container, and writes the new value(s) to .env. If a password was previously hardcoded in the compose file rather than referenced via ${VAR}, a warning is printed reminding you to update the compose file to use the variable reference.
Passphrase: portmgr prompts once and caches the passphrase for the rest of the run. For non-interactive use (e.g. in scripts or provisioning), set:
PORTMGR_PASSPHRASE=<passphrase> portmgr D
After running portmgr u, a footer line is printed if any services have not yet been sealed.
Tipps
If you use portmgr a lot like me, you might want to shorten it to one letter. For bash, you can add alias p='portmgr' to ~/.bashrc. For fish-shell you can add abbr p portmgr to ~/.config/fish/config.fish.
Release files for portmgr 1.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| portmgr-1.11.0.tar.gz | 41.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| portmgr-1.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.0 kB
Release files / portmgr-1.11.0.tar.gz
| Download URL | portmgr-1.11.0.tar.gz |
|---|---|
| Size | 41.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
630f55966fb93fa7b3dea17773c2e6e92925d04ec68607d65dbc8b18173e7b5f
|
|
BLAKE2b-256 checksum How to use checksums |
ec8156ee3320656062855fd14419362d8cfb26ed2e669d8b5016700f117d4b08
|
| 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 Sep 25, 2026.
Transparency logRelease files / portmgr-1.11.0-py3-none-any.whl
| Download URL | portmgr-1.11.0-py3-none-any.whl |
|---|---|
| Size | 27.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4842c43a706f3a2dde879867cd445abc0b221797ef91d56cb8e40cbfe26f5d4c
|
|
BLAKE2b-256 checksum How to use checksums |
2f7482e9220e00bd51d103664761e3e89b25d6129cb3125b88b703f95e1f1138
|
| 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 Sep 25, 2026.
Transparency log