Docker Swarm
← Home
Five-node Swarm cluster managed from PCT 108 (network, 192.168.2.82). PCT 109 (ai) is not a Swarm node — all its services are standalone Docker Compose.
| Node | Hostname | IP | Role |
|---|---|---|---|
| PCT 101 | downloads | 192.168.2.190 | Worker |
| PCT 102 | media-core | 192.168.2.191 | Worker |
| PCT 104 | documents | 192.168.2.105 | Worker |
| PCT 107 | debian | 192.168.2.81 | Worker |
| PCT 108 | network | 192.168.2.82 | Manager |
Key Commands
All Swarm commands run from the Proxmox host via pct exec 108 --.
# Status pct exec 108 -- docker node ls pct exec 108 -- docker stack ls pct exec 108 -- docker service ls # Inspect a service pct exec 108 -- docker service ps <service> # failed replica history pct exec 108 -- docker service logs <service> --tail 50 # Deploy / redeploy pct exec 108 -- docker stack deploy -c <compose> <stack> pct exec 108 -- docker service update --force <service> # restart + fresh overlay IP # Check containers on a specific node pct exec 102 -- docker ps -a --filter name=<name> pct exec 104 -- docker ps
Compose Files
Stored in Portainer on PCT 108:
/mnt/tank/appdata/portainer/compose/<id>/docker-compose.yml
Portainer env var gotcha: Portainer stores stack env vars in its internal DB (portainer.db) — there are no .env files on disk. When redeploying via CLI, export vars manually:
pct exec 108 -- bash -c 'export VAR=val && docker stack deploy -c <compose> <stack>'
Portainer Directory → Stack Mapping
| Dir ID | Stack |
|---|---|
| 2 | documents-linkwarden |
| 4 | documents-paperless |
| 6 | network-guacamole |
| 7 | documents-nextcloud |
| 10 | documents-immich |
| 22 | documents-homarr |
| 23 | authentik |
| 25 | dozzel |
| 26 | media-core |
| 28 | documents-actual-budget |
| 31 | network-cloudbeaver |
| 32 | romm |
Swarm Labels vs Container Labels
In Docker Swarm, only deploy.labels are read by Traefik — container-level labels: are ignored.
services: myservice: deploy: labels: - "traefik.enable=true" - "traefik.http.routers.myservice.rule=Host(`myservice.carr-family.org`)" - "traefik.http.services.myservice.loadbalancer.server.port=8080"
Cross-Provider Middleware Reference
Middlewares defined in routes.yml (file provider) must be referenced with @file suffix in Swarm labels:
- "traefik.http.routers.myservice.middlewares=lan-only@file"
Plain names default to @swarm and return 404.
Overlay Network Health Check
Services on PCT 102 can develop stale VXLAN attachments after redeployments, causing 504s even though the service shows 1/1 and the container is healthy.
Diagnose:
TRAEFIK=$(pct exec 108 -- docker ps -q --filter name=traefik) IP=$(pct exec 102 -- docker inspect <container-name> --format '{{(index .NetworkSettings.Networks "traefik-public").IPAddress}}') pct exec 108 -- docker exec $TRAEFIK ping -c 2 $IP
Fix:
pct exec 108 -- docker service update --force <service-name>
All 11 media-core services were force-updated 2026-05-13 to clear a full-stack stale attachment event.
Standalone Containers (not Swarm-managed)
Several services run as docker compose or docker run directly on specific nodes, outside Swarm. They need static routes in Traefik's routes.yml since Swarm label discovery doesn't apply.
| Container | Node | IP:Port | Route |
|---|---|---|---|
qbittorrent-mam |
PCT 101 | 192.168.2.190:8080 | static route |
qbittorrent-vpn |
PCT 101 | 192.168.2.190:8081 | static route |
gluetun-proton |
PCT 101 | — | VPN gateway only |
qui |
PCT 101 | 192.168.2.190:7476 | static route |
jellyfin |
PCT 101 | 192.168.2.191:8096 | static route |
otterwiki |
PCT 104 | 192.168.2.105:8081 | static route |
litellm + postgres |
PCT 109 | 192.168.2.83:4000 | static route |
n8n + qdrant |
PCT 109 | 192.168.2.83:5678 | static route |
openclaw |
PCT 109 | 192.168.2.83:18789 | static route |
odysseus |
PCT 109 | 192.168.2.83:7000 | static route |
firecrawl |
PCT 109 | 192.168.2.83:3002 | no route |
whisper |
PCT 109 | 192.168.2.83:8001 | no route |
proton-bridge |
PCT 109 | 192.168.2.83:1025/1143 | no route |
| Pterodactyl Panel | PCT 300 | 192.168.2.136:80 | static route (lan-only) |
General Compose Notes
- All services use
PUID=1000 PGID=1000andTZ=America/Torontounless noted gai_conf_ipv4is an external Docker config used by Prowlarr to force IPv4 DNS- Before adding bind mounts:
mkdir -p /tank/appdata/<app>/<dir> && chown 1000:1000 ...on the host first - PCT 108 cannot read
/tank/appdata/<app>/files directly from other nodes' compose dirs — always pipe compose files in via stdin when deploying from a compose file that lives on another node
