Skip to content

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

  1. Your compose files have standard Traefik labels
  2. Compose Farm reads labels and generates file-provider config
  3. Traefik watches the generated file
  4. 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

cf traefik-file --all -o /opt/traefik/dynamic.d/compose-farm.yml

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:

labels:
  - traefik.http.routers.myapp.middlewares=auth@file

Variable Substitution

Labels can use environment variables:

labels:
  - traefik.http.routers.myapp.rule=Host(`${DOMAIN}`)

Compose Farm resolves variables from: 1. Stack's .env file 2. Current environment

# /opt/compose/myapp/.env
DOMAIN=app.example.com

Port Resolution

Compose Farm determines the target URL from published ports:

ports:
  - "8080:80"           # Uses 8080
  - "192.168.1.11:8080:80"  # Uses 8080 on specific IP

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

  1. Check port is published:

    ports:
      - "8080:80"  # Must be published, not just exposed
    

  2. Check label syntax:

    cf check mystack
    

  3. Verify generated config:

    cf traefik-file mystack
    

  4. Check Traefik logs:

    docker logs traefik
    

Config Not Regenerating

  1. Verify traefik_file is set:

    cf config show | grep traefik
    

  2. Check file permissions:

    ls -la /opt/traefik/dynamic.d/
    

  3. Manually regenerate:

    cf traefik-file --all -o /opt/traefik/dynamic.d/compose-farm.yml
    

Variable Not Resolved

  1. Check .env file exists:

    cat /opt/compose/myservice/.env
    

  2. Test variable resolution:

    cd /opt/compose/myservice
    docker compose config