Traefik: first steps

If Ansible helped me declare the desired state of my machines, I still needed a simple, automatic way to route HTTP/HTTPS traffic to my various services (Docker, VMs, bare metal). After several recommendations, I dove into Traefik, a reverse proxy / ingress controller written in Go.

Why I chose Traefik

  • Dynamic configuration: Traefik continuously queries its providers (Docker, Kubernetes, files…) and updates routes on the fly, no more nginx -s reload every time a container shows up.
  • Built-in Let’s Encrypt: certificate generation and renewal without a homemade script.
  • Clear dashboard: you can see routes, middlewares, and entry points in real time.
  • Native Docker/K8s support: three labels are enough to expose a service.

My first discoveries

  1. Quick install
   docker network create traefik
   docker compose -f traefik.yml up -d
  1. Static vs dynamic Static: ports, providers, ACME. Dynamic: routing rules, middlewares, certificates, changeable without a restart.

  2. Docker labels Minimal example:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.blog.rule=Host(`blog.example.com`)"
      - "traefik.http.services.blog.loadbalancer.server.port=3000"
    
  3. Let’s Encrypt in 30 seconds A certresolver=le in the labels plus an email address, and Traefik handles the rest.

  4. Middlewares HTTPS redirect, security headers, basic auth… declarable via YAML or a label.


Full example: Traefik + Vaultwarden over HTTPS

1. Traefik stack (traefik.yml)

services:
  traefik:
    image: traefik:v3.0
    restart: unless-stopped
    command:
      - '--providers.docker=true'
      - '--providers.docker.exposedByDefault=false'
      - '--entrypoints.web.address=:80'
      - '--entrypoints.websecure.address=:443'
      - '[email protected]'
      - '--certificatesresolvers.le.acme.storage=/certs/acme.json'
      - '--certificatesresolvers.le.acme.tlschallenge=true'
      - '--api.dashboard=true'
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock:ro'
      - './certs:/certs'
    networks:
      - traefik

networks:
  traefik:
    external: true

2. Exposed service: Vaultwarden (vaultwarden.yml)

version: '3.9'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      WEBSOCKET_ENABLED: "true"
      DOMAIN: "https://vault.example.com"
      SMTP_HOST: "smtp.example.net"
      SMTP_FROM: "[email protected]"
      SMTP_PORT: "587"
      SMTP_SECURITY: "starttls"
      SMTP_USERNAME: "smtp-user"
      SMTP_PASSWORD: "smtp-pass"
    volumes:
      - ./bw-data:/data
    networks:
      - traefik
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.vault.rule=Host(`vault.example.com`)'
      - 'traefik.http.routers.vault.entrypoints=websecure'
      - 'traefik.http.routers.vault.tls.certresolver=le'
      - 'traefik.http.services.vault.loadbalancer.server.port=80'
      - 'traefik.http.routers.vault.middlewares=vault-redirect'
      - 'traefik.http.middlewares.vault-redirect.redirectscheme.scheme=https'

networks:
  traefik:
    external: true

What I like most

  • Zero downtime: adding or changing a service doesn’t interrupt any connection.
  • Real-time dashboard: debugging is super easy, you immediately see the active rules.
  • Self-managed certificates: no more Let’s Encrypt renewal cron jobs.

What I liked (a little) less

FrictionDetail
Verbose labelsA complex service → 10-15 labels; it’s hard on the eyes.
K8s-style hierarchyEntrypoint → Router → Middleware → Service: takes some getting used to.
Multi-provider prioritiesDocker + K8s + File: watch out for rule conflicts.

Things to explore

  • Traefik plugins: custom middlewares (rate-limit, OAuth…)
  • ForwardAuth + Keycloak: SSO for self-hosted apps
  • Prometheus monitoring: detailed metrics
  • Redundancy: testing in swarm or K8s cluster

Conclusion

Traefik replaced my homemade Nginx configs. Today I have a reverse proxy that updates itself, SSL certificates that renew without me thinking about it, and a dashboard where I can see everything at a glance.

Next step: centralized authentication and community plugins.