- LICENSE: texto integro de la GPL-3.0 - docs/publicacion-anonima.md: politica de publicacion sin datos reales (nombres, rutas con usuario, dominios, IPs, credenciales) y checklist - tools/verificar-anonimato.sh: chequeo automatico de PII y archivos sensibles trackeados, con salida por codigo (0 limpio / 1 con hallazgos) - README: seccion 'Publicacion anonima' y licencia GPL-3.0 - .env.global: ruta base sin nombre de usuario - README ejemplo: nombres de proyecto genericos (miapp) en lugar de reales
14 KiB
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
~/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):
TrafficServerLocal/
├── README.md
├── LICENSE # GPL-3.0
├── .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
│ └── publicacion-anonima.md # Política de publicación anonimizada
├── tools/
│ └── verificar-anonimato.sh # Chequea PII antes de publicar
└── 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: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
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
mkdir -p ~/srv/core/data
touch ~/srv/core/data/acme.json
chmod 600 ~/srv/core/data/acme.json
acme.jsoncontiene la clave privada de la cuenta de Let's Encrypt. Nunca se versiona (ya está en.gitignore).
3.3 Levantar el proxy
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) |
Puertos expuestos por el proxy
ports:
- "80:80" # entrypoint: web → HTTP
- "443:443" # entrypoint: websecure → HTTPS
5. Crear un proyecto base
cd ~/srv
./init_project.sh api-ventas api.midominio.com 80
Genera:
~/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
# 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
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.
A) Dominio público + Let's Encrypt
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
-
Generar el certificado:
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" -
Declararlo en
core/data/dynamic_tls.yml:tls: certificates: - certFile: /etc/traefik/certs/miapp.local.crt keyFile: /etc/traefik/certs/miapp.local.key -
Etiquetar el proyecto sin certresolver:
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" -
Reiniciar el proxy y resolver el nombre en el cliente:
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.
C) Sin redirección (solo HTTP)
Para entornos internos donde no interese HTTPS:
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
# 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: proyecto Laravel + PostgreSQL
Ejemplo anonimizado con nombres genéricos. Los proyectos reales se publican siempre sin nombres, dominios ni credenciales del servidor real: ver
docs/publicacion-anonima.md.
services:
web:
build:
context: ./app
dockerfile: Dockerfile
container_name: miapp-web
restart: unless-stopped
env_file: [ .env ]
volumes:
- ./app/src:/var/www/miapp
- ./data/storage:/var/www/miapp/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.miapp-http.rule=Host(`miapp.local`)"
- "traefik.http.routers.miapp-http.entrypoints=web"
- "traefik.http.routers.miapp-http.middlewares=redirect-to-https"
- "traefik.http.routers.miapp-https.rule=Host(`miapp.local`)"
- "traefik.http.routers.miapp-https.entrypoints=websecure"
- "traefik.http.routers.miapp-https.tls=true"
- "traefik.http.routers.miapp-https.service=miapp-svc"
- "traefik.http.services.miapp-svc.loadbalancer.server.port=80"
pg:
image: postgres:14
container_name: miapp-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.envdel proyecto. Las contraseñas escritas directamente en eldocker-compose.ymlterminan 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:
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.gitignorede este repo lo cubre. - Los
.envpor proyecto se crean conchmod 600. docker.sockse monta solo lectura (ro) en Traefik.- El dashboard está habilitado. En producción:
- agregá
basicAuth/forwardAutha las etiquetas del dashboard, o - deshabilitalo con
api.dashboard: false.
- agregá
- Traefik corre con
no-new-privileges:true. acme.jsondebe quedar en600o el proxy no managea los certificados.- Restringí los puertos publicados de las bases de datos (
ports:) a127.0.0.1si solo los necesitás desde el host:ports: ["127.0.0.1:5432:5432"]
11. Publicación anónima
Todo lo que se sube a un repositorio del laboratorio se publica anonimizado. Este repositorio es el ejemplo canónico: no contiene nombres de personas, dominios reales, direcciones IP, nombres de proyectos internos ni credenciales.
Reglas:
- Nombres de proyecto → genéricos (
miapp,ejemplo,demo). - Dominios →
*.local,example.com,midominio.com. Nunca los reales. - Rutas y usuarios →
~/srv,$USER. Nunca/home/<usuario-real>/. - Credenciales → placeholders
${DB_PASSWORD}y.env.example. - IPs → RFC 5737 (
192.0.2.0/24,198.51.100.0/24) o192.168.0.0/16. - Nada de
.env,acme.json, certificados, bases de datos ni respaldos. - Metadatos de commit: revisar
git logantes de publicar.
Procedimiento completo y checklist en
docs/publicacion-anonima.md.
Verificación automática:
./tools/verificar-anonimato.sh
12. Actualizar el proxy
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.
cd ~/srv/core && docker compose pull && docker compose up -d # actualizar proxy
Licencia
GPL-3.0 (GNU General Public License, versión 3) — texto completo en
LICENSE.
Software libre bajo los principios del movimiento de Software Libre: podés usar, estudiar, copiar, modificar y redistribuir este software. Si lo distribuís o lo desplegás para terceros, debés publicar también las fuentes de tus modificaciones bajo la misma licencia.
Autoría: Laboratorio de Investigación en Software Libre.