Traefik Integration¶
Compose Farm can generate Traefik file-provider configuration for routing traffic across multiple hosts.
The Problem¶
When you run Traefik on one host but stacks on others, Traefik's docker provider can't see remote containers. The file provider bridges this gap.
Internet
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Host: nuc │
│ │
│ ┌─────────┐ │
│ │ Traefik │◄─── Docker provider sees local containers │
│ │ │ │
│ │ │◄─── File provider sees remote stacks │
│ └────┬────┘ (from compose-farm.yml) │
│ │ │
└───────┼─────────────────────────────────────────────────────┘
│
├────────────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Host: hp │ │ Host: nas │
│ │ │ │
│ plex:32400 │ │ jellyfin:8096 │
└───────────────┘ └───────────────┘
How It Works¶
- Your compose files have standard Traefik labels
- Compose Farm reads labels and generates file-provider config
- Traefik watches the generated file
- Traffic routes to remote stacks via host IP + published port
Setup¶
Step 1: Configure Traefik File Provider¶
Add directory watching to your Traefik config:
# traefik.yml or docker-compose.yml command
providers:
file:
directory: /opt/traefik/dynamic.d
watch: true
Or via command line:
services:
traefik:
command:
- --providers.file.directory=/dynamic.d
- --providers.file.watch=true
volumes:
- /opt/traefik/dynamic.d:/dynamic.d:ro
Step 2: Add Traefik Labels to Services¶
Your compose files use standard Traefik labels:
# /opt/compose/plex/docker-compose.yml
services:
plex:
image: lscr.io/linuxserver/plex
ports:
- "32400:32400" # IMPORTANT: Must publish port!
labels:
- traefik.enable=true
- traefik.http.routers.plex.rule=Host(`plex.example.com`)
- traefik.http.routers.plex.entrypoints=websecure
- traefik.http.routers.plex.tls.certresolver=letsencrypt
- traefik.http.services.plex.loadbalancer.server.port=32400
Important: Services must publish ports for cross-host routing. Traefik connects via host_ip:published_port.
Step 3: Generate File Provider Config¶
This generates:
# /opt/traefik/dynamic.d/compose-farm.yml
http:
routers:
plex:
rule: Host(`plex.example.com`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: plex
services:
plex:
loadBalancer:
servers:
- url: http://192.168.1.11:32400
Auto-Regeneration¶
Configure automatic regeneration in compose-farm.yaml:
compose_dir: /opt/compose
traefik_file: /opt/traefik/dynamic.d/compose-farm.yml
traefik_stack: traefik
hosts:
nuc:
address: 192.168.1.10
hp:
address: 192.168.1.11
stacks:
traefik: nuc # Traefik runs here
plex: hp # Routed via file-provider
sonarr: hp
With traefik_file set, these commands auto-regenerate the config:
- cf up
- cf down
- cf restart
- cf update
- cf apply
traefik_stack Option¶
When set, stacks on the same host as Traefik are skipped in file-provider output. Traefik's docker provider handles them directly.
traefik_stack: traefik # traefik runs on nuc
stacks:
traefik: nuc # NOT in file-provider (docker provider)
portainer: nuc # NOT in file-provider (docker provider)
plex: hp # IN file-provider (cross-host)
Label Syntax¶
Routers¶
labels:
# Basic router
- traefik.http.routers.myapp.rule=Host(`app.example.com`)
- traefik.http.routers.myapp.entrypoints=websecure
# With TLS
- traefik.http.routers.myapp.tls=true
- traefik.http.routers.myapp.tls.certresolver=letsencrypt
# With middleware
- traefik.http.routers.myapp.middlewares=auth@file
Services¶
labels:
# Load balancer port
- traefik.http.services.myapp.loadbalancer.server.port=8080
# Health check
- traefik.http.services.myapp.loadbalancer.healthcheck.path=/health
Middlewares¶
Middlewares should be defined in a separate file (not generated by Compose Farm):
# /opt/traefik/dynamic.d/middlewares.yml
http:
middlewares:
auth:
basicAuth:
users:
- "user:$apr1$..."
Reference in labels:
Variable Substitution¶
Labels can use environment variables:
Compose Farm resolves variables from:
1. Stack's .env file
2. Current environment
Port Resolution¶
Compose Farm determines the target URL from published ports:
If no suitable port is found, a warning is shown.
Complete Example¶
compose-farm.yaml¶
compose_dir: /opt/compose
traefik_file: /opt/traefik/dynamic.d/compose-farm.yml
traefik_stack: traefik
hosts:
nuc:
address: 192.168.1.10
hp:
address: 192.168.1.11
nas:
address: 192.168.1.100
stacks:
traefik: nuc
plex: hp
jellyfin: nas
sonarr: nuc
radarr: nuc
/opt/compose/plex/docker-compose.yml¶
services:
plex:
image: lscr.io/linuxserver/plex
container_name: plex
ports:
- "32400:32400"
labels:
- traefik.enable=true
- traefik.http.routers.plex.rule=Host(`plex.example.com`)
- traefik.http.routers.plex.entrypoints=websecure
- traefik.http.routers.plex.tls.certresolver=letsencrypt
- traefik.http.services.plex.loadbalancer.server.port=32400
# ... other config
Generated compose-farm.yml¶
http:
routers:
plex:
rule: Host(`plex.example.com`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: plex
jellyfin:
rule: Host(`jellyfin.example.com`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: jellyfin
services:
plex:
loadBalancer:
servers:
- url: http://192.168.1.11:32400
jellyfin:
loadBalancer:
servers:
- url: http://192.168.1.100:8096
Note: sonarr and radarr are NOT in the file because they're on the same host as Traefik (nuc).
Combining with Existing Config¶
If you have existing Traefik dynamic config:
# Move existing config to directory
mkdir -p /opt/traefik/dynamic.d
mv /opt/traefik/dynamic.yml /opt/traefik/dynamic.d/manual.yml
# Generate Compose Farm config
cf traefik-file --all -o /opt/traefik/dynamic.d/compose-farm.yml
# Update Traefik to watch directory
# --providers.file.directory=/dynamic.d
Traefik merges all YAML files in the directory.
Troubleshooting¶
Stack Not Accessible¶
-
Check port is published:
-
Check label syntax:
-
Verify generated config:
-
Check Traefik logs:
Config Not Regenerating¶
-
Verify traefik_file is set:
-
Check file permissions:
-
Manually regenerate:
Variable Not Resolved¶
-
Check .env file exists:
-
Test variable resolution: