- core/: reverse proxy Traefik v3 (HTTP 80 / HTTPS 443) - traefik.yml con provider Docker (exposedByDefault: false) y provider file - dynamic_tls.yml sanitizado con certificados .local comentados - certs/README.md con procedimiento de certificados auto-firmados - init_project.sh: generador de proyectos base (SRV_DIR configurable) - plantilla compose con redireccion HTTP->HTTPS + Let's Encrypt - bloque alternativo comentado para dominios .local - README.md: documentacion integral (puesta en marcha, HTTP/HTTPS, troubleshooting y seguridad) - docs/arquitectura.md: guia de instalacion y gestion de URLs - docs/http-https.md: referencia de etiquetas Traefik, TLS y certresolver - .gitignore: excluye .env, acme.json, certificados, datos y respaldos
248 lines
8.0 KiB
Markdown
248 lines
8.0 KiB
Markdown
# 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.<scope>.<component>.<atributo>=<valor>
|
|
│ │ │
|
|
│ │ └─ 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>` | 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 |
|