Docker Stacks
Every directory under docker-compose-apps/ is an independent Compose project. For the
full machine-generated list of stacks, services, images and published ports, see the
Stack catalog.
How a stack is put together
Section titled “How a stack is put together”Small stacks are a single docker-compose.yml. Larger hosts assemble one project out of
per-application files using compose’s include: directive:
include: - host/arcane.yaml - ../dozzle/dozzle-agent.yaml - apps/gluetun/gluetun.yaml - apps/qbittorrent/qbittorrent.yaml # ...That keeps one file per application while still giving compose a single project to manage. The UGREEN NAS stack is the fullest example, pulling in twelve applications; the CIFS apps stack is the same shape on VM 217, for the apps that need the NAS shares mounted.
One consequence of include: worth knowing before adding an app: relative bind mounts
resolve against the file that declares them, not against the project directory. A
./config in apps/foo/foo.yaml lands in apps/foo/config, inside the git checkout.
Two agent sidecars ride along with nearly every stack and are filtered out of the catalog tables to keep them readable:
| Sidecar | Purpose |
|---|---|
arcane-agent | Container management via Arcane |
dozzle-agent | Log aggregation into the central Dozzle UI |
Routing and TLS
Section titled “Routing and TLS”Anything needing routing attaches to the external traefik-web Docker network. Traefik
is the single entry point and holds both certificate resolvers:
- Internal (
*.toolsera.lan) — certificates from the Step CA atstepca.toolsera.lan - Public (
*.toolsera.fr) — Let’s Encrypt via the Cloudflare DNS challenge, reached through a Cloudflare tunnel
Most routers are not defined by compose labels at all — they come from the Traefik Proxmox provider plugin, which reads Traefik labels out of each Proxmox guest’s description field. That is why the catalog deliberately does not print hostnames: scraping compose labels would produce a misleadingly partial list.
Routing a new guest therefore means appending a label block to its description, not
naming the guest correctly and not adding compose labels. A guest with no labels in its
description gets no router however it is named, and because the toolsera.lan zone
wildcards to Traefik, the hostname still resolves — it just answers 404. See the
Dashy stack for a worked example, including the label block
and the pct set command that applies it.
Docker socket access
Section titled “Docker socket access”Traefik and Portainer do not mount the raw Docker socket. They go through
docker-socket-proxy, which exposes a read-only slice of the API — CONTAINERS,
SERVICES and TASKS enabled, POST disabled.
Starting and restarting stacks
Section titled “Starting and restarting stacks”Always through bws run, so secrets are present:
bws run --access-token "$BWS_ACCESS_TOKEN" -- docker compose up -d <service>Fleet-wide, use the Ansible playbook instead:
ansible-playbook -i inventory/proxmox.proxmox.yml playbooks/restart-docker-stacks.ymlA bare docker compose up -d leaves every BWS-only variable empty. See
Architecture for why that matters,
and Host access notes before running any of it on a live
host.
Publishing images
Section titled “Publishing images”Custom images are built and pushed to Harbor by tagging image/<name>-<version>. See
Publishing images to Harbor.