Agrega el credito "Laboratorio de investigacion en Software Libre Gugler - FCyT" en el encabezado y la seccion Licencia del README, y como pie en ambos documentos de docs/. Se documenta la exencion institucional en la politica de publicacion anonima: el nombre del laboratorio y git.gugler.com.ar son publicos por decision propia y no se anonimizan. El verificador recibe una lista blanca que separa autores institucionales de los que hay que revisar, y excluye el dominio del laboratorio del filtro de emails.
16 KiB
TrafficServerLocal
Autoría: Laboratorio de investigacion en Software Libre Gugler - FCyT.
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 investigacion en Software Libre Gugler - FCyT.
Racional de la licencia
GPL-3.0 es una decisión deliberada, no una escolha por omisión. No debe cambiarse sin revisar este apartado.
El criterio del laboratorio es proteger la libertad sin atar a nadie:
- No impone condiciones de uso. Quien instala el proxy, lo corre en su servidor o lo modifica para uso interno no debe nada a nadie.
- La obligación aparece solo al distribuir. Si entregás el código a terceros —incluido un servicio comercial—, debés publicar los cambios bajo la misma licencia. Nadie puede cerrar el proyecto sobre trabajo derivado de acá.
- Se descartó la AGPL-3.0 (
AGPL-3.0-or-later) porque su cláusula 13 extiende ese deber también al uso en red: obligaría a publicar los cambios incluso en un servicio interno no distribuido. El laboratorio lo considera más restrictivo de lo necesario y contrario al objetivo de no imponer condiciones más allá de la distribución.
En resumen: copyleft estricto en la distribución, libertad total en el uso.
Si en el futuro se evaluara adoptar una licencia más permisiva (MIT, BSD, Apache-2.0), debe resolverse en el mismo sentido: consultando a quienes participan y revisando cómo afecta a la distribución de versiones anteriores.