El próximo mes de noviembre se cumplirán tres años desde que instalé Nginx Proxy Manager en Docker por primera vez en mi entorno de #HomeLab.

Aunque estoy bastante contento con su facilidad de uso y comodidad, cuando comienzas a tener servidores y servicios se necesita algo más moderno, dinámico y automatizable.

Por este motivo he decidido instalar Traefik usando Docker pero, a diferencia de la mayoría de configuraciones, no usaré etiquetas para realizar el enrutado de los servicios.

He usado la misma táctica que mi compañero de trabajo Robert y he usado ficheros para dividir la configuración de Traefik en dos categorías principales:

  • configuración de instalación (o configuración estática). Aquella configuración que requiere reiniciar Traefik para aplicarla (entry points, providers, etc.)
  • configuración de enrutado (o configuración dinámica). Incluye elementos que pueden ser actualizados sin reiniciar Traefik (routers, services y middlewares)

Instalación en Docker (compose.yaml)

La instalación en Docker es bastante sencilla y únicamente requiere definir un fichero compose.yaml con el siguiente contenido:

services:
  traefik:
    image: traefik:v3.5.0
    container_name: traefik
    hostname: traefik

    restart: unless-stopped
    security_opt:
      - no-new-privileges:true

    environment:
      TZ: ${TZ:-Europe/Madrid}
      CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN}

    networks:
      - traefik
    ports:
      - "80:80"
      - "443:443"

    volumes:
      - /usr/share/zoneinfo/${TZ:-Europe/Madrid}:/etc/localtime:ro
      - ./data/traefik.yaml:/traefik.yaml:ro
      - ./data/conf.d:/conf.d:ro
      - ./data/ssl:/ssl
      - ./data/logs:/var/log/traefik

    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

networks:
  traefik:
    name: traefik

Para “completar” el entorno se necesitan crear algunos ficheros y directorios:

touch .env
mkdir -p ./data/conf.d ./data/ssl ./data/logs
touch ./data/ssl/acme.json
chmod 600 ./data/ssl/acme.json

En el fichero .env se definirá, como mínimo, la variable CF_DNS_API_TOKEN con un token de Cloudflare que permita editar nuestra zona DNS.

TZ=Europe/Madrid
CF_DNS_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Configuración estática (traefik.yaml)

El fichero ./data/traefik.yaml contendrá la configuración estática de Traefik:

################################################################
# Global configuration
################################################################

global:
  checknewversion: false
  sendanonymoususage: false

################################################################
# API and dashboard configuration
################################################################

api:
  insecure: false
  dashboard: true
  debug: false

################################################################
# Load dynamic configuration from .yaml files in a directory
# - Routers
# - Middleware
# - Services
################################################################

providers:
  file:
    directory: /conf.d
    watch: true

################################################################
# Certificate Resolvers
################################################################

certificatesResolvers:
  letsencrypt:
    acme:
      email: acme@midominio.com
      storage: /ssl/acme.json
      caServer: https://acme-v02.api.letsencrypt.org/directory
      dnsChallenge:
        provider: cloudflare
#        resolvers:
#          - "1.1.1.1:53"
#          - "1.0.0.1:53"

  letsencrypt_staging:
    acme:
      email: acme@midominio.com
      storage: /ssl/acme.json
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory
      dnsChallenge:
        provider: cloudflare
#        resolvers:
#          - "1.1.1.1:53"
#          - "1.0.0.1:53"

################################################################
# EntryPoints configuration
################################################################

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"
    http:
      tls:
#        certResolver: letsencrypt
        certResolver: letsencrypt_staging

################################################################
# Traefik logs configuration
# - common: formato plano (para humanos)
################################################################

log:
  filePath: "/var/log/traefik/traefik.log"
  format: "common"
#  format: "json"
#  level: INFO
  level: DEBUG

################################################################
# Access logs configuration
# - common: Apache/Nginx
# - combined: common + otros campos (Referer, User-Agent, etc.)
# Con los formatos anteriores, no hay filtros/fields/headers
################################################################

accessLog:
  filePath: "/var/log/traefik/traefik-access.log"
  format: "json"
  filters:
    statusCodes:
      - "200"
      - "400-599"
    retryAttempts: true
    minDuration: "10ms"
  bufferingSize: 0
  fields:
    headers:
      defaultMode: drop
      names:
        User-Agent: keep

Nota: Para evitar problemas con los límites del entorno de producción de Let’s Encrypt, se recomienda usar el entorno de staging durante las pruebas iniciales.

Configuración dinámica

El fichero o ficheros con la configuración dinámica se dejarán en el directorio ./data/conf.d.

Este directorio está vigilado constantemente por Traefik y, en cuanto se escriba un fichero de configuración válido, se cargará y se aplicará.

Aunque Traefik permite hacer cosas bastante complejas con los routers, services y middlewares, en mi caso usaré básicamente los enrutados:

  • HTTPHTTPS
  • HTTPSHTTPS

Un ejemplo de fichero de configuración para el primer tipo de servicio es el siguiente:

# Fichero homeassistant.yaml
http:
  routers:
    homeassistant:
      rule: "Host(`homeassistant.midominio.com`)"
      service: homeassistant
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    homeassistant:
      loadBalancer:
        servers:
          - url: "http://<ip-homeassistant>:8123"

Un ejemplo de fichero de configuración para el segundo tipo de servicio (que normalmente tiene certificados auto-firmados que no queremos comprobar) es el siguiente:

# Fichero proxmox.yaml
http:
  routers:
    proxmox:
      rule: "Host(`proxmox.midominio.com`)"
      service: proxmox
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  serversTransports:
    proxmox:
      insecureSkipVerify: true

  services:
    proxmox:
      loadBalancer:
        servers:
          - url: "https://<ip-proxmox>:8006"
        serversTransport: proxmox
        passHostHeader: false

Se puede usar el siguiente script plantilla para pegarlo en la shell y crear un fichero de configuración de forma rápida:

service="homeassistant"
url="http://<ip-homeassistant>:8123"

cat << EOF > "${service}.yaml"
http:
  routers:
    ${service}:
      rule: "Host(\`${service}.midominio.com\`)"
      service: ${service}
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    ${service}:
      loadBalancer:
        servers:
          - url: "${url}"
EOF

Certificados acme.json

El fichero ./data/ssl/acme.json contendrá los certificados obtenidos desde Let’s Encrypt usando un DNS Challenge contra Cloudflare.

Prueba

Si la configuración es correcta, al acceder a un servicio a través de Traefik se debería realizar el enrutado y usar un certificado válido de Let’s Encrypt tal como se muestra a continuación:

Certificado Let's Encrypt

Nota: Aunque no lo he comentado, es necesario crear entradas para cada servicio en el DNS apuntando a la IP de la máquina en la que se haya instalado Traefik ;-)

Protección del dashboard

El dashboard de Traefik se puede proteger con autenticación básica usando el siguiente fichero de configuración dinámica:

# Fichero traefik.yaml
http:
  middlewares:
    traefik:
      basicAuth:
        users:
          - "admin:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

  routers:
    traefik:
      rule: "Host(`traefik.midominio.com`)"
      service: api@internal
      entryPoints:
        - websecure
      middlewares:
        - traefik
      tls:
        certResolver: letsencrypt

Y generando el password del usuario admin mediante la siguiente ejecución del comando htpasswd de las utilidades de Apache:

docker run -it --rm httpd:2.4 /bin/sh
htpasswd -B -n admin

Traefik Dashboard

Referencias

Historial de cambios

  • 2025-08-26: Documento inicial