---
icon: lucide/globe
---

# 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:

```yaml
# traefik.yml or docker-compose.yml command
providers:
  file:
    directory: /opt/traefik/dynamic.d
    watch: true
```

Or via command line:

```yaml
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:

```yaml
# /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

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

This generates:

```yaml
# /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`:

```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
  grafana: hp
```

With `traefik_file` set, these commands auto-regenerate the config:
- `cf up`
- `cf down`
- `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.

```yaml
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

```yaml
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

```yaml
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):

```yaml
# /opt/traefik/dynamic.d/middlewares.yml
http:
  middlewares:
    auth:
      basicAuth:
        users:
          - "user:$apr1$..."
```

Reference in labels:

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

## Variable Substitution

Labels can use environment variables:

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

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

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

## Port Resolution

Compose Farm determines the target URL from published ports:

```yaml
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

```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
  grafana: nuc
  nextcloud: nuc
```

### /opt/compose/plex/docker-compose.yml

```yaml
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

```yaml
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: `grafana` and `nextcloud` 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:

```bash
# 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:**
   ```yaml
   ports:
     - "8080:80"  # Must be published, not just exposed
   ```

2. **Check label syntax:**
   ```bash
   cf check mystack
   ```

3. **Verify generated config:**
   ```bash
   cf traefik-file mystack
   ```

4. **Check Traefik logs:**
   ```bash
   docker logs traefik
   ```

### Config Not Regenerating

1. **Verify traefik_file is set:**
   ```bash
   cf config show | grep traefik
   ```

2. **Check file permissions:**
   ```bash
   ls -la /opt/traefik/dynamic.d/
   ```

3. **Manually regenerate:**
   ```bash
   cf traefik-file --all -o /opt/traefik/dynamic.d/compose-farm.yml
   ```

### Variable Not Resolved

1. **Check .env file exists:**
   ```bash
   cat /opt/compose/myservice/.env
   ```

2. **Test variable resolution:**
   ```bash
   cd /opt/compose/myservice
   docker compose config
   ```
