Reverse Proxy Templates: Traefik

The following are examples for configuring the Traefik proxy as a reverse proxy for Firezone.

In these examples, we assume Traefik is deployed using Docker on the same host as Firezone. For this to work, you'll need to make sure PHOENIX_EXTERNAL_TRUSTED_PROXIES is set to include the Docker address of the Traefik service.

Without SSL termination

Since Firezone requires HTTPS for the web portal, please bear in mind a downstream proxy will need to terminate SSL connections in this scenario.

Use the following docker-compose.yml and rules.yml files as a starting point for your own Firezone config:

docker-compose.yml

version: '3'

x-deploy: &default-deploy
  restart_policy:
    condition: on-failure
    delay: 5s
    max_attempts: 3
    window: 120s
  update_config:
    order: start-first

networks:
  firezone-network:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.25.0.0/16
        - subnet: fcff:3990:3990::/64

services:
  traefik:
    deploy:
      <<: *default-deploy
    # The official v2 Traefik docker image
    image: traefik:v2.8
    # Enables the web UI and tells Traefik to listen to docker
    command:
    - "--providers.docker"
    - "--providers.file.filename=rules.yml"
    - "--entrypoints.web.address=:80"
    - "--entrypoints.web.forwardedHeaders.insecure"
    - "--log.level=DEBUG"
    extra_hosts:
    - "host.docker.internal:host-gateway"
    ports:
      # The HTTP port
      - "80:80"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
      - ./rules.yml:/rules.yml
    # make the IP of this service deterministic
    networks:
      firezone-network:
        ipv4_address: 172.25.0.99
        ipv6_address: fcff:3990:3990::99

  firezone:
    image: firezone/firezone
    ports:
      - 51820:51820/udp
    env_file:
      # This should contain a list of env vars for configuring Firezone.
      # See https://www.firezone.dev/docs/reference/env-vars for more info.
      - ${FZ_INSTALL_DIR:-.}/.env
    volumes:
      # IMPORTANT: Persists WireGuard private key and other data. If
      # /var/firezone/private_key exists when Firezone starts, it is
      # used as the WireGuard private. Otherwise, one is generated.
      - ${FZ_INSTALL_DIR:-.}/firezone:/var/firezone
    cap_add:
      # Needed for WireGuard and firewall support.
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      # Needed for masquerading and NAT.
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv4.ip_forward=1
      - net.ipv6.conf.all.forwarding=1
    depends_on:
      - postgres
    networks:
      firezone-network:
        ipv4_address: 172.25.0.100
    deploy:
      <<: *default-deploy

  postgres:
    image: postgres:15
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${DATABASE_NAME:-firezone}
      POSTGRES_USER: ${DATABASE_USER:-postgres}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD:?err}
    networks:
      - firezone-network
    deploy:
      <<: *default-deploy
      update_config:
        order: stop-first

# Postgres needs a named volume to prevent perms issues on non-linux platforms
volumes:
  postgres-data:

networks:
  firezone-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.25.0.0/16

rules.yml

http:
  routers:
    test:
      entryPoints:
        - "web"
      service: test
      rule: "Host(`44.200.42.78`)"
  services:
    test:
      loadBalancer:
        servers:
          - url: "http://firezone:13000"

Now you should be able to start the Traefik proxy with docker compose up.

With SSL termination

This configuration uses Firezone's auto-generated self-signed certificates.

SSL docker-compose.yml

version: "3"

x-deploy: &default-deploy
  restart_policy:
    condition: on-failure
    delay: 5s
    max_attempts: 3
    window: 120s
  update_config:
    order: start-first

networks:
  app:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 172.28.0.0/16
        - subnet: fcff:3990:3990::/64

services:
  firezone:
    image: firezone/firezone
    ports:
      - 51820:51820/udp
    volumes:
      # IMPORTANT: Persists WireGuard private key and other data. If
      # /var/firezone/private_key exists when Firezone starts, it is
      # used as the WireGuard private. Otherwise, one is generated.
      - ${HOME}/.firezone/firezone:/var/firezone
    environment:
      EXTERNAL_URL: ${EXTERNAL_URL:?err}
      DEFAULT_ADMIN_EMAIL: ${DEFAULT_ADMIN_EMAIL:?err}
      DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD:?err}
      GUARDIAN_SECRET_KEY: ${GUARDIAN_SECRET_KEY:?err}
      SECRET_KEY_BASE: ${SECRET_KEY_BASE:?err}
      LIVE_VIEW_SIGNING_SALT: ${LIVE_VIEW_SIGNING_SALT:?err}
      COOKIE_SIGNING_SALT: ${COOKIE_SIGNING_SALT:?err}
      COOKIE_ENCRYPTION_SALT: ${COOKIE_ENCRYPTION_SALT:?err}
      DATABASE_ENCRYPTION_KEY: ${DATABASE_ENCRYPTION_KEY:?err}

      # Ensure this includes the traefik service IP.
      PHOENIX_EXTERNAL_TRUSTED_PROXIES: [""]
    networks:
      - app
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv4.ip_forward=1
      - net.ipv6.conf.all.forwarding=1
    depends_on:
      - postgres
  traefik:
    deploy:
      <<: *default-deploy
    # The official v2 Traefik docker image
    image: traefik:v2.8
    # Enables the web UI and tells Traefik to listen to docker
    command:
      - "--providers.docker"
      - "--providers.file.filename=rules.yml"
      - "--entrypoints.web.address=:443"
      - "--entrypoints.web.forwardedHeaders.insecure"
      - "--log.level=DEBUG"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    ports:
      # The HTTP port
      - "443:443"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock
      - ./rules.yml:/rules.yml
    # make the IP of this service deterministic
    networks:
      app:
        ipv4_address: 172.28.0.99
        ipv6_address: fcff:3990:3990::99
  postgres:
    image: postgres:15
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      # same value as ## DB section above
      POSTGRES_DB: ${DATABASE_NAME:-firezone}
      POSTGRES_USER: ${DATABASE_USER:-postgres}
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD:?err}
    deploy:
      <<: *default-deploy
      update_config:
        order: stop-first

# Postgres needs a named volume to prevent perms issues on non-linux platforms
volumes:
  postgres-data:

SSL rules.yml

http:
  routers:
    test:
      entryPoints:
        - "web"
      service: test
      rule: "Host(`44.200.42.78`)"
      tls: {}
  services:
    test:
      loadBalancer:
        servers:
          - url: "http://firezone:13000"
tls:
  stores:
    default:
      defaultCertificate:
        certFile: /path/to/your/cert.crt
        keyFile: /path/to/your/key.key