Traefik

Traefik config lives on Docker labels attached to your containers, so there’s no separate config file to babysit. It also handles SSL through Let’s Encrypt automatically.

Prerequisites

You’ll want a Traefik instance already running on the same Docker network, with a proxy network defined and a Let’s Encrypt certificate resolver named letsencrypt. New to this? The Traefik docs walk through the setup.

Labels

Add these labels to the hub service in your docker-compose.yml:

docker-compose.yml
services:
  hub:
    image: lxghtblvee/perch-hub:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.perch.rule=Host(`metrics.example.com`)"
      - "traefik.http.routers.perch.entrypoints=websecure"
      - "traefik.http.routers.perch.tls.certresolver=letsencrypt"
      - "traefik.http.services.perch.loadbalancer.server.port=8484"
    networks:
      - proxy
      - default
    environment:
      PERCH_HUB_TOKEN: ${PERCH_HUB_TOKEN}
      PERCH_DB_HOST: db
      PERCH_DB_USER: perch
      PERCH_DB_PASS: ${PERCH_DB_PASS}
      PERCH_DB_NAME: perch
      PERCH_ADMIN_EMAIL: ${PERCH_ADMIN_EMAIL}
      PERCH_ADMIN_PASSWORD: ${PERCH_ADMIN_PASSWORD}
      PERCH_BASE_URL: https://metrics.example.com
    volumes:
      - hub-uploads:/app/apps/hub/uploads
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

networks:
  proxy:
    external: true
  default:

WebSockets just work

Traefik passes WebSocket upgrades through with no extra config, so the live dashboard and container logs work out of the box.

Custom domain status pages

Using custom domains on status pages? You’ll want a catch-all router with a lower priority sitting next to your main one.

Replace the label block above with this version, which keeps your hub domain as the primary route and sends everything else to the same service:

labels:
  - "traefik.enable=true"

  # Primary router: your hub domain
  - "traefik.http.routers.perch.rule=Host(`metrics.example.com`)"
  - "traefik.http.routers.perch.entrypoints=websecure"
  - "traefik.http.routers.perch.tls.certresolver=letsencrypt"
  - "traefik.http.routers.perch.priority=10"
  - "traefik.http.services.perch.loadbalancer.server.port=8484"

  # Catch-all router: custom status page domains
  - "traefik.http.routers.perch-custom.rule=HostRegexp(`.+`)"
  - "traefik.http.routers.perch-custom.entrypoints=websecure"
  - "traefik.http.routers.perch-custom.tls.certresolver=letsencrypt"
  - "traefik.http.routers.perch-custom.priority=1"
  - "traefik.http.routers.perch-custom.service=perch"

The catch-all sits at priority 1, so any domain that a higher-priority router doesn’t claim falls through to the hub. The hub then serves the right status page based on the Host header. Your main metrics.example.com router stays at priority 10, so it always wins for your dashboard.