# HTTP / HTTPS en Traefik — referencia de etiquetas Todos los ejemplos son etiquetas de Docker Compose aplicables al servicio `web` del proyecto. Traefik las lee por el socket de Docker y construye la cadena **Router → Middleware → Service**. --- ## 1. Anatomía de una etiqueta ```text traefik.http...= │ │ │ │ │ └─ propiedad (rule, entrypoints, tls, port…) │ └─────────── tipo (routers, services, middlewares) └──────────────────── ámbito/grupo (ej. "miapp-https") ``` Componentes usados: | Componente | Función | |---|---| | `routers` | Decide **qué** petición llega (Host, Path) y **a qué entrypoint** responde | | `middlewares` | Transformaciones (redirecciones, auth, headers) | | `services` | Traduce a **con qué puerto interno** del contenedor | | `tls` | Habilita TLS y define de dónde sale el certificado | --- ## 2. Exponer un proyecto (mínimo funcional) ```yaml networks: default: driver: bridge proxy_network: external: true services: web: build: { context: ./app, dockerfile: Dockerfile } container_name: miapp-web restart: unless-stopped networks: [ default, proxy_network ] # ← ambas redes labels: - "traefik.enable=true" - "traefik.docker.network=proxy_network" - "traefik.http.routers.miapp.rule=Host(`miapp.local`)" - "traefik.http.routers.miapp.entrypoints=websecure" - "traefik.http.services.miapp.loadbalancer.server.port=80" ``` Notas: - `traefik.enable=true` es **obligatorio** (`exposedByDefault: false`). - `traefik.docker.network=proxy_network` fuerza a leer las etiquetas desde esa red cuando el contenedor está en varias. - El puerto del service es el **puerto interno**, no el publicado. --- ## 3. Modo A — Dominio público con Let's Encrypt ```yaml labels: - "traefik.enable=true" - "traefik.docker.network=proxy_network" # 1. Middleware de redirección - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true" # 2. Router HTTP (:80) → redirige - "traefik.http.routers.miapp-http.rule=Host(`app.ejemplo.com`)" - "traefik.http.routers.miapp-http.entrypoints=web" - "traefik.http.routers.miapp-http.middlewares=redirect-to-https" # 3. Router HTTPS (:443) → sirve el contenido - "traefik.http.routers.miapp-https.rule=Host(`app.ejemplo.com`)" - "traefik.http.routers.miapp-https.entrypoints=websecure" - "traefik.http.routers.miapp-https.tls=true" - "traefik.http.routers.miapp-https.tls.certresolver=letsencrypt" - "traefik.http.routers.miapp-https.service=miapp-svc" # 4. Servicio interno - "traefik.http.services.miapp-svc.loadbalancer.server.port=80" ``` Requisitos: 1. DNS `A/AAAA` apuntando a la IP del servidor. 2. Puertos **80 y 443** abiertos (el challenge HTTP-01 de Let's Encrypt usa el 80). 3. `~/srv/core/data/acme.json` existente y con permisos `600`. > Traefik emite el certificado la primera vez que recibe tráfico HTTPS por ese > Host. Para forzarlo: `docker logs -f core-proxy` y hacer un `curl` al dominio. ### Mejorar el router HTTP con challenge sin romper la redirección ```yaml - "traefik.http.routers.miapp-http.tls=true" - "traefik.http.routers.miapp-http.tls.certresolver=letsencrypt" - "traefik.http.routers.miapp-http.middlewares=redirect-to-https" ``` Esto permite que Let's Encrypt valide por el puerto 80 aunque todo el tráfico se redirija a HTTPS. --- ## 4. Modo B — Dominio local `.local` con certificado auto-firmado ### 4.1 Generar el certificado ```bash cd ~/srv/core/data/certs openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout miapp.local.key -out miapp.local.crt \ -subj "/CN=miapp.local" \ -addext "subjectAltName=DNS:miapp.local" chmod 600 miapp.local.key ``` Certificado multi-dominio: ```bash openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout intranet.local.key -out intranet.local.crt \ -subj "/CN=intranet.local" \ -addext "subjectAltName=DNS:intranet.local,DNS:app.intranet.local,DNS:api.intranet.local" ``` ### 4.2 Declararlo en el provider `file` `~/srv/core/data/dynamic_tls.yml` (montado en `/etc/traefik/dynamic_tls.yml`): ```yaml tls: certificates: - certFile: /etc/traefik/certs/miapp.local.crt keyFile: /etc/traefik/certs/miapp.local.key ``` > La ruta es **la de dentro del contenedor**, porque el directorio se monta en > `/etc/traefik/certs`. ### 4.3 Etiquetar el proyecto (sin certresolver) ```yaml labels: - "traefik.enable=true" - "traefik.docker.network=proxy_network" - "traefik.http.routers.miapp.rule=Host(`miapp.local`)" - "traefik.http.routers.miapp.entrypoints=websecure" - "traefik.http.routers.miapp.tls=true" - "traefik.http.services.miapp.loadbalancer.server.port=80" ``` Recargar: ```bash cd ~/srv/core && docker compose restart traefik ``` ### 4.4 Resolver el nombre en el cliente ```bash echo "192.168.1.50 miapp.local" | sudo tee -a /etc/hosts ``` --- ## 5. Modo C — Solo HTTP (entornos internos) ```yaml labels: - "traefik.enable=true" - "traefik.docker.network=proxy_network" - "traefik.http.routers.miapp.rule=Host(`miapp.local`)" - "traefik.http.routers.miapp.entrypoints=web" - "traefik.http.services.miapp.loadbalancer.server.port=80" ``` --- ## 6. Varios dominios en un mismo proyecto Dos approaches: **a) Múltiples reglas en un router** (OR lógico): ```yaml - "traefik.http.routers.miapp.rule=Host(`app.local`) || Host(`www.app.local`)" ``` **b) Un router por dominio**, apuntando al mismo service: ```yaml - "traefik.http.routers.www.rule=Host(`www.app.local`)" - "traefik.http.routers.www.entrypoints=websecure" - "traefik.http.routers.www.tls=true" - "traefik.http.routers.www.service=miapp-svc" ``` Ambos necesitan los certificados correspondientes en `dynamic_tls.yml` o en Let's Encrypt (agregando los dominios a la regla del router con certresolver). --- ## 7. Errores frecuentes | Error | Causa | Solución | |---|---|---| | `404` en todos los dominios | `traefik.enable` ausente | Agregar la etiqueta | | `404` solo en un dominio | Regla `Host()` mal escrita | Verificar comillas invertidas: `` Host(`dominio`) `` | | `502 Bad Gateway` | Puerto interno incorrecto | Confirmar `EXPOSE` del Dockerfile y el puerto real | | `too many redirects` | Router HTTP con `redirectscheme` sin HTTPS válido | Verificar que exista el router `websecure` | | `TLS handshake error` / `no certificate` | `dynamic_tls.yml` mal cargado o certificado expirado | Rutas internas correctas + `docker compose restart traefik` | | `cannot generate ACME certificate` | DNS mal, puertos cerrados o `acme.json` sin permisos | `dig`, revisar firewall y `chmod 600 acme.json` | | El router no cambia al editar el compose | Etiquetas cacheadas | `docker compose up -d --force-recreate` | Verificación: ```bash curl -sI http://miapp.local # 301 → https://miapp.local curl -sk https://miapp.local -I # respuesta del backend (-k ignora el cert) ``` --- ## 8. Cheatsheet de etiquetas | Etiqueta | Efecto | |---|---| | `traefik.enable=true` | Publica el contenedor | | `traefik.docker.network=` | Red desde la que leer etiquetas | | `...routers.X.rule=Host(\`d\`)` | Enruta por dominio | | `...routers.X.rule=PathPrefix(\`/api\`)` | Enruta por ruta | | `...routers.X.entrypoints=web\|websecure` | Puerto de escucha (80 / 443) | | `...routers.X.tls=true` | Habilita TLS en ese router | | `...routers.X.tls.certresolver=letsencrypt` | Emite/obtiene el certificado | | `...routers.X.middlewares=mw1,mw2` | Aplica middlewares (orden izquierda→derecha) | | `...routers.X.service=svc` | Fuerza el service (si hay más de uno) | | `...services.X.loadbalancer.server.port=N` | Puerto interno del contenedor | | `...middlewares.X.redirectscheme.scheme=https` | Redirige HTTP → HTTPS | | `...middlewares.X.redirectscheme.permanent=true` | 301 en vez de 302 | | `...middlewares.X.basicauth.users=file` | Protección básica | --- **Autoría:** Laboratorio de investigacion en Software Libre Gugler - FCyT. Distribuido bajo GPL-3.0 (ver [`LICENSE`](../LICENSE)).