4

Routers y reglas de enrutamiento

Vistas: 0

Los routers son el corazón de Traefik. En el capítulo anterior viste cómo los EntryPoints son las orejas de Traefik, los puntos por donde entra el tráfico. Pues bien, los routers son el cerebro: se encargan de analizar cada petición entrante y, en función de una serie de reglas, decidir a qué servicio debe dirigirse. Si los EntryPoints son las orejas de Traefik, los routers son el cerebro que decide qué hacer con lo que oyen. Y como todo cerebro, cuanto más entrenado esté, mejores decisiones tomará. De eso va este capítulo: de entrenar a Traefik para que enrute como un auténtico profesional.

¿Cómo funciona un router?

Cada router tiene una serie de propiedades que lo definen:

  • Reglas (rules): condiciones que debe cumplir la petición para que el router la procese.
  • EntryPoints: los puntos de entrada por los que puede recibir tráfico.
  • Servicio: el destino al que enviar la petición cuando coincide la regla.
  • Middlewares: una lista opcional de transformaciones a aplicar.
  • TLS: configuración TLS si aplica.
  • Prioridad: para resolver conflictos entre rutas.

En Docker, los routers se configuran mediante labels. La estructura general que ya vimos en el capítulo anterior es,

labels:
  - "traefik.http.routers.<nombre>.rule=<regla>"
  - "traefik.http.routers.<nombre>.entrypoints=<entrypoint>"
  - "traefik.http.routers.<nombre>.service=<servicio>"
  - "traefik.http.routers.<nombre>.middlewares=<middleware>"
  - "traefik.http.routers.<nombre>.tls=<true/false>"
  - "traefik.http.routers.<nombre>.priority=<numero>"

Pero, ¿qué tipos de reglas existen? ¿Cómo se combinan? ¿Y cómo se evitan conflictos? Vamos a verlo.

Matchers (reglas de enrutamiento)

Traefik v3 ofrece una amplia variedad de matchers para definir las reglas. Cada uno comprueba una parte diferente de la petición HTTP. Puedes usarlos solos o combinarlos para crear reglas tan específicas como necesites. Estos son los más importantes:

Host

Coincide con el nombre de dominio (Host header). Este es el más usado, sin duda. Sirve para que Traefik sepa a qué servicio enviar cada petición según el dominio al que se dirige,

# Coincide exactamente con un dominio
- "traefik.http.routers.mi-app.rule=Host(`miapp.dominio.com`)"

# Múltiples dominios para el mismo servicio
- "traefik.http.routers.mi-app.rule=Host(`app1.com`) || Host(`app2.com`)"

Fíjate en que los valores van entre backticks, no entre comillas. Esa es la sintaxis de las reglas de Traefik y es importante respetarla. Te confundirás más de una vez si vienes de Nginx o Caddy, donde se usan comillas.

Una duda que me surge a menudo: ¿qué pasa con el www? Pues que tienes que tratarlo como un dominio separado. Si tu servicio responde en dominio.com y en www.dominio.com, necesitas ambas reglas,

- "traefik.http.routers.mi-app.rule=Host(`dominio.com`) || Host(`www.dominio.com`)"

O puedes usar HostRegexp para capturarlos a la vez, como verás ahora.

HostRegexp

Coincide con un patrón de dominio usando expresiones regulares. Esto te puede ahorrar mucho trabajo si tienes muchos subdominios,

# Todos los subdominios de dominio.com
- "traefik.http.routers.mi-app.rule=HostRegexp(`{subdomain:[a-z]+}.dominio.com`)"

# Cualquier subdominio (el más usado)
- "traefik.http.routers.mi-app.rule=HostRegexp(`{subdomain:.+}.dominio.com`)"

# Comodín con HostRegexp
- "traefik.http.routers.mi-app.rule=HostRegexp(`{subdomain:.+}.dominio.com`)"

Con HostRegexp puedes usar patrones con named regexps. El {subdomain:.+} captura cualquier subdominio. Para casos más complejos, HostRegexp te da mucho más control.

Por ejemplo, imagina que tienes subdominios por entorno: dev.dominio.com, staging.dominio.com, prod.dominio.com. Puedes capturarlos y usar el valor capturado más adelante con un middleware,

- "traefik.http.routers.mi-app.rule=HostRegexp(`{env:(dev|staging|prod)}.dominio.com`)"

¿Y si necesitas capturar también el dominio de nivel superior? También puedes:

- "traefik.http.routers.mi-app.rule=HostRegexp(`{subdomain:[a-z]+}.{domain:.+}`)"

Esto empareja cualquier cosa como blog.ejemplo.com, api.midominio.org, etc. Eso sí, ten cuidado con reglas demasiado abiertas: podrían capturar tráfico que no esperabas.

Path

Coincide con una ruta exacta,

- "traefik.http.routers.api.rule=Path(`/api`)"

Esto solo coincide con la ruta exacta /api. Si alguien hace una petición a /api/usuarios, no coincidirá. Para eso necesitas PathPrefix.

Un caso práctico donde Path te viene genial: cuando tienes un endpoint concreto que quieres enviar a un servicio diferente del resto. Por ejemplo, un webhook:

- "traefik.http.routers.webhook.rule=Host(`app.dominio.com`) && Path(`/webhook/paypal`)"
- "traefik.http.routers.webhook.service=servicio-paypal"

Solo las peticiones exactas a /webhook/paypal irán al servicio de PayPal. El resto del tráfico a app.dominio.com irá a tu servicio principal.

PathPrefix

Coincide con el prefijo de una ruta. Esto es muy útil cuando tienes un servicio que sirve múltiples rutas bajo un mismo prefijo,

# Cualquier ruta que empiece por /api
- "traefik.http.routers.api.rule=PathPrefix(`/api`)"

Con esto, /api, /api/usuarios, /api/productos/123, etc., todas coinciden. Pero te aviso de una cosa: PathPrefix puede ser peligroso si no lo combinas bien.

Imagina que tienes dos servicios:

- "traefik.http.routers.api.rule=PathPrefix(`/api`)"
- "traefik.http.routers.frontend.rule=PathPrefix(`/`)"

¿Qué crees que pasa con una petición a /api/usuarios? Pues que ambos routers coinciden. Aquí es donde entra en juego la prioridad, que veremos más adelante.

Un error bastante típico: usar PathPrefix(/) como catch-all sin asignarle una prioridad baja. Puede tragarse todo el tráfico y dejar tus otros routers sin funcionar. Ya te digo que esto te puede ahorrar más de un disgusto.

Method

Coincide con el método HTTP de la petición. Esto es una pasada para APIs REST, donde quieres que ciertas operaciones vayan a servicios distintos según si son lecturas o escrituras,

# Solo peticiones GET
- "traefik.http.routers.api-lectura.rule=Host(`api.dominio.com`) && Method(`GET`)"

# Peticiones de escritura
- "traefik.http.routers.api-escritura.rule=Host(`api.dominio.com`) && Method(`POST`, `PUT`, `PATCH`, `DELETE`)"

Puedes pasar varios métodos separados por comas. El matcher devuelve true si el método de la petición coincide con cualquiera de los listados.

Un caso práctico de verdad: separar el tráfico de lectura y escritura para escalar de forma diferente,

services:
  api-lectura:
    image: miapp:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.lectura.rule=Host(`api.dominio.com`) && Method(`GET`, `HEAD`, `OPTIONS`)"
      - "traefik.http.routers.lectura.entrypoints=websecure"
      - "traefik.http.routers.lectura.tls=true"
      - "traefik.http.routers.lectura.service=api-lectura"
      - "traefik.http.services.api-lectura.loadbalancer.server.port=3000"

  api-escritura:
    image: miapp:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.escritura.rule=Host(`api.dominio.com`) && Method(`POST`, `PUT`, `PATCH`, `DELETE`)"
      - "traefik.http.routers.escritura.entrypoints=websecure"
      - "traefik.http.routers.escritura.tls=true"
      - "traefik.http.routers.escritura.service=api-escritura"
      - "traefik.http.services.api-escritura.loadbalancer.server.port=3000"

Así puedes tener 10 réplicas del servicio de lectura y solo 2 del de escritura, ajustando los recursos a la demanda real. Dale caña.

Headers

Coincide con el valor de una cabecera HTTP. Esto es útil para cosas como tráfico interno entre servicios, autenticación básica por cabecera, o direccionamiento basado en versiones,

# Coincide con una cabecera específica
- "traefik.http.routers.mi-app.rule=Header(`X-Service`, `internal`)"

# Múltiples cabeceras (se evalúan con AND)
- "traefik.http.routers.mi-app.rule=Header(`X-Service`, `internal`) && Header(`X-Version`, `2`)"

¿Te suena a cómo funcionan los tests de integración? Efectivamente, puedes usar Headers para simular entornos. Por ejemplo, si usas una cabecera X-Env para enviar tráfico a un servicio canary o de pruebas:

- "traefik.http.routers.mi-app-canary.rule=Host(`app.dominio.com`) && Header(`X-Env`, `canary`)"
- "traefik.http.routers.mi-app-canary.service=servicio-canary"

Así, cualquier petición que incluya la cabecera X-Env: canary irá al servicio canary. El resto del tráfico va al servicio estable. Esto es una forma muy limpia de hacer despliegues progresivos sin tocar nada más que la petición.

Y ojo, que las cabeceras son clave-valor: el matcher Header compara tanto el nombre como el valor exactamente. Si el valor puede variar, mejor usa HeaderRegexp, que veremos ahora.

HeaderRegexp

Coincide con una cabecera usando expresión regular. Esto te permite hacer coincidencias parciales,

# Versiones v2.x
- "traefik.http.routers.mi-app.rule=HeaderRegexp(`X-Version`, `^v2\\.[0-9]+$`)"

# Cualquier valor que contenga "beta"
- "traefik.http.routers.mi-app.rule=HeaderRegexp(`X-Channel`, `.*beta.*`)"

# Authorization con tipo Bearer
- "traefik.http.routers.mi-app.rule=HeaderRegexp(`Authorization`, `^Bearer .+$`)"

Fíjate en que las expresiones regulares van sin delimitadores. No pongas /^v2\\.[0-9]+$/ porque no funciona. Es otro de esos pequeños detalles que te harán perder un rato hasta que caigas.

Un caso real: tienes una API que acepta varios formatos de contenido y quieres enrutar según el Accept header,

- "traefik.http.routers.api-json.rule=Host(`api.dominio.com`) && HeaderRegexp(`Accept`, `application/json.*`)"
- "traefik.http.routers.api-xml.rule=Host(`api.dominio.com`) && HeaderRegexp(`Accept`, `application/xml.*`)"

Así puedes tener servicios especializados para cada formato, o versiones diferentes de la misma API según lo que el cliente prefiera.

Query

Coincide con un parámetro de query string. Esto es genial para enrutamiento por parámetros URL,

# Coincide si el parámetro version=2 está presente
- "traefik.http.routers.mi-app.rule=Query(`version`, `2`)"

# Múltiples parámetros
- "traefik.http.routers.mi-app.rule=Query(`version`, `2`) && Query(`lang`, `es`)"

Puedes usar esto para muchas cosas. Por ejemplo, enrutar tráfico de pruebas A/B:

- "traefik.http.routers.mi-app-v2.rule=Host(`app.dominio.com`) && Query(`v`, `2`)"
- "traefik.http.routers.mi-app-v2.service=servicio-v2"

Cualquier petición a app.dominio.com?v=2 irá a la versión 2 de tu servicio. El resto sigue yendo a la versión estable.

¿Y si el parámetro puede venir con diferentes nombres?

- "traefik.http.routers.mi-app-beta.rule=Query(`beta`, `true`) || Query(`canary`, `true`) || Header(`X-Beta`, `true`)"

Así cubres varios frentes: parámetro en URL, cabecera HTTP… el que quiera probar tu beta, que le ponga cualquiera de esas señales.

También puedes combinarlo con PathPrefix para tener parámetros que solo activen el enrutamiento en rutas específicas:

- "traefik.http.routers.admin-debug.rule=Host(`admin.dominio.com`) && PathPrefix(`/debug`) && Query(`token`, `secreto-compartido`)"

Esto solo permite acceder a la ruta /debug si además pasas el token correcto en la URL. No es un sistema de autenticación, ojo, pero para herramientas internas con poca exposición puede valer.

ClientIP

Esta es una novedad de Traefik v3 que está muy bien. Coincide con la IP del cliente. Te evita tener que montar un middleware de IP whitelist, que era lo que tocaba hacer en v2,

# Una subred completa
- "traefik.http.routers.admin.rule=ClientIP(`192.168.1.0/24`)"

# Una IP concreta
- "traefik.http.routers.admin.rule=ClientIP(`10.0.0.42/32`)"

# Múltiples rangos
- "traefik.http.routers.admin.rule=ClientIP(`10.0.0.0/8`, `192.168.0.0/16`)"

Puedes pasar varios rangos separados por comas. El matcher devuelve true si la IP del cliente está en alguno de ellos.

¿Y con IPv6? También funciona. Especifica los rangos en notación IPv6 normal:

- "traefik.http.routers.admin.rule=ClientIP(`2001:db8::/32`)"

Un caso muy típico: tienes un panel de administración que solo debe ser accesible desde la oficina o desde una VPN. Con ClientIP lo resuelves en una línea:

services:
  admin-panel:
    image: admin:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.admin.rule=Host(`admin.dominio.com`) && ClientIP(`83.45.12.0/24`, `10.8.0.0/16`)"
      - "traefik.http.routers.admin.entrypoints=websecure"
      - "traefik.http.routers.admin.tls=true"
      - "traefik.http.routers.admin.middlewares=authelia@docker"

Aquí combinamos Host, ClientIP y un middleware de autenticación. Doble capa de seguridad: primero la IP, luego el login.

Atención con los proxies inversos: si tienes Traefik detrás de otro proxy (Cloudflare, Nginx, un balanceador de carga), la IP que ve Traefik no será la del cliente real, sino la del proxy. En ese caso necesitas configurar forwardedHeaders.trustedIPs para que Traefik confíe en el proxy y extraiga la IP real de la cabecera X-Forwarded-For. Si no haces esto, ClientIP no servirá de nada porque verá siempre la misma IP. Lo veremos en un capítulo más avanzado.

Combinación de reglas

Esto es lo realmente potente de Traefik. Puedes combinar múltiples condiciones en una misma regla usando operadores lógicos. Esto te permite crear reglas muy específicas que se ajusten exactamente a lo que necesitas.

AND (&&)

Todas las condiciones deben cumplirse para que el router procese la petición,

# Solo peticiones a /api que vengan de midominio.com
- "traefik.http.routers.api.rule=Host(`midominio.com`) && PathPrefix(`/api`)"

# Peticiones POST a /api desde la red interna
- "traefik.http.routers.api-interna.rule=Host(`midominio.com`) && PathPrefix(`/api`) && Method(`POST`) && ClientIP(`10.0.0.0/8`)"

Este último ejemplo es muy interesante. Imagina que tienes un endpoint de ingesta de datos que solo acepta POST desde tu red interna. Con esa regla, solo las peticiones que cumplan las cuatro condiciones a la vez pasarán. Si falla una sola, Traefik busca otro router que coincida.

OR (||)

Cualquiera de las condiciones puede cumplirse,

# Acepta peticiones de cualquiera de los dos dominios
- "traefik.http.routers.mi-app.rule=Host(`app1.com`) || Host(`app2.com`)"

# Acceso desde la oficina o desde la VPN
- "traefik.http.routers.admin.rule=ClientIP(`83.45.12.0/24`) || ClientIP(`10.8.0.0/16`)"

El OR es ideal para cuando tienes varios dominios o rangos de IP que deben ir al mismo sitio.

Combinaciones complejas

Puedes mezclar AND y OR con paréntesis. Esto te da una flexibilidad enorme,

# Peticiones a api.com o app.com bajo /v2
- "traefik.http.routers.mi-app.rule=(Host(`api.com`) || Host(`app.com`)) && PathPrefix(`/v2`)"

# Peticiones GET a /api, o cualquier método a /webhook
- "traefik.http.routers.mixto.rule=(Host(`api.com`) && PathPrefix(`/api`) && Method(`GET`)) || (Host(`api.com`) && PathPrefix(`/webhook`))"

La clave está en los paréntesis: agrupan condiciones para que el orden de evaluación sea el que tú quieres. Sin paréntesis, el orden de precedencia de Traefik evalúa el AND antes que el OR, como en cualquier lenguaje de programación.

Otra combinación muy potente es ClientIP con Method para crear endpoints de administración que solo acepten escrituras desde la red interna. Así proteges tus datos incluso si alguien consigue las credenciales de la API desde fuera,

- "traefik.http.routers.admin-write.rule=Host(`api.com`) && PathPrefix(`/admin`) && Method(`POST`, `PUT`, `DELETE`) && ClientIP(`10.0.0.0/8`)"

Un endpoint así solo acepta operaciones de escritura en /admin si vienen de la red interna. Desde fuera, cualquier POST a /admin simplemente no encuentra router y devuelve 404. No hay posibilidad de error.

Un ejemplo más real: tienes una API donde versionas por URL y por cabecera,

- "traefik.http.routers.api-v2.rule=(Host(`api.com`) && PathPrefix(`/v2`)) || (Host(`api.com`) && HeaderRegexp(`X-Version`, `^2\\.[0-9]+$`))"

Esto permite que los clientes accedan a la v2 tanto por ruta (/v2/usuarios) como por cabecera (X-Version: 2.1). Los clientes antiguos que aún no han actualizado la URL pueden ir migrando poco a poco.

Prioridad de routers

¿Qué pasa cuando dos routers pueden coincidir con la misma petición? Aquí es donde entra en juego la prioridad. Traefik usa la prioridad para decidir qué router gana. A mayor número, mayor prioridad.

labels:
  # Router genérico para cualquier ruta (prioridad baja)
  - "traefik.http.routers.frontend.rule=PathPrefix(`/`)"
  - "traefik.http.routers.frontend.priority=1"

  # Router específico para /api (prioridad alta)
  - "traefik.http.routers.api.rule=PathPrefix(`/api`)"
  - "traefik.http.routers.api.priority=10"

En este ejemplo, una petición a /api/usuarios coincide con ambos routers. Como el router api tiene prioridad 10 y frontend tiene prioridad 1, gana api.

Si no se especifica prioridad, Traefik asigna una automáticamente. Pero no te fíes de la automática cuando tengas rutas que puedan solaparse. Establecer prioridades explícitamente te puede ahorrar más de un disgusto.

¿Cómo calcula Traefik la prioridad automática? Usa la longitud de la regla: a más caracteres en la regla, más prioridad. Esto suele funcionar bien porque las reglas más específicas suelen ser más largas. Pero no siempre. Por ejemplo:

  • PathPrefix(/) tiene 12 caracteres → prioridad automática baja
  • PathPrefix(/api) && Method(POST) tiene muchos más → prioridad más alta

Pero si tienes dos reglas de longitud similar que pueden solaparse, la cosa se complica. Por eso te recomiendo ser explícito.

Un error muy común con prioridades

Imagina esta configuración:

services:
  frontend:
    image: frontend:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.frontend.rule=PathPrefix(`/`)"
      - "traefik.http.routers.frontend.priority=1"

  api:
    image: api:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=PathPrefix(`/api`)"

El router api no tiene prioridad explícita, así que Traefik le asigna una automática basada en la longitud de la regla. Como PathPrefix(/api) es más larga que PathPrefix(/), la prioridad automática de api será mayor que 1. En este caso funciona. Pero si luego añades un tercer router con PathPrefix(/api/v2) sin prioridad explícita, la prioridad automática será más alta que la de /api. ¿Estás seguro de que quieres eso? ¿O preferirías que /api/v2 tuviera prioridad 20 y /api prioridad 10?

Las prioridades explícitas eliminan la ambigüedad y hacen tu configuración predecible. Acostúmbrate a ponerlas.

Router por defecto (catch-all)

Puedes crear un router que capture todo el tráfico que no coincida con ninguna otra regla,

labels:
  - "traefik.http.routers.catchall.rule=HostRegexp(`{host:.+}`)"
  - "traefik.http.routers.catchall.priority=1"
  - "traefik.http.routers.catchall.service=noop@internal"

Usando el servicio noop@internal de Traefik, que no hace nada, o redirigiendo a una página de error personalizada. Esto te permite tener control sobre lo que pasa con el tráfico no esperado.

Otra opción es que el catch-all sirva una página personalizada:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.catchall.rule=HostRegexp(`{host:.+}`)"
  - "traefik.http.routers.catchall.priority=1"
  - "traefik.http.routers.catchall.service=error-page"
  - "traefik.http.routers.catchall.entrypoints=websecure"

  error-page:
    image: nginx:alpine
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.error-page.rule=HostRegexp(`{host:.+}`) && PathPrefix(`/error`)"

El catch-all captura todo lo que no tenga un router más específico y lo envía a un Nginx que muestra un «404 personalizado». Elegante, ¿verdad?

Otra alternativa es usar un middleware redirectRegex para redirigir todo el tráfico no coincidente a tu dominio principal. Pero eso lo veremos en el capítulo de middlewares.

Router sin servicio especificado

Si no especificas un servicio en el router, Traefik intentará inferirlo automáticamente a partir del nombre del contenedor. Por ejemplo, si defines,

- "traefik.http.routers.mi-app.rule=Host(`app.dominio.com`)"

Traefik asumirá que el servicio se llama mi-app, el mismo nombre que el router. Si el servicio tiene otro nombre, debes especificarlo explícitamente,

- "traefik.http.routers.mi-app.rule=Host(`app.dominio.com`)"
- "traefik.http.routers.mi-app.service=mi-servicio"
- "traefik.http.services.mi-servicio.loadbalancer.server.port=3000"

Esta inferencia automática es cómoda, pero puede darte sorpresas si tienes servicios con nombres muy largos o con caracteres especiales. Traefik normaliza los nombres quitando guiones y caracteres extraños, así que el nombre inferido podría no ser el que esperas. Mi recomendación: especifica siempre el servicio explícitamente. Es una línea más, pero te evitas dolores de cabeza.

TLS en routers

Para que un router use HTTPS, debes indicarlo explícitamente. Esto es algo que se me olvida a veces y luego me pregunto por qué no funciona,

labels:
  - "traefik.http.routers.mi-app.rule=Host(`app.dominio.com`)"
  - "traefik.http.routers.mi-app.entrypoints=websecure"
  - "traefik.http.routers.mi-app.tls=true"
  - "traefik.http.routers.mi-app.tls.certresolver=letsencrypt"

Si no especificas tls: true, el router usará HTTP aunque esté en el entrypoint websecure. Esto es una fuente de errores muy común. Te pongo el escenario típico: configuras todo, pones el entrypoint websecure, lanzas el contenedor, abres el navegador y… «Conexión no segura» o directamente no carga. Y después de veinte minutos maldiciendo, te das cuenta de que te faltaba el tls: true.

Hay una excepción: si tienes configurado tls.enforce: true en el EntryPoint, Traefik forzará TLS para todos los routers que usen ese EntryPoint. Pero no es lo habitual.

También puedes configurar routers específicamente HTTP para redirigir a HTTPS:

labels:
  - "traefik.http.routers.mi-app-http.rule=Host(`app.dominio.com`)"
  - "traefik.http.routers.mi-app-http.entrypoints=web"
  - "traefik.http.routers.mi-app-http.middlewares=redirect-https"

  - "traefik.http.routers.mi-app-https.rule=Host(`app.dominio.com`)"
  - "traefik.http.routers.mi-app-https.entrypoints=websecure"
  - "traefik.http.routers.mi-app-https.tls=true"
  - "traefik.http.routers.mi-app-https.tls.certresolver=letsencrypt"

El router HTTP usa un middleware que redirige a HTTPS. Pero esto ya es cosa del siguiente capítulo.

Novedades en v3 para routers

Traefik v3 introduce un par de novedades interesantes en los routers,

  • ClientIP matcher: puedes enrutar por IP de cliente sin necesidad de un middleware. Esto es nuevo y está muy bien para servicios internos.
  • Sintaxis extendida de reglas: más expresividad en las combinaciones, con mejor soporte para anidación de paréntesis y operadores.
  • Backward compatibility con v2: tus reglas v2 siguen funcionando, pero puedes migrar gradualmente a la nueva sintaxis.

Además, en v3 se ha mejorado el rendimiento del motor de enrutamiento. Las reglas se evalúan de forma más eficiente, lo que se nota cuando tienes decenas o cientos de routers definidos.

Errores comunes y cómo evitarlos

A lo largo de los años usando Traefik, he visto los mismos errores una y otra vez. Te los resumo para que no caigas en ellos:

1. Olvidar el tls: true: ya lo he mencionado, pero merece repetirse. Sin tls: true, no hay HTTPS, aunque el EntryPoint se llame websecure.

2. Prioridades automáticas que no hacen lo que esperas: cuando tienes PathPrefix(/) y PathPrefix(/api), la prioridad automática suele funcionar. Pero añade PathPrefix(/api/v2) y la cosa se complica. Pon prioridades explícitas.

3. Reglas sin entrypoint: si no especificas un entrypoint, el router escuchará en todos los entrypoints definidos globalmente. Esto puede hacer que un servicio pensado solo para HTTPS responda también en HTTP. Especifica siempre el entrypoint.

4. Backticks olvidados: las reglas usan backticks (`) para los valores, no comillas. Host("midominio.com") no funciona. Tiene que ser Host(\midominio.com`)`.

5. Comillas en las labels de Docker Compose: en un archivo YAML, los valores con backticks pueden liarte. Si tienes problemas, prueba a usar comillas dobles alrededor de todo el valor: - "traefik.http.routers.mi-app.rule=Host(\app.com`)»`. El YAML es sensible a los caracteres especiales.

6. Nombres de router duplicados: si dos contenedores definen un router con el mismo nombre, Traefik se quejará. Asegúrate de que cada router tenga un nombre único en todo el stack. Puedes usar el nombre del servicio como prefijo: nextcloud-router, bitwarden-router.

7. ClientIP detrás de un proxy: si usas ClientIP y tienes Traefik detrás de Cloudflare, Nginx o similar, la IP que ves no es la real. Configura forwardedHeaders.trustedIPs o el matcher no te servirá de nada.

Buenas prácticas

Te dejo algunas recomendaciones basadas en mi experiencia,

  1. Usa nombres descriptivos para los routers: nextcloud, bitwarden, api-v2. Evita nombres genéricos como app1, router1. Cuando tienes 20 servicios, te agradecerás haber puesto nombres claros.
  2. Establece prioridades explícitas cuando tengas rutas que puedan solaparse. No confíes en las prioridades automáticas para casos ambiguos.
  3. No uses PathPrefix(/) como catch-all sin control, puede tragarse tráfico que debería ir a otros servicios. Si lo usas, ponle una prioridad baja explícita.
  4. Separa routers públicos y privados usando reglas ClientIP. No expongas paneles de administración a Internet aunque tengan autenticación. La IP es una capa extra que no está de más.
  5. Especifica siempre el entrypoint — no asumas que el router escuchará en todos. Te ahorrarás sorpresas.
  6. Prueba las reglas una a una cuando estés aprendiendo. Empieza con un Host simple, añade PathPrefix, luego combina con Method, y así sucesivamente. Si algo falla, sabes exactamente qué añadiste.
  7. Documenta las reglas complejas. Un comentario en el docker-compose.yml explicando qué hace una combinación enrevesada te lo agradecerás tú mismo dentro de seis meses. Algo como:
# Router para la API v2: acepta /v2/ruta o cabecera X-Version: 2.x
# Prioridad 20 para que gane a /v1 y a /

Conclusión

Los routers son el componente más importante de Traefik. Dominar las reglas de enrutamiento te permite controlar exactamente cómo y cuándo cada servicio recibe tráfico. Con los matchers que has visto aquí puedes cubrir prácticamente cualquier escenario de enrutamiento que se te ocurra.

Desde el simple Host que separa servicios por dominio, hasta combinaciones complejas de Method, Headers, Query y ClientIP, pasando por la gestión de prioridades y los catch-all. Al final, no se trata más que de decirle a Traefik: «esto va aquí, esto va allá». Y hacerlo bien desde el principio te ahorrará quebraderos de cabeza cuando tu infraestructura crezca.

En el próximo capítulo veremos los Middlewares, la capa que te permite transformar peticiones antes de que lleguen a tus servicios. Esto es lo que realmente diferencia a Traefik de otros proxies inversos. Autenticación, rate limiting, redirecciones, cabeceras personalizadas… Todo eso y mucho más. Te espero allí.

Más información

Deja una respuesta