comp eliminates the repetitive commands and multi-stage setups
often required to run complex Docker Compose environments.
While docker compose is powerful, managing advanced setups,
such as mapping host user permissions or sharing external networks,
typically requires writing custom wrapper scripts.
I wrote comp to standardize my hooks, cleanly organize data vs config, and implement strict safety checks across all my services.
-
Dynamic Environment Hooks: Running non-root containers with the host's UID/GID is essential to avoid volume permission errors. Instead of relying on static ".env" files that go stale,
compuses pre-run hooks to dynamically grab host-side configurations (like UID, GID, and TZ) and inject them into the compose environment. -
Automated External Networks: Sharing networks across multiple compositions usually requires manual
docker network createcommands before spinning up your containers.compautomatically parses your YAML files for external networks and creates them on the fly if they do not already exist. -
Strict Permission Safeguards: If you mount a non-existent host directory, Docker automatically creates it as
rooteven if the--userflag is set.compintercepts this behavior and safely errors out, prompting you for explicit action to prevent root-owned directory messes. -
Predictable Conventions: Enforces a strict, modular directory structure mapping host files to containers, alongside standardized port allocations for WAN/LAN and internal vs. external traffic.
Seemed like overkill, when I initially wrote comp.
Why not?
Because typing docker compose takes 14 keystrokes,
and I have things to do.
Typical workflows:
-
(Re)start compositions:
./comp down,up tang pihole -
Update repo & restart a composition:
git pull && ./comp pdu piholeNote how we specify verbs
pull,down,upusing just their first characters. -
Checking the status of compositions:
./comp status tang piholeor
./comp s tang pihole
See comp#L387 for all supported supported flags and options.
Also see Structure & Conventions for optionally customizing compositions via overrides.
| ╭── Compositions \ Supported Archs ─── | amd 64 | arm 64 | arm v7 | risc v64 | |||
|---|---|---|---|---|---|---|---|
|
AirDC++
:443/airdcpp/ |
B | airdcpp |
2.13.2
|
✔️ | ✔️ | ✔️ | ✖️ |
|
Beszel Agent
:45876 |
B | agent |
0.18.7
|
✔️ | ✔️ | ✔️ | ✖️ |
|
Beszel Hub
:24432/beszel/ |
B | beszel |
0.18.7
|
✔️ | ✔️ | ✔️ | ✖️ |
|
bitmagnet
:4433/ |
B C |
postgres
bitmagnet |
18.3...ne
v0.10.0
|
✔️ | ✔️ | ✔️ | ✔️ |
|
Certbot
:80/ |
B | certbot |
v5.7.0
|
✔️ | ✔️ | ✖️ | ✖️ |
|
docker_sock
:44432/docker_sock/ |
A | docker.sock‑proxy |
3.0.7
|
✔️ | ✔️ | ✔️ | ✖️ |
|
ESPHome
:44433/ |
B | esphome |
2026.7.2
|
✔️ | ✔️ | ✖️ | ✖️ |
|
Frigate
:4433/ |
B | frigate |
0.17.2
|
✔️ | ✔️ | ✖️ | ✖️ |
|
Gitea
:443/gitea/ |
B | gitea |
1.27.0-r...s
|
✔️ | ✔️ | ✖️ | ✖️ |
|
HAss
:8123/ |
B | hass |
2026.7.4_2.0.5
|
✔️ | ✔️ | ✔️ | ✖️ |
|
HortusFox
:4431/ |
B B |
mariadb
hortusfox |
12.2.2
v5.8
|
✔️ | ✔️ |
✖️
✔️ |
✖️
✖️ |
|
Indexarr
:4432/jackett/ |
B B |
solvarr
jackett |
v3.5.0
0.24.2027
|
✔️ | ✔️ | ✔️ |
✖️
✖️ |
|
Jellyfin
:443/jellyfin/ |
B | jellyfin |
10.11.11
|
✔️ | ✔️ | ✖️ | ✖️ |
|
Monitarr
:4432/{lid,rad,son}arr/ |
B B B |
lidarr
radarr sonarr |
3.1.0.4875
6.2.1.10461
4.0.17.2952
|
✔️ | ✔️ | ✔️ |
✖️
✖️ ✖️ |
|
Mosquitto
:1883 |
B | mosquitto |
2.0.22
|
✔️ | ✔️ | ✔️ | ✖️ |
|
Nextcloud
:443/nextcloud/ |
B B C C B B |
mariadb
valkey fulltextsearch imaginary nextcloud cron |
12.2.2
9.1.1...ne
20260702...46
20260702...46
34.0.2
34.0.2
|
✔️ | ✔️ |
✖️
✔️ ✖️ ✖️ ✔️ ✔️ |
✖️
✔️ ✖️ ✖️ ✖️ ✖️ |
|
ntfy
:25, :44431/, :44433/ |
A | ntfy |
v2.26.3
|
✔️ | ✔️ | ✔️ | ✖️ |
|
OpenVPN
:5432 |
A | vpn |
2.6.20
|
✔️ | ✔️ | ✔️ | ✔️ |
|
Pi‑hole
:53, :44433/pihole/ |
A B |
dnscrypt‑proxy
pihole |
2.1.18
2026.07.2
|
✔️ | ✔️ | ✔️ |
✖️
✔️ |
|
qBittorrent
:443/qbittorrent/ |
A | qbittorrent‑nox |
5.2.3
|
✔️ | ✔️ | ✔️ | ✖️ |
|
Tang
:802/tang/ |
A | tang |
git.02...c8
|
✔️ | ✔️ | ✔️ | ✖️ |
|
Teslamate
:24432/teslamate/ |
B B |
db
teslamate |
18.3...ne
2.2.0
|
✔️ | ✔️ | ✔️ |
✔️
✖️ |
|
Tiny HTTPD
:443/ |
A | tiny-httpd |
2.29-r5
|
✔️ | ✔️ | ✔️ | ✖️ |
|
Traefik
:443, :802, :803, :2443, :4431, :4432 :4433, :4443, :24431, :24432, :24433 :44431, :44432, :44433 :24432/traefik |
B | traefik |
3.7.9
|
✔️ | ✔️ | ✖️ | ✔️ |
|
Unifi
:3478, :8080, :8843/, :10001 |
B X |
mongodb
unifi |
8.2.5
10.4.57
|
✔️ | ✔️ |
✖️
✖️ |
✖️
✖️ |
|
WireGuard
:5432 |
A | wg |
2025.05.21-p1
|
✔️ | ✔️ | ✔️ | ✔️ |
|
Zigbee2MQTT
:4432/zigbee2mqtt/ |
A | tiny-httpd |
2.12.1
|
✔️ | ✔️ | ✔️ | ✔️ |
Below, we say that a container is a non-root container,
if it allows running the target service as a non-root user,
e.g. using --user with docker run.
Note that this is different from (less secure compared to) running a root-less container, i.e., with a root-less docker daemon.
| A | Trustworthy: Non-root containers with binaries compiled during build, or from OS repos |
|---|---|
| B | Secure: Non-root containers with open-source binaries only |
| C | Open: Root-ed containers with open-source binaries only |
| X | Untrusted: Containers with closed-source binaries |
| (Default Ports) | HTTP (?80?) | HTTPS (?443?) | |||
| Service (80?) |
Internal (4443?) |
Monitor (2443?) |
Service (443?) |
||
| WAN | Shared (*) |
✖️ | 2443 | 443 | |
| Exclusive (*1) |
24431 | 4431 | |||
| LAN | Shared (*2) |
802 | 44432 | 24432 | 4432 |
| Exclusive (*3) |
803 | 44433 | 24433 | 4433 | |
- Shared: Ports for applications that support serving under subpaths
- Exclusive: Ports for containers that do not support subpaths
- Service: Ports for end-user UI
- Monitor: Ports for monitoring applications
- Internal: Ports for other internal applications
All local tweaks should be added to *.override.* files.
-
at the repo root:
versions.global.override.envmay store global version numbers- these are exposed as-is to docker compose
- see
versions.global.envfor the default
static.global.override.envmay store global constants- e.g. ACME configs, ports to be open etc.
- these are exposed as-is to docker compose
- see
static.global.envfor the default
dynamic.global.override.env.shmay generate additional dynamic variables- e.g. as public IP, UID of calling user etc.
- these are (re)computed just before running each a composition
- see
dynamic.global.env.shfor the default
-
within each composition:
versions.global.override.envmay store additional composition-specific version numbers- similar idea as its global counterpart
versions.global.env - see
unifi/versions.envfor an example
- similar idea as its global counterpart
static.override.envmay store additional composition-specific constants- similar idea as its global counterpart
static.global.env - see
gitea/static.envfor an example
- similar idea as its global counterpart
dynamic.override.env.shmay generate additional composition-specific environment variables- similar idea as its global counterpart
dynamic.global.env.sh - see
pihole/dynamic.env.shfor an example
- similar idea as its global counterpart
meta.override.ymlmay:- specify other compositions as prerequisites
- see
airdcpp/meta.ymlfor an example - these are started and validated before starting the composition
- see
- specify other compositions as related
- see
tang/meta.ymlfor an example - these are optionally started and validated after starting the composition
- see
- contain overrides for the
{devices|labels|logging|ports}fragments
(flags used on the command-line will further override this)- see
.github/workflows/config/meta.override.ymlfor an example
- see
- specify messages to be displayed after verb executions
- see
traefik/meta.ymlfor an example
- see
- specify other compositions as prerequisites
compose.override.{yml|yaml}may contain overrides for docker compose- modular overrides may also be specified for individual YAML fragment files:
compose.{devices|labels|logging|ports}.override.{yml|yaml}
- modular overrides may also be specified for individual YAML fragment files:
compose.{pre,post}_hook.override.*.shmay define additional hooks to be run beforedocker compose- see
nextcloud/compose.pre_hook.shfor an example
- see
compose.{up,down,clean}.{pre,post}_hook.override.*.shmay define additional verb-specific hooks to be run- see
monitarr/compose.up.pre_hook.shfor an example
- see
- immediate subdirectories under
config/,data/,extra/, andgenerated/must match the service names withincompose.yml - the directory structure at each of
config/X/Y/...,data/X/Y/...,extra/X/Y/..., andgenerated/X/Y/...must match the absolute path hierarchy/Y/...in the container for serviceX
| Mount mode | Git commit | Comments | |
|---|---|---|---|
| config/ | :ro or - |
✔️ | |
| data/ | :rw |
✖️ | |
| env/ | - | ✔️ | May contain env_files |
| extra/ | - | ✔️ | Only indirect access, e.g. via generated/ |
| generated/ | :ro |
✖️ |