TrafficServerLocal/README.md
merios 33c368936e feat: infraestructura base de Traefik + generador de proyectos
- 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
2026-10-15 11:34:39 -03:00

405 lines
12 KiB
Markdown

# TrafficServerLocal
Infraestructura base para alojar múltiples proyectos Docker detrás de un único
proxy inverso **Traefik v3**, con soporte para **HTTP y HTTPS**:
- **Dominios públicos** → certificados de **Let's Encrypt** (automático).
- **Dominios locales `.local`** → certificados **auto-firmados** montados desde
el filesystem.
Este repositorio contiene **solo la infraestructura reutilizable**. El código,
las bases de datos y los datos persistentes de cada proyecto viven en
`~/srv/projects/<proyecto>/` y se publican en sus propios repositorios.
---
## 1. Estructura
```text
~/srv/
├── core/ # Reverse proxy Traefik
│ ├── docker-compose.yml
│ └── data/
│ ├── traefik.yml # Configuración estática
│ ├── dynamic_tls.yml # Certificados estáticos (.local)
│ ├── acme.json # Clave ACME (NO versionado)
│ └── certs/ # .crt / .key locales (NO versionados)
├── projects/ # Un directorio por proyecto
│ └── <proyecto>/
│ ├── app/ # Código fuente / Dockerfile
│ ├── data/{db,storage}/ # Persistencia
│ ├── .env # Secretos (chmod 600)
│ └── docker-compose.yml # Etiquetas de Traefik
└── backups/ # Respaldos (fuera de Git)
```
En este repositorio (lo publicable):
```text
TrafficServerLocal/
├── README.md
├── .gitignore
├── .env.global
├── init_project.sh # Generador de proyectos base
├── docs/
│ ├── arquitectura.md # Guía de instalación y gestión de URLs
│ └── http-https.md # Referencia de etiquetas HTTP/HTTPS y TLS
└── core/
├── docker-compose.yml
└── data/
├── traefik.yml
├── dynamic_tls.yml
└── certs/README.md # Cómo generar certificados .local
```
---
## 2. Requisitos
- **Docker Engine** y **Docker Compose** v2 (`docker compose`)
- El usuario debe pertenecer al grupo `docker`:
```bash
sudo usermod -aG docker "$USER"
newgrp docker # o cerrá y volvé a iniciar sesión
```
- Puertos **80/443** libres en el host
- Para HTTPS público: el dominio debe apuntar por DNS a la IP del servidor
---
## 3. Puesta en marcha
### 3.1 Estructura y red compartida
```bash
mkdir -p ~/srv/core ~/srv/projects
docker network create proxy_network # red externa compartida
```
`proxy_network` es **externa**: la crea una vez y la reutilizan el proxy y
todos los proyectos.
### 3.2 Almacenamiento de certificados ACME
```bash
mkdir -p ~/srv/core/data
touch ~/srv/core/data/acme.json
chmod 600 ~/srv/core/data/acme.json
```
> `acme.json` contiene la clave privada de la cuenta de Let's Encrypt.
> **Nunca se versiona** (ya está en `.gitignore`).
### 3.3 Levantar el proxy
```bash
cd ~/srv/core
docker compose up -d
docker ps | grep core-proxy
```
Traefik queda escuchando en `:80` (HTTP) y `:443` (HTTPS).
---
## 4. Cómo funciona el proxy
| Concepto | Detalle |
|---|---|
| **Provider Docker** | Traefik lee `/var/run/docker.sock` (solo lectura) y detecta contenedores con `traefik.enable=true` |
| `exposedByDefault: false` | Solo se publican los contenedores etiquetados |
| **Provider File** | Carga certificados estáticos desde `dynamic_tls.yml` con `watch: true` |
| `proxy_network` | Red externa por la que Traefik alcanza a los servicios |
| Red `default` | Red bridge **aislada** del proxy para bases de datos, colas, etc. |
| `no-new-privileges` | Endurecimiento básico del contenedor del proxy |
| Dashboard | Habilitado en `api.dashboard` (ver [Seguridad](#10-seguridad)) |
### Puertos expuestos por el proxy
```yaml
ports:
- "80:80" # entrypoint: web → HTTP
- "443:443" # entrypoint: websecure → HTTPS
```
---
## 5. Crear un proyecto base
```bash
cd ~/srv
./init_project.sh api-ventas api.midominio.com 80
```
Genera:
```text
~/srv/projects/api-ventas/
├── app/ # Código fuente, repo o Dockerfile
├── data/
│ ├── db/ # Persistencia de base de datos
│ └── storage/ # Archivos del usuario
├── .env # Variables (chmod 600)
└── docker-compose.yml # Conectado a proxy_network
```
El directorio del proyecto queda con permisos `700`.
### Pasos siguientes
```bash
# 1. Colocar el código
git clone https://github.com/tu-usuario/api-ventas.git \
~/srv/projects/api-ventas/app/
# o bien:
cp -r /ruta/a/tus/archivos/* ~/srv/projects/api-ventas/app/
# 2. Ajustar docker-compose.yml
# - Opción A: descomentar build: { context: ./app, dockerfile: Dockerfile }
# - Opción B: cambiar image: api-ventas:latest por tu imagen
# - Servicios extra (Postgres, Redis) → red `default` (aislados del proxy)
# 3. Levantar
cd ~/srv/projects/api-ventas
docker compose up -d
```
### Variables generadas en `.env`
```ini
APP_NAME=api-ventas
APP_DOMAIN=api.midominio.com
PORT=80
```
Agregá ahí tus credenciales. **Nunca** subas ese archivo a Git.
---
## 6. HTTP / HTTPS
Detalle completo en **[`docs/http-https.md`](docs/http-https.md)**.
### A) Dominio público + Let's Encrypt
```yaml
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy_network"
# Middleware de redirección HTTP → HTTPS
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
# Router HTTP (redirige)
- "traefik.http.routers.miapp-http.rule=Host(`api.midominio.com`)"
- "traefik.http.routers.miapp-http.entrypoints=web"
- "traefik.http.routers.miapp-http.middlewares=redirect-to-https"
# Router HTTPS (emite el certificado)
- "traefik.http.routers.miapp-https.rule=Host(`api.midominio.com`)"
- "traefik.http.routers.miapp-https.entrypoints=websecure"
- "traefik.http.routers.miapp-https.tls=true"
- "traefik.http.routers.miapp-https.tls.certresolver=letsencrypt"
# Servicio interno
- "traefik.http.services.miapp.loadbalancer.server.port=80"
```
### B) Dominio local `.local` + certificado auto-firmado
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"
```
2. Declararlo en `core/data/dynamic_tls.yml`:
```yaml
tls:
certificates:
- certFile: /etc/traefik/certs/miapp.local.crt
keyFile: /etc/traefik/certs/miapp.local.key
```
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"
```
4. Reiniciar el proxy y resolver el nombre en el cliente:
```bash
cd ~/srv/core && docker compose restart traefik
echo "192.168.1.50 miapp.local" | sudo tee -a /etc/hosts
```
Instrucciones completas (SAN múltiple, instalación del CA en cada SO) en
**[`core/data/certs/README.md`](core/data/certs/README.md)**.
### C) Sin redirección (solo HTTP)
Para entornos internos donde no interese HTTPS:
```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"
```
---
## 7. Comandos útiles
```bash
# Estado y logs del proxy
docker ps | grep core-proxy
docker logs -f core-proxy
# Recargar tras editar dynamic_tls.yml
cd ~/srv/core && docker compose restart traefik
# Proyecto: ciclo de vida
cd ~/srv/projects/miapp
docker compose up -d # levantar
docker compose up -d --build # reconstruir imagen
docker compose restart # reiniciar
docker compose down # detener (conserva datos)
docker compose logs -f web
# Entrar al contenedor
docker exec -it miapp-web bash
# Ver redes de un contenedor
docker network inspect proxy_network --format '{{.Containers}}'
```
---
## 8. Ejemplo real: proyecto Laravel + PostgreSQL
```yaml
services:
web:
build:
context: ./app
dockerfile: Dockerfile
container_name: optica-web
restart: unless-stopped
env_file: [ .env ]
volumes:
- ./app/src:/var/www/optica
- ./data/storage:/var/www/optica/storage
networks: [ default, proxy_network ]
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy_network"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.optica-http.rule=Host(`optica.local`)"
- "traefik.http.routers.optica-http.entrypoints=web"
- "traefik.http.routers.optica-http.middlewares=redirect-to-https"
- "traefik.http.routers.optica-https.rule=Host(`optica.local`)"
- "traefik.http.routers.optica-https.entrypoints=websecure"
- "traefik.http.routers.optica-https.tls=true"
- "traefik.http.routers.optica-https.service=optica-svc"
- "traefik.http.services.optica-svc.loadbalancer.server.port=80"
pg:
image: postgres:14
container_name: optica-pg
networks: [ default ] # ← aislada: no accesible desde el proxy
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes: [ ./data/db:/var/lib/postgresql ]
networks:
default:
driver: bridge
proxy_network:
external: true
```
> ⚠️ Mové las credenciales de `environment:` al `.env` del proyecto.
> Las contraseñas escritas directamente en el `docker-compose.yml` terminan en
> el historial de Git.
---
## 9. Solución de problemas
| Síntoma | Causa probable | Solución |
|---|---|---|
| `404 Not Found` | Contenedor sin `traefik.enable=true` o fuera de `proxy_network` | `docker network inspect proxy_network` |
| El router no aparece en el dashboard | Servicio sin `traefik.enable` o red incorrecta | Verificar etiquetas y redes del compose |
| `502 Bad Gateway` | El servicio interno no escucha en el puerto declarado | `docker exec -it <cont> sh -c 'netstat -tlnp'` |
| Certificado no se emite (Let's Encrypt) | DNS no apunta al server, o 80/443 bloqueados | `dig +short dominio`, revisar firewall |
| `too many certificates` / `rate limit` | Se emitted de más en staging/producción | Revisar `acme.json` y esperar la ventana de rate limit |
| `port is already allocated` | Otro servicio ocupa 80/443 | `docker ps --filter publish=80` |
| `permission denied` en Docker socket | Usuario fuera del grupo `docker` | `sudo usermod -aG docker $USER` y re-loguear |
| El `.crt` local no se aplica | Ruta incorrecta en `dynamic_tls.yml` | Usar la ruta **dentro del contenedor** (`/etc/traefik/certs/...`) y reiniciar |
| `no such file or directory: /acme.json` | Falta el archivo o permisos | `touch acme.json && chmod 600 acme.json` |
| Cambios en `traefik.yml` sin efecto | El archivo está montado `:ro` | `docker compose restart traefik` |
Diagnóstico rápido:
```bash
docker logs -f core-proxy | grep -iE "error|warn"
curl -sI http://miapp.local # debería devolver 301 → https
curl -sk https://miapp.local -I # -k ignora el certificado auto-firmado
```
---
## 10. Seguridad
- **Nunca subas**: `.env`, `acme.json`, `*.key`, `*.crt`, contraseñas de bases de
datos, dumps SQL ni respaldos. El `.gitignore` de este repo lo cubre.
- Los `.env` por proyecto se crean con `chmod 600`.
- `docker.sock` se monta **solo lectura** (`ro`) en Traefik.
- El dashboard está habilitado. En producción:
- agregá `basicAuth` / `forwardAuth` a las etiquetas del dashboard, o
- deshabilitalo con `api.dashboard: false`.
- Traefik corre con `no-new-privileges:true`.
- `acme.json` debe quedar en `600` o el proxy no managea los certificados.
- Restringí los puertos publicados de las bases de datos (`ports:`) a `127.0.0.1`
si solo los necesitás desde el host:
```yaml
ports: ["127.0.0.1:5432:5432"]
```
---
## 11. Publicación
Este repositorio es la **capa de infraestructura**. Cada proyecto se mantiene en
su propio repositorio y se despliega en `~/srv/projects/<proyecto>/` siguiendo
los pasos de este README.
```bash
cd ~/srv/core && docker compose pull && docker compose up -d # actualizar proxy
```
---
## Licencia
MIT — ver el repositorio para detalles.