Skip to content

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.

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:

SidecarPurpose
arcane-agentContainer management via Arcane
dozzle-agentLog aggregation into the central Dozzle UI

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 at stepca.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.

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.

Always through bws run, so secrets are present:

Terminal window
bws run --access-token "$BWS_ACCESS_TOKEN" -- docker compose up -d <service>

Fleet-wide, use the Ansible playbook instead:

Terminal window
ansible-playbook -i inventory/proxmox.proxmox.yml playbooks/restart-docker-stacks.yml

A 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.

Custom images are built and pushed to Harbor by tagging image/<name>-<version>. See Publishing images to Harbor.