Gotchas¶
Quirks and footguns worth knowing before touching the stack. Each is also noted on its own service's page; this page is the consolidated view.
Environment¶
sudo.ws, notsudo. Ubuntu 26.04 ships Rust sudo (sudo-rs) as the defaultsudo, and Ansiblebecomehangs against it.ansible.cfgpointsbecome_exeatsudo.wsinstead — this applies to every play. Thepvehost itself is Debian-based and keeps plainsudoin itshost_varsoverride.proxmox_vm_validate_certs: false(group_vars/proxmox_vms.yml) is a deliberate but temporary relaxation: the test Proxmox host presents PVE's default self-signed cluster CA. Re-enable once the host has a trusted certificate.- Linters (
ansible-lint,yamllint) run frompipxon~/.local/bin, which must be onPATHformake test/make lintto find them — they have gone missing between sessions before.
Known inconsistencies to verify¶
vault_sysadmin_passwordis plaintext, not!vault-encrypted, in the liveinventory/group_vars/all/vault.yml, unlike every other populated key in that file. Confirm this is intentional for the disposable test host before relying on it being vault-protected, and encrypt it before this stack touches anything that matters.timezoneis never defined. Pi-hole and Nextcloud key their container timezone off a plain{{ timezone | default('Etc/UTC') }}, but notimezonevariable exists anywhere in this repository — onlycommon_timezone(Europe/London, consumed only by thecommonrole) is set. Both appliances silently run onEtc/UTC. Worth fixing before relying on their logs/timestamps being in local time.- Nagios is pinned below its originally intended tag.
4.5.9does not exist on Docker Hub forjasonrivers/nagios;4.5.7(2024) is the newest tag actually published, and lags upstream Nagios Core. A move to a maintained image is tracked as separate follow-up work.
Authentication model exceptions¶
Forward-auth (authentik@swarm) is the fallback, used only where a service
has no native OIDC/SAML — see each service's Auth column on the
Services page. Three further exceptions to that rule,
each deliberate:
- Emby, Navidrome: kept off Authentik entirely — their TV/mobile/Subsonic clients authenticate directly against the server, and a forward-auth gate on the router would break them.
- Lychee: kept off Authentik because it serves public shared galleries that anonymous visitors must reach without logging in.
- Matomo: uses native OIDC for admin login only; the tracking endpoints
(
/matomo.php,/matomo.js) must stay public for instrumented sites to post analytics, so there is no blanket forward-auth on the domain. - Firefly-III: the opposite exception — forward-auth is not just a
fallback here but load-bearing.
AUTHENTICATION_GUARD=remote_user_guarddisables Firefly's own login entirely and trusts theX-authentik-emailheader completely; removing the middleware without re-enabling native login would let a client spoof that header. - Ontime: forward-auth currently gates its public output/overlay views too, which arguably should stay open (e.g. for a stream overlay). Flagged as known future work in the compose template, not yet split onto its own unauthenticated router.
Each multi-container stack has a private network¶
Every stack with more than one container defines its own private internal
overlay network (Swarm namespaces it as <stackname>_internal, so it never
collides across stacks even though every stack names it internal).
Backend-only containers (databases, caches, workers) join only internal;
the container(s) a stack actually exposes through Traefik join both
internal and the shared traefik network. This closes what was previously
a real service-name collision risk on Swarm's embedded DNS: several stacks
reuse the same generic service name (db, postgresql, redis,
postgres), which was only safe once each stack got its own network.
Fixed in issue #109.
Single-container stacks (beszel, duplicati, emby, linkding, lychee, mealie,
nagios, navidrome, ontime, openproject, whatsupdocker, zigbee2mqtt, traefik
itself) have nothing to isolate and stay on the shared network alone. The
arr stack is the one exception with more than one container where every
service still joins the shared network directly: each of its five apps is
individually routed through Traefik, and internal is additional, carrying
their inter-app RPC (Prowlarr sync, arr-to-Transmission) instead.
A changed Swarm secret fails the deploy until forced¶
docker_secret detects a changed value itself (a stored label hash) and
tries to recreate the secret to match, whether or not force is set; Docker
secrets are immutable, so recreating means removing the current one first.
Docker refuses that removal while any service still references it, so a
plain deploy-stacks.yml run fails outright ("... is in use by the
following service ...") the moment a vault.yml value changes for a stack
that is already deployed — it does not silently keep the old value. That
failure is the signal to set stack_force_secret_rotation: true
(-e stack_force_secret_rotation=true), which tears the whole stack down
first (freeing every one of its secrets), recreates them, and redeploys.
Defaults to false so a routine run with no intended secret change never
takes a stack offline; expect the stack's full downtime for the run's
duration when it is set, not just the affected service restarting.
Authentik surfaced the underlying immutability gap as Secret key missing
after an earlier run left authentik_secret_key empty; the same
docker_secret task creates every stack's Swarm secrets, so any of them can
hit this.
Fixed in issue #115.
Authentik blueprints must be .yaml and world-readable¶
Authentik's custom-blueprint auto-discovery under /blueprints/custom only
scans for .yaml; a blueprint rendered as .yml is silently never applied,
no error either. The mounted directory also needs to stay world-readable:
the shared stack role's extra-templates directory defaults to 0750,
root-only, which blocks the non-root authentik worker from reading it at
all. Every entry in the authentik stack's extra_templates
(playbooks/deploy-stacks.yml) renders to a .yaml destination and sets
dir_mode: "0755" to cover both.
Fixed in issue #117.
No explicit startup ordering¶
No compose file in the repository uses depends_on: — Swarm mode doesn't
use it. Services either tolerate their database not being ready yet, or (for
Gitea's OIDC bootstrap) poll until the dependency is ready. Don't assume a
deploy brings services up in dependency order.
Manual post-deploy steps¶
Not automated by Ansible; each is real friction on every fresh deploy of that service, see Deployment for the full table:
- Gitea's OIDC login source (
oidc-bootstrap.sh, idempotent and self-polling). - Fleet's SAML org settings (
fleetctl gitops). - Snipe-IT's SAML config (admin UI only, no config-file surface).
- Matomo's LoginOIDC plugin install and endpoint/client config (admin UI only).
- Nextcloud AIO's and OpenProject's first-run setup wizards.
Not yet live¶
- Zigbee2MQTT is non-functional: the USB adaptor is unchosen
(
zigbee_usb_hostspec,zigbee_serial_deviceboth empty) and the Home Assistant Mosquitto broker (mqtt_host) is unconfigured. - Emby runs CPU-only transcoding: no GPU is physically fitted in
swarm-mediayet (docker_install_nvidia_toolkit: falseoverride). - Home Assistant's
homeassistant_vm_ip(10.36.0.14) is declared for Traefik's file provider, but not applied by Ansible: HAOS has no cloud-init, so a DHCP reservation keyed tohome_assistant_mac_addressis what makes the address actually match. It also has nohost_vars/homeassistant.ymlof its own. - No router DHCP reservation exists yet for any of the five VMs' pinned MACs
(
proxmox_vm_mac_address/home_assistant_mac_address); see Network. - Duplicati has nothing to back up until
nfs_client_mountsis configured so the shared NFS export is actually mounted on the Swarm managers. - Media and experimental tier
vault_*variables are not populated in any real vault yet (they live in the[DEFERRED]block ofvault.yml.example) — don't deploy those tiers against a populated Swarm until they are, or setdeploy_stack_tiersto excludemedia/experimental(see Deployment).
Test-environment values, not production facts¶
vault.yml.test's generated secrets, and the Proxmox host address/API token
in the live vault (pve.lan.mw.jasonrivers.co.uk, 192.168.0.76), belong to
the disposable test deployment. Rotate the Proxmox credentials before
reusing this repository against a different or permanent Proxmox host.