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:
HTTP
→HTTPS
HTTPS
→HTTPS
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:
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
Referencias
- Traefik Proxy Documentation
- The Ultimate Guide to Setting Up Traefik
- Introduction to Traefik
- Traefik v2 Examples
- Configuring Traefik to work over Taiscale
- Traefik Let’s Encrypt Certificates Configuration
Historial de cambios
- 2025-08-26: Documento inicial