The Tech Debtor

Follow

github linkedin

Share

Twitter Linkedin Facebook

Saves

Directing Traefik

by Travis Boettcher

Directing Traefik
Photo by Denys Nevozhai / Unsplash

So - you've set up some fancy new service on your home server that will let you digitize and get rid of that pile of papers that keeps stacking up on top of your file cabinet (instead of actually filing the papers - yay procrastination!). The problem: you can only access said service on 192.168.0.37:8234. Oh, and now you have 7 other services that you also need to remember the IP and port for.

You might be able to solve part of the problem at the router, or using an internal DNS, but if you have multiple services running on one server, you probably still need to remember multiple port numbers.

Another solution would be to use a reverse proxy - maybe using NGINX, Apache, or HAProxy. The solution I ended up going with was Traefik. Traefik allows proxying traffic to your services, using a number of configuration providers. Check out their excellent documentation for a detailed look at the various concepts you can apply to the routing. Below, I will detail my setup and the various configurations and conventions I use.

Static Configuration

Traefik configuration is broken up between static and dynamic configuration. The static configuration is only loaded once on startup, whereas the dynamic configuration is automatically reloaded as it changes.

The following is my static configuration:

# Enables the Traefik API and Dashboard
# Dynamic config also needed to correctly route
api:
  dashboard: true
  
# Enable the `/ping` health-check endpoint
ping: {}

# Enable prometheus metrics for routers and services
metrics:
  prometheus:
    addRoutersLabels: true
    addServicesLabels: true

# Enable dynamic configuration through
# docker and the file provider
providers:
  docker: {}
  file:
    directory: /etc/traefik/conf
    watch: true

# The entrypoints to listen on
entryPoints:
  # The standard HTTPS port, with a cert resolver
  web:
    address: ":443"
    proxyProtocol:
      trustedIPs: 10.1.10.0/24
    http:
      tls:
        certResolver: lets-encrypt
  
  # The standard HTTP port, for internal services
  local:
    address: ":80"
    proxyProtocol:
      trustedIPs: 10.1.10.0/24
  
  # An SFTP endpoint
  sftp:
    address: ":2222/tcp"
    proxyProtocol:
      trustedIPs: 10.1.10.0/24

# The cert resolver for HTTPS, using
# Let's Encrypt and the TLS challenge
certificatesResolvers:
  lets-encrypt:
    acme:
      email: ********
      storage: /etc/traefik/acme/acme.json
      tlsChallenge: {}

Dynamic Configuration

The dynamic configuration defines the routers and services. For the most part, I use the Docker provider, using labels on the containers, as can be seen below:

version: '3.8'
services:
  reverse-proxy:
    image: traefik:v2.10
    restart: unless-stopped
    ports:
      - "80:80"
      - "8080:8080"
      - "443:443"
      - "2222:2222/tcp"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik:/etc/traefik
    healthcheck:
      test: traefik healthcheck
      interval: 1m30s
      timeout: 10s
      retries: 3
      start_period: 30s
    extra_hosts:
      - "host.docker.internal:host-gateway"
    labels:
      - "traefik.http.routers.dashboard.rule=Host(`traefik.techdebtor.io`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.entrypoints=web"
      - "traefik.http.routers.dashboard.middlewares=basic-auth"
      - "traefik.http.middlewares.basic-auth.basicauth.users=${TRAEFIK_BASIC_AUTH}"
      - "traefik.http.routers.dashboard-local.rule=Host(`traefik.techdebtor.local`)"
      - "traefik.http.routers.dashboard-local.service=api@internal"
      - "traefik.http.routers.dashboard-local.entrypoints=local"
    
  whoami:
    image: traefik/whoami
    restart: unless-stopped
    container_name: whoami
    labels:
      - "traefik.http.routers.whoami.rule=Host(`whoami.techdebtor.io`)"
      - "traefik.http.routers.whoami.entrypoints=web"
      - "traefik.http.routers.whoami-local.rule=Host(`whoami.techdebtor.local`)"
      - "traefik.http.routers.whoami-local.entrypoints=local"

One problem I have with my current configuration is the need to define two different routers - one for HTTPS and one for HTTP. I use the two different hostnames to avoid the round-trip cost to hit a server that's on the same network - if I use the techdebtor.io host, the device makes a round trip out to my VPS and then back through the wireguard tunnel to my home server; if I use the techdebtor.local host, I can access the home server directly. It's on my list though to solve this configuration complexity.

There are a few services I have where I use the file provider - these are either running on other servers or simply not in Docker. An example of a service running on the Docker host is below:

http:
  routers:
    omv:
      entryPoints:
        - web
      service: omv
      rule: Host(`omv.techdebtor.io`)
    omv-local:
      entryPoints:
        - local
      service: omv
      rule: Host(`omv.techdebtor.local`)
  
  services:
    omv:
      loadBalancer:
        servers:
          - url: http://host.docker.internal:8085/

Finally, although I use Docker Compose for all my Docker container deployments, I don't configure them all in the same file. This means containers configured in other Compose files need a little more assistance so everything plays nicely together. Note the extra label and network in the following example:

version: '3.8'
services:
  dashy:
    image: lissy93/dashy
    volumes:
      - ./config.yml:/app/public/conf.yml
    networks:
      - traefik-net
    expose:
      - 80
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    healthcheck:
      test: node /app/services/healthcheck
      interval: 1m
      timeout: 10s
      retries: 3
      start_period: 10s
    labels:
      - "traefik.http.routers.dashy-local.rule=Host(`dashy.techdebtor.local`)"
      - "traefik.http.routers.dashy-local.entrypoints=local"
      - "traefik.docker.network=traefik_default"
      
networks:
  traefik-net:
    name: traefik_default
    external: true

And that's about it to Traefik! If I come across other cool usages or complicated configurations, I'll be sure to write about those, but this is my basic configuration.

What am I listening to?

This month, I've been listening to You Are Who You Hang Out With by The Front Bottoms.