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
This commit is contained in:
commit
33c368936e
13
.env.global
Normal file
13
.env.global
Normal file
@ -0,0 +1,13 @@
|
||||
# ==============================================================================
|
||||
# Configuración global del servidor (opcional)
|
||||
# ==============================================================================
|
||||
# Este archivo NO contiene secretos reales y puede versionarse.
|
||||
# Los secretos por proyecto viven en ~/srv/projects/<proyecto>/.env (chmod 600)
|
||||
# y NUNCA deben copiarse acá ni subirse al repositorio.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Ruta base donde se alojan los proyectos administrados por init_project.sh
|
||||
SRV_DIR=/home/merios/srv
|
||||
|
||||
# Dominio/contacto del administrador del servidor (opcional)
|
||||
# SERVER_ADMIN=marcos@ejemplo.com
|
||||
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# ==============================================================================
|
||||
# Archivos que NUNCA deben subirse al repositorio
|
||||
# ==============================================================================
|
||||
|
||||
# --- Secretos y variables de entorno -----------------------------------------
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.global
|
||||
*.pem
|
||||
*.p12
|
||||
*.pfx
|
||||
|
||||
# --- Certificados (públicos y privados) --------------------------------------
|
||||
*.crt
|
||||
*.key
|
||||
*.csr
|
||||
core/data/certs/*
|
||||
!core/data/certs/README.md
|
||||
|
||||
# --- Cuenta ACME de Let's Encrypt (contiene la clave privada) ----------------
|
||||
acme.json
|
||||
**/acme.json
|
||||
|
||||
# --- Datos persistentes, bases de datos y respaldos --------------------------
|
||||
/data/
|
||||
/projects/
|
||||
/backups/
|
||||
*.sql
|
||||
*.sql.gz
|
||||
*.dump
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.tgz
|
||||
|
||||
# --- Dependencias y artefactos de terceros -----------------------------------
|
||||
vendor/
|
||||
node_modules/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# --- Editores / IDE ----------------------------------------------------------
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*~
|
||||
404
README.md
Normal file
404
README.md
Normal file
@ -0,0 +1,404 @@
|
||||
# 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.
|
||||
110
core/data/certs/README.md
Normal file
110
core/data/certs/README.md
Normal file
@ -0,0 +1,110 @@
|
||||
# Certificados locales (`.local`)
|
||||
|
||||
Este directorio aloja los certificados **auto-firmados** que usan los dominios
|
||||
internos (`*.local`). Está montado en el contenedor de Traefik como:
|
||||
|
||||
```yaml
|
||||
- ./data/certs:/etc/traefik/certs:ro
|
||||
```
|
||||
|
||||
⚠️ **El contenido de esta carpeta NO se versiona** (ver `.gitignore`):
|
||||
las claves privadas no deben salir del servidor.
|
||||
|
||||
---
|
||||
|
||||
## 1. Generar un certificado auto-firmado
|
||||
|
||||
Desde la raíz del repositorio:
|
||||
|
||||
```bash
|
||||
cd 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"
|
||||
```
|
||||
|
||||
Archivos resultantes:
|
||||
|
||||
| Archivo | Descripción |
|
||||
|---|---|
|
||||
| `miapp.local.crt` | Certificado público (se puede distribuir) |
|
||||
| `miapp.local.key` | **Clave privada** — permisos `600`, nunca se comparte |
|
||||
|
||||
Si el certificado debe cubrir **varios** nombres en un solo `.crt`:
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
## 2. Declararlo en Traefik
|
||||
|
||||
Editar `../dynamic_tls.yml` y descomentar el bloque **con la ruta interna**:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
certificates:
|
||||
- certFile: /etc/traefik/certs/miapp.local.crt
|
||||
keyFile: /etc/traefik/certs/miapp.local.key
|
||||
```
|
||||
|
||||
## 3. Recargar el proxy
|
||||
|
||||
```bash
|
||||
cd ../../ # desde core/
|
||||
docker compose restart traefik
|
||||
docker logs -f core-proxy
|
||||
```
|
||||
|
||||
`dynamic_tls.yml` tiene `watch: true`, pero reiniciar es la forma más segura de
|
||||
confirmar que el certificado se cargó.
|
||||
|
||||
## 4. Etiquetar el proyecto
|
||||
|
||||
El router debe declarar `tls=true` **sin** certresolver (el certificado ya lo
|
||||
aporta el provider `file`):
|
||||
|
||||
```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"
|
||||
```
|
||||
|
||||
## 5. Resolver el nombre en el cliente
|
||||
|
||||
En cada máquina que deba acceder al sitio:
|
||||
|
||||
```bash
|
||||
echo "192.168.1.50 miapp.local" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
## 6. Evitar el aviso del navegador
|
||||
|
||||
El navegador mostrará "certificado no confiable" porque es auto-firmado.
|
||||
Opciones:
|
||||
|
||||
- **Windows**: `certutil -addstore -f "ROOT" miapp.local.crt` (PowerShell admin).
|
||||
- **Linux (systemd)**: copiar a `/usr/local/share/ca-certificates/` y ejecutar
|
||||
`sudo update-ca-certificates`.
|
||||
- **macOS**: doble clic sobre el `.crt` → *Añadir al llavero* → *Confiar*.
|
||||
- **Firefox** no usa el almacén del sistema:Settings → Certificates → Import.
|
||||
|
||||
---
|
||||
|
||||
## Certificados reales que usa este servidor
|
||||
|
||||
Los certificados de producción de los dominios públicos **no** viven acá: los
|
||||
emite Let's Encrypt y Traefik los guarda en `../acme.json` (también excluido
|
||||
del repositorio).
|
||||
19
core/data/dynamic_tls.yml
Normal file
19
core/data/dynamic_tls.yml
Normal file
@ -0,0 +1,19 @@
|
||||
# ==============================================================================
|
||||
# Certificados TLS estáticos (dominios .local / sin Let's Encrypt)
|
||||
# ==============================================================================
|
||||
# Traefik monta este archivo en /etc/traefik/dynamic_tls.yml (solo lectura) y lo
|
||||
# recarga automáticamente (watch: true en traefik.yml).
|
||||
#
|
||||
# Para habilitar un certificado local:
|
||||
# 1. Generá el par .crt / .key en core/data/certs/ (ver core/data/certs/README.md)
|
||||
# 2. Descomentá el bloque correspondiente con la RUTA ABSOLUTA dentro del
|
||||
# contenedor (/etc/traefik/certs/...), no la ruta del host.
|
||||
# 3. Reiniciá el proxy: docker compose restart traefik (desde core/)
|
||||
#
|
||||
# Los archivos .crt y .key están excluidos por .gitignore: nunca se versionan.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
tls:
|
||||
certificates: []
|
||||
# - certFile: /etc/traefik/certs/miapp.local.crt
|
||||
# keyFile: /etc/traefik/certs/miapp.local.key
|
||||
18
core/data/traefik.yml
Normal file
18
core/data/traefik.yml
Normal file
@ -0,0 +1,18 @@
|
||||
api:
|
||||
dashboard: true
|
||||
insecure: true
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
websecure:
|
||||
address: ":443"
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
network: "proxy_network"
|
||||
exposedByDefault: false
|
||||
file:
|
||||
filename: "/etc/traefik/dynamic_tls.yml"
|
||||
watch: true
|
||||
23
core/docker-compose.yml
Normal file
23
core/docker-compose.yml
Normal file
@ -0,0 +1,23 @@
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: core-proxy
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./data/traefik.yml:/traefik.yml:ro
|
||||
- ./data/dynamic_tls.yml:/etc/traefik/dynamic_tls.yml:ro
|
||||
- ./data/certs:/etc/traefik/certs:ro
|
||||
- ./data/acme.json:/acme.json
|
||||
networks:
|
||||
- proxy_network
|
||||
|
||||
networks:
|
||||
proxy_network:
|
||||
external: true
|
||||
245
docs/arquitectura.md
Normal file
245
docs/arquitectura.md
Normal file
@ -0,0 +1,245 @@
|
||||
# Arquitectura del servidor y gestión de URLs
|
||||
|
||||
Guía de instalación del entorno base en el host (`~/srv`), despliegue de la
|
||||
infraestructura común y alta de proyectos Docker enlazados al proxy inverso.
|
||||
|
||||
> Documento de referencia. Para el detalle fino de etiquetas TLS ver
|
||||
> [`http-https.md`](http-https.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Configuración inicial del host
|
||||
|
||||
### 1.1 Estructura base y red Docker
|
||||
|
||||
```bash
|
||||
mkdir -p ~/srv/core ~/srv/projects
|
||||
docker network create proxy_network
|
||||
```
|
||||
|
||||
Permisos para usar Docker sin `sudo`:
|
||||
|
||||
```bash
|
||||
sudo usermod -aG docker "$USER"
|
||||
newgrp docker
|
||||
```
|
||||
|
||||
### 1.2 Habilitar el script generador
|
||||
|
||||
```bash
|
||||
chmod +x ~/srv/init_project.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Puesta en marcha del reverse proxy (`~/srv/core`)
|
||||
|
||||
### Opción A — Traefik (detección automática por etiquetas)
|
||||
|
||||
`~/srv/core/docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: core-proxy
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./data/traefik.yml:/traefik.yml:ro
|
||||
- ./data/acme.json:/acme.json
|
||||
networks:
|
||||
- proxy_network
|
||||
|
||||
networks:
|
||||
proxy_network:
|
||||
external: true
|
||||
```
|
||||
|
||||
Configuración estática `~/srv/core/data/traefik.yml`:
|
||||
|
||||
```yaml
|
||||
api:
|
||||
dashboard: true
|
||||
insecure: true
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
websecure:
|
||||
address: ":443"
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
network: "proxy_network"
|
||||
exposedByDefault: false
|
||||
file:
|
||||
filename: "/etc/traefik/dynamic_tls.yml"
|
||||
watch: true
|
||||
```
|
||||
|
||||
Inicialización del almacenamiento de certificados:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/srv/core/data
|
||||
touch ~/srv/core/data/acme.json
|
||||
chmod 600 ~/srv/core/data/acme.json
|
||||
```
|
||||
|
||||
Arranque:
|
||||
|
||||
```bash
|
||||
cd ~/srv/core && docker compose up -d
|
||||
```
|
||||
|
||||
### Opción B — Nginx Proxy Manager (panel web)
|
||||
|
||||
Misma idea, pero la configuración se hace desde la interfaz:
|
||||
|
||||
1. Entrar a `http://<IP-SERVIDOR>:81`.
|
||||
2. **Hosts → Proxy Hosts → Add Proxy Host**.
|
||||
- **Domain Names:** `api.midominio.com`
|
||||
- **Scheme:** `http`
|
||||
- **Forward Hostname / IP:** nombre del contenedor en `proxy_network`
|
||||
- **Forward Port:** puerto interno del contenedor
|
||||
- **Block Common Exploits:** activado
|
||||
3. Pestaña **SSL** → *Request a new SSL Certificate* → *Force SSL*.
|
||||
4. **Save**.
|
||||
|
||||
---
|
||||
|
||||
## 3. Alta de proyectos
|
||||
|
||||
### Paso 1 — Crear la estructura limpia
|
||||
|
||||
```bash
|
||||
~/srv/init_project.sh api-ventas api.midominio.com 80
|
||||
```
|
||||
|
||||
Genera:
|
||||
|
||||
```text
|
||||
~/srv/projects/api-ventas/
|
||||
├── app/ # Código, repo o Dockerfile
|
||||
├── data/
|
||||
│ ├── db/
|
||||
│ └── storage/
|
||||
├── .env # Variables de entorno (chmod 600)
|
||||
└── docker-compose.yml # Plantilla conectada a proxy_network
|
||||
```
|
||||
|
||||
### Paso 2 — Colocar el proyecto
|
||||
|
||||
```bash
|
||||
# Clonando un repositorio
|
||||
git clone https://github.com/tu-usuario/api-ventas.git ~/srv/projects/api-ventas/app/
|
||||
|
||||
# O copiando archivos locales
|
||||
cp -r /ruta/a/tus/archivos/* ~/srv/projects/api-ventas/app/
|
||||
```
|
||||
|
||||
### Paso 3 — Ajustar `docker-compose.yml`
|
||||
|
||||
- **Build local:** descomentar
|
||||
```yaml
|
||||
build:
|
||||
context: ./app
|
||||
dockerfile: Dockerfile
|
||||
```
|
||||
- **Imagen ya publicada:** reemplazar `image: <proyecto>:latest`
|
||||
- **Servicios adicionales** (Postgres, Redis): agregarlos en el mismo archivo
|
||||
usando la red `default`, así quedan aislados del proxy.
|
||||
|
||||
### Paso 4 — Levantar
|
||||
|
||||
```bash
|
||||
cd ~/srv/projects/api-ventas
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Paso 5 — Post-instalación (opcional, apps Laravel)
|
||||
|
||||
```bash
|
||||
docker exec -it <proyecto>-web composer install --no-interaction --prefer-dist
|
||||
docker exec -it <proyecto>-web php artisan key:generate --force
|
||||
docker exec -it <proyecto>-web php artisan migrate --force
|
||||
docker exec -it <proyecto>-web php artisan optimize:clear
|
||||
```
|
||||
|
||||
Verificar permisos si el usuario `www-data` no puede escribir:
|
||||
|
||||
```bash
|
||||
docker exec -it <proyecto>-web chown -R www-data:www-data /var/www/<app>/storage
|
||||
docker exec -it <proyecto>-web chmod -R 775 /var/www/<app>/storage /var/www/<app>/bootstrap/cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Dónde y cómo se registran las URLs
|
||||
|
||||
### Traefik (automático)
|
||||
|
||||
El registro se hace con **etiquetas** en el `docker-compose.yml` del proyecto:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=proxy_network"
|
||||
- "traefik.http.routers.api-ventas.rule=Host(`api.midominio.com`)"
|
||||
- "traefik.http.routers.api-ventas.entrypoints=websecure"
|
||||
- "traefik.http.routers.api-ventas.tls=true"
|
||||
- "traefik.http.routers.api-ventas.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.api-ventas.loadbalancer.server.port=80"
|
||||
```
|
||||
|
||||
Traefik lee el socket de Docker, detecta el contenedor, genera el enrutamiento y
|
||||
solicita el certificado SSL automáticamente.
|
||||
|
||||
### Nginx Proxy Manager (panel web)
|
||||
|
||||
Ver [Opción B](#opción-b--nginx-proxy-manager-panel-web).
|
||||
|
||||
---
|
||||
|
||||
## 5. Copias de seguridad
|
||||
|
||||
Los respaldos se guardan **fuera** de los repositorios Git:
|
||||
|
||||
```bash
|
||||
# PostgreSQL
|
||||
docker exec <cont-pg> pg_dump -U <user> <db> > ~/srv/backups/<proyecto>-$(date +%F).sql
|
||||
|
||||
# MySQL
|
||||
docker exec <cont-mysql> mysqldump -u<user> -p <db> > ~/srv/backups/<proyecto>-$(date +%F).sql
|
||||
|
||||
# Archivos
|
||||
tar czf ~/srv/backups/<proyecto>-files-$(date +%F).tar.gz ~/srv/projects/<proyecto>/data
|
||||
```
|
||||
|
||||
Sugerencia de rotación (crontab diario a las 3:00):
|
||||
|
||||
```cron
|
||||
0 3 * * * /usr/local/bin/backup-proyectos.sh >> ~/srv/backups/backup.log 2>&1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Resumen de convenciones
|
||||
|
||||
| Elemento | Convención |
|
||||
|---|---|
|
||||
| Carpeta de proyecto | `~/srv/projects/<nombre>/` |
|
||||
| Nombre del contenedor | `<nombre>-web` |
|
||||
| Red del proxy | `proxy_network` (externa) |
|
||||
| Red interna | `default` (bridge, aislada) |
|
||||
| Secretos | `~/srv/projects/<nombre>/.env` con `chmod 600` |
|
||||
| Dominios locales | `<proyecto>.local` |
|
||||
| Certificados `.local` | `~/srv/core/data/certs/` + `dynamic_tls.yml` |
|
||||
| Certificados públicos | Let's Encrypt vía `acme.json` |
|
||||
247
docs/http-https.md
Normal file
247
docs/http-https.md
Normal file
@ -0,0 +1,247 @@
|
||||
# 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 |
|
||||
187
init_project.sh
Executable file
187
init_project.sh
Executable file
@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================================
|
||||
# Script de aprovisionamiento de proyectos Docker (Estructura Limpia)
|
||||
# ==============================================================================
|
||||
# Uso: ./init_project.sh <nombre-del-proyecto> <dominio-o-url> [puerto-interno]
|
||||
#
|
||||
# Genera en $SRV_DIR/projects/<proyecto> la estructura base:
|
||||
# app/ -> código fuente, repo o Dockerfile
|
||||
# data/{db,storage}/ -> volúmenes persistentes
|
||||
# .env -> variables de entorno (chmod 600)
|
||||
# docker-compose.yml -> plantilla conectada a proxy_network + etiquetas Traefik
|
||||
#
|
||||
# La plantilla incluye HTTP con redirección a HTTPS y HTTPS con Let's Encrypt,
|
||||
# más un bloque comentado alternativo para dominios .local con certificado
|
||||
# auto-firmado (ver docs/http-https.md).
|
||||
# ==============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# Directorio base de los proyectos. Configurable con SRV_DIR.
|
||||
BASE_DIR="${SRV_DIR:-$HOME/srv}"
|
||||
PROJECTS_DIR="$BASE_DIR/projects"
|
||||
NETWORK_NAME="proxy_network"
|
||||
|
||||
# Colores para salida de terminal
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Validar argumentos
|
||||
if [ $# -lt 2 ]; then
|
||||
echo -e "${RED}Uso:${NC} $0 <nombre-del-proyecto> <dominio-o-url> [puerto-interno]"
|
||||
echo -e "Ejemplo: $0 api-ventas api.midominio.com 80"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PROJECT_NAME="$1"
|
||||
PROJECT_DOMAIN="$2"
|
||||
INTERNAL_PORT="${3:-80}"
|
||||
TARGET_DIR="$PROJECTS_DIR/$PROJECT_NAME"
|
||||
|
||||
echo -e "${BLUE}==>${NC} Inicializando entorno para: ${GREEN}$PROJECT_NAME${NC}"
|
||||
|
||||
# 1. Asegurar red compartida externa
|
||||
if ! docker network ls --format '{{.Name}}' | grep -wq "$NETWORK_NAME"; then
|
||||
echo -e "${YELLOW}Creando red externa $NETWORK_NAME...${NC}"
|
||||
docker network create "$NETWORK_NAME"
|
||||
fi
|
||||
|
||||
# 2. Comprobar que no exista el proyecto
|
||||
if [ -d "$TARGET_DIR" ]; then
|
||||
echo -e "${RED}Error:${NC} El directorio $TARGET_DIR ya existe."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3. Crear árbol de carpetas limpias
|
||||
# - app/: Destino para clonar repositorios o poner tu código fuente / Dockerfile
|
||||
# - data/: Para persistencia de volúmenes de base de datos o archivos
|
||||
mkdir -p "$PROJECTS_DIR"
|
||||
mkdir -p "$TARGET_DIR/app"
|
||||
mkdir -p "$TARGET_DIR/data/db"
|
||||
mkdir -p "$TARGET_DIR/data/storage"
|
||||
|
||||
# Asignar permisos restrictivos al directorio raíz del proyecto
|
||||
chmod 700 "$TARGET_DIR"
|
||||
|
||||
# 4. Crear archivo .env base personalizable
|
||||
cat <<EOF > "$TARGET_DIR/.env"
|
||||
# ==============================================================================
|
||||
# Variables de entorno - $PROJECT_NAME
|
||||
# ==============================================================================
|
||||
APP_NAME=$PROJECT_NAME
|
||||
APP_DOMAIN=$PROJECT_DOMAIN
|
||||
PORT=$INTERNAL_PORT
|
||||
|
||||
# Agrega aquí las variables y secretos de tu proyecto.
|
||||
# Este archivo tiene permisos 600 y NO debe subirse a Git.
|
||||
EOF
|
||||
|
||||
chmod 600 "$TARGET_DIR/.env"
|
||||
|
||||
# 5. Crear plantilla docker-compose.yml lista para tu propia imagen o build local
|
||||
cat <<EOF > "$TARGET_DIR/docker-compose.yml"
|
||||
services:
|
||||
# Servicio principal conectado al Proxy Reverso
|
||||
web:
|
||||
# Opción A: Construir tu propio Dockerfile dentro de app/
|
||||
build:
|
||||
context: ./app
|
||||
dockerfile: Dockerfile
|
||||
|
||||
# Opción B: Usar una imagen existente de tu registro o Docker Hub
|
||||
# (comentá el bloque build: de arriba y descomentá la línea image:)
|
||||
|
||||
image: ${PROJECT_NAME}:latest
|
||||
container_name: ${PROJECT_NAME}-web
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./app:/app
|
||||
- ./data/storage:/app/storage
|
||||
networks:
|
||||
- default
|
||||
- ${NETWORK_NAME}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=${NETWORK_NAME}"
|
||||
|
||||
# ======================================================================
|
||||
# MODO A — Dominio público con Let's Encrypt
|
||||
# ======================================================================
|
||||
|
||||
# 1. Middleware de redirección HTTP -> HTTPS
|
||||
- "traefik.http.middlewares.${PROJECT_NAME}-redirect.redirectscheme.scheme=https"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME}-redirect.redirectscheme.permanent=true"
|
||||
|
||||
# 2. Router HTTP (puerto 80): redirige al HTTPS
|
||||
- "traefik.http.routers.${PROJECT_NAME}-http.rule=Host(\`${PROJECT_DOMAIN}\`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-http.entrypoints=web"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-http.middlewares=${PROJECT_NAME}-redirect"
|
||||
|
||||
# 3. Router HTTPS (puerto 443): atiende el tráfico seguro
|
||||
- "traefik.http.routers.${PROJECT_NAME}-https.rule=Host(\`${PROJECT_DOMAIN}\`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-https.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-https.tls=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-https.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-https.service=${PROJECT_NAME}-svc"
|
||||
|
||||
# 4. Servicio: puerto interno del contenedor
|
||||
- "traefik.http.services.${PROJECT_NAME}-svc.loadbalancer.server.port=${INTERNAL_PORT}"
|
||||
|
||||
# ======================================================================
|
||||
# MODO B — Dominio .local con certificado auto-firmado
|
||||
# ======================================================================
|
||||
# Reemplazá los routers de arriba por estos, agregá el certificado en
|
||||
# ~/srv/core/data/dynamic_tls.yml y quitá el certresolver (el certificado
|
||||
# lo aporta el provider "file"). Ver docs/http-https.md
|
||||
#
|
||||
# - "traefik.http.routers.${PROJECT_NAME}.rule=Host(\`${PROJECT_DOMAIN}\`)"
|
||||
# - "traefik.http.routers.${PROJECT_NAME}.entrypoints=websecure"
|
||||
# - "traefik.http.routers.${PROJECT_NAME}.tls=true"
|
||||
# - "traefik.http.services.${PROJECT_NAME}.loadbalancer.server.port=${INTERNAL_PORT}"
|
||||
|
||||
# ==============================================================================
|
||||
# Servicios internos (base de datos, colas, etc.)
|
||||
# ==============================================================================
|
||||
# IMPORTANTE: conectalos solo a la red "default" para que queden aislados
|
||||
# del proxy inverso y no sean accesibles desde internet.
|
||||
#
|
||||
# db:
|
||||
# image: postgres:14
|
||||
# container_name: ${PROJECT_NAME}-pg
|
||||
# restart: unless-stopped
|
||||
# networks:
|
||||
# - default
|
||||
# environment:
|
||||
# POSTGRES_USER: \${POSTGRES_USER}
|
||||
# POSTGRES_PASSWORD: \${POSTGRES_PASSWORD}
|
||||
# POSTGRES_DB: \${POSTGRES_DB}
|
||||
# volumes:
|
||||
# - ./data/db:/var/lib/postgresql
|
||||
# # Exponer solo en localhost, nunca público:
|
||||
# ports:
|
||||
# - "127.0.0.1:5432:5432"
|
||||
|
||||
# Redes: default (aislada para servicios internos) y proxy_network (para el reverse proxy)
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
${NETWORK_NAME}:
|
||||
external: true
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓${NC} Estructura limpia creada en: ${BLUE}$TARGET_DIR${NC}"
|
||||
echo -e " ├── ${BLUE}app/${NC} <- Pega aquí tu código, Dockerfile o clona tu repo"
|
||||
echo -e " ├── ${BLUE}data/${NC} <- Directorio para volúmenes persistentes"
|
||||
echo -e " ├── ${YELLOW}.env${NC} <- Variables de entorno"
|
||||
echo -e " └── ${YELLOW}docker-compose.yml${NC}<- Orquestación enlazada al proxy"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Próximos pasos:${NC}"
|
||||
echo -e " 1. Copia tus archivos en: ${BLUE}$TARGET_DIR/app/${NC}"
|
||||
echo -e " 2. Ajusta ${YELLOW}$TARGET_DIR/docker-compose.yml${NC} según tu imagen o build"
|
||||
echo -e " 3. Levanta el proxy si aún no está: ${GREEN}cd $HOME/srv/core && docker compose up -d${NC}"
|
||||
echo -e " 4. Inicia el proyecto: ${GREEN}cd $TARGET_DIR && docker compose up -d${NC}"
|
||||
Loading…
Reference in New Issue
Block a user