Compare commits
6 Commits
81c7b45069
...
backup-ori
| Author | SHA1 | Date | |
|---|---|---|---|
| 624be31e57 | |||
| 5c7adf4980 | |||
| 02effe5d7a | |||
|
|
184d92bf9a | ||
|
|
117a0c51ac | ||
|
|
ac9091e32f |
2
.env
2
.env
@@ -19,7 +19,7 @@
|
|||||||
# TRAEFIK_CERTRESOLVER=letsencrypt
|
# TRAEFIK_CERTRESOLVER=letsencrypt
|
||||||
|
|
||||||
# Middleware de autenticación (SSO, etc.)
|
# Middleware de autenticación (SSO, etc.)
|
||||||
# TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
|
# TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
|
||||||
|
|
||||||
# Dominios de ejemplo (cámbialos por los tuyos)
|
# Dominios de ejemplo (cámbialos por los tuyos)
|
||||||
# PORTAINER_DOMAIN=portainer.example.com
|
# PORTAINER_DOMAIN=portainer.example.com
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ Variables principales a configurar:
|
|||||||
- `PORTAINER_DOMAIN`: Tu dominio para Portainer UI (ej: `portainer.tudominio.com`)
|
- `PORTAINER_DOMAIN`: Tu dominio para Portainer UI (ej: `portainer.tudominio.com`)
|
||||||
- `PORTAINER_API_DOMAIN`: Tu dominio para la API de Portainer (ej: `portainer-api.tudominio.com`)
|
- `PORTAINER_API_DOMAIN`: Tu dominio para la API de Portainer (ej: `portainer-api.tudominio.com`)
|
||||||
- `PORTAINER_API_IP_WHITELIST`: IPs permitidas para acceso directo a la API
|
- `PORTAINER_API_IP_WHITELIST`: IPs permitidas para acceso directo a la API
|
||||||
- `TRAEFIK_AUTH_MIDDLEWARE`: Middleware de autenticación (ej: `ths-authentik@docker`)
|
- `TRAEFIK_AUTH_MIDDLEWARE`: Middleware de autenticación (ej: `authentik@docker`)
|
||||||
|
|
||||||
### Paso 10: Actualizar Stack de Portainer (Opcional)
|
### Paso 10: Actualizar Stack de Portainer (Opcional)
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ PORTAINER_API_DOMAIN=portainer-api.example.com
|
|||||||
|
|
||||||
# Seguridad
|
# Seguridad
|
||||||
PORTAINER_API_IP_WHITELIST=10.8.0.0/24,172.18.0.1/32
|
PORTAINER_API_IP_WHITELIST=10.8.0.0/24,172.18.0.1/32
|
||||||
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
|
TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuraciones por Stack
|
### Configuraciones por Stack
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ labels:
|
|||||||
traefik.http.routers.dashboard.entrypoints: "websecure"
|
traefik.http.routers.dashboard.entrypoints: "websecure"
|
||||||
traefik.http.routers.dashboard.tls.certresolver: "letsencrypt"
|
traefik.http.routers.dashboard.tls.certresolver: "letsencrypt"
|
||||||
traefik.http.routers.dashboard.service: "api@internal"
|
traefik.http.routers.dashboard.service: "api@internal"
|
||||||
traefik.http.routers.dashboard.middlewares: "ths-authentik@docker"
|
traefik.http.routers.dashboard.middlewares: "authentik@docker"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Opción 2: Acceso local (inseguro - solo desarrollo)**
|
**Opción 2: Acceso local (inseguro - solo desarrollo)**
|
||||||
@@ -165,7 +165,7 @@ services:
|
|||||||
traefik.http.services.mi-servicio.loadbalancer.server.port: "80"
|
traefik.http.services.mi-servicio.loadbalancer.server.port: "80"
|
||||||
|
|
||||||
# Middleware (opcional)
|
# Middleware (opcional)
|
||||||
traefik.http.routers.mi-servicio.middlewares: "ths-authentik@docker"
|
traefik.http.routers.mi-servicio.middlewares: "authentik@docker"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
proxy:
|
proxy:
|
||||||
@@ -180,7 +180,7 @@ labels:
|
|||||||
traefik.http.routers.app-ui.rule: "Host(`app.tudominio.com`)"
|
traefik.http.routers.app-ui.rule: "Host(`app.tudominio.com`)"
|
||||||
traefik.http.routers.app-ui.entrypoints: "websecure"
|
traefik.http.routers.app-ui.entrypoints: "websecure"
|
||||||
traefik.http.routers.app-ui.tls.certresolver: "letsencrypt"
|
traefik.http.routers.app-ui.tls.certresolver: "letsencrypt"
|
||||||
traefik.http.routers.app-ui.middlewares: "ths-authentik@docker"
|
traefik.http.routers.app-ui.middlewares: "authentik@docker"
|
||||||
traefik.http.routers.app-ui.priority: "10"
|
traefik.http.routers.app-ui.priority: "10"
|
||||||
|
|
||||||
# API pública sin protección
|
# API pública sin protección
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ ADGUARD_CERT_KEY_PATH=/opt/adguard/certs/adguard.key
|
|||||||
TRAEFIK_DOCKER_NETWORK=proxy
|
TRAEFIK_DOCKER_NETWORK=proxy
|
||||||
TRAEFIK_ENTRYPOINT_SECURE=websecure
|
TRAEFIK_ENTRYPOINT_SECURE=websecure
|
||||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||||
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
|
TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
|
||||||
```
|
```
|
||||||
|
|
||||||
## ⚙️ Configuración Post-Instalación
|
## ⚙️ Configuración Post-Instalación
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ Una vez configurado el middleware, añade la label a los servicios que quieras p
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
labels:
|
labels:
|
||||||
traefik.http.routers.portainer.middlewares: "ths-authentik@docker"
|
traefik.http.routers.portainer.middlewares: "authentik@docker"
|
||||||
```
|
```
|
||||||
|
|
||||||
O si definiste el middleware en archivo:
|
O si definiste el middleware en archivo:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
ths-authentik-postgres:
|
authentik-postgres:
|
||||||
image: ${AUTHENTIK_POSTGRES_IMAGE}
|
image: ${AUTHENTIK_POSTGRES_IMAGE}
|
||||||
container_name: ths-authentik-postgres
|
container_name: authentik-postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD}
|
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD}
|
||||||
@@ -10,103 +10,95 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${AUTHENTIK_POSTGRES_PATH}:/var/lib/postgresql/data:Z
|
- ${AUTHENTIK_POSTGRES_PATH}:/var/lib/postgresql/data:Z
|
||||||
networks:
|
networks:
|
||||||
- ths_authentik_internal
|
- authentik_internal
|
||||||
|
|
||||||
ths-authentik-redis:
|
authentik-redis:
|
||||||
image: ${AUTHENTIK_REDIS_IMAGE}
|
image: ${AUTHENTIK_REDIS_IMAGE}
|
||||||
container_name: ths-authentik-redis
|
container_name: authentik-redis
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
|
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
|
||||||
volumes:
|
volumes:
|
||||||
- ${AUTHENTIK_REDIS_PATH}:/data:Z
|
- ${AUTHENTIK_REDIS_PATH}:/data:Z
|
||||||
networks:
|
networks:
|
||||||
- ths_authentik_internal
|
- authentik_internal
|
||||||
|
|
||||||
ths-authentik-server:
|
authentik-server:
|
||||||
image: ${AUTHENTIK_IMAGE}
|
image: ${AUTHENTIK_IMAGE}
|
||||||
container_name: ths-authentik-server
|
container_name: authentik-server
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["server"]
|
command: ["server"]
|
||||||
environment:
|
environment:
|
||||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||||
|
|
||||||
# OJO: forzamos hosts internos para evitar colisiones y depender del .env
|
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_DB_HOST}
|
||||||
AUTHENTIK_POSTGRESQL__HOST: ths-authentik-postgres
|
|
||||||
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER}
|
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER}
|
||||||
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME}
|
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME}
|
||||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
|
||||||
|
|
||||||
AUTHENTIK_REDIS__HOST: ths-authentik-redis
|
AUTHENTIK_REDIS__HOST: ${AUTHENTIK_REDIS_HOST}
|
||||||
|
|
||||||
|
# Bootstrap inicial (primera vez)
|
||||||
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
|
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
|
||||||
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}
|
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}
|
||||||
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN}
|
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN}
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- ths-authentik-postgres
|
- authentik-postgres
|
||||||
- ths-authentik-redis
|
- authentik-redis
|
||||||
|
|
||||||
expose:
|
expose:
|
||||||
- "${AUTHENTIK_HTTP_PORT}"
|
- "${AUTHENTIK_HTTP_PORT}"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- ths_authentik_internal
|
- authentik_internal
|
||||||
- proxy
|
- proxy
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
|
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
|
||||||
|
|
||||||
# Service Authentik (panel + endpoints)
|
# Router del panel de Authentik
|
||||||
traefik.http.services.ths-authentik.loadbalancer.server.port: "${AUTHENTIK_HTTP_PORT}"
|
traefik.http.routers.authentik.rule: "Host(`${AUTHENTIK_DOMAIN}`)"
|
||||||
|
traefik.http.routers.authentik.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||||
|
traefik.http.routers.authentik.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||||
|
traefik.http.services.authentik.loadbalancer.server.port: "${AUTHENTIK_HTTP_PORT}"
|
||||||
|
|
||||||
# Panel Authentik (auth.thehomelesssherlock.com)
|
# Middleware de forwardAuth que usaremos en Portainer, Pi-hole, etc.
|
||||||
traefik.http.routers.ths-authentik.rule: "Host(`${AUTHENTIK_DOMAIN}`)"
|
traefik.http.middlewares.authentik.forwardauth.address: "http://authentik-server:${AUTHENTIK_HTTP_PORT}/outpost.goauthentik.io/auth/traefik"
|
||||||
traefik.http.routers.ths-authentik.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: "true"
|
||||||
traefik.http.routers.ths-authentik.tls: "true"
|
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Uid,X-Authentik-Jwt"
|
||||||
traefik.http.routers.ths-authentik.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
|
||||||
traefik.http.routers.ths-authentik.service: "ths-authentik"
|
|
||||||
|
|
||||||
# Middleware forwardAuth (para proteger otros servicios) -> usar ths-ths-authentik@docker en tus stacks THS
|
# Callback del outpost en gitea hacia Authentik
|
||||||
traefik.http.middlewares.ths-authentik.forwardauth.address: "http://ths-authentik-server:${AUTHENTIK_HTTP_PORT}/outpost.goauthentik.io/auth/traefik"
|
traefik.http.routers.authentik-outpost-gitea.rule: "Host(`${GITEA_DOMAIN}`) && PathPrefix(`/outpost.goauthentik.io/`)"
|
||||||
traefik.http.middlewares.ths-authentik.forwardauth.trustForwardHeader: "true"
|
traefik.http.routers.authentik-outpost-gitea.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||||
traefik.http.middlewares.ths-authentik.forwardauth.authResponseHeaders: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Uid,X-Authentik-Jwt"
|
traefik.http.routers.authentik-outpost-gitea.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||||
|
traefik.http.routers.authentik-outpost-gitea.service: "authentik"
|
||||||
|
traefik.http.routers.authentik-outpost-gitea.priority: "50"
|
||||||
|
|
||||||
# OUTPOST genérico para TODO el dominio THS (subdominios + apex + www)
|
authentik-worker:
|
||||||
# ✅ Sin comas dentro de Host()
|
|
||||||
traefik.http.routers.ths-authentik-outpost.rule: "(HostRegexp(`{subdomain:[a-z0-9-]+}.thehomelesssherlock.com`) || Host(`thehomelesssherlock.com`) || Host(`www.thehomelesssherlock.com`)) && PathPrefix(`/outpost.goauthentik.io/`)"
|
|
||||||
traefik.http.routers.ths-authentik-outpost.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
|
||||||
traefik.http.routers.ths-authentik-outpost.tls: "true"
|
|
||||||
traefik.http.routers.ths-authentik-outpost.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
|
||||||
traefik.http.routers.ths-authentik-outpost.service: "ths-authentik"
|
|
||||||
traefik.http.routers.ths-authentik-outpost.priority: "1000"
|
|
||||||
|
|
||||||
ths-authentik-worker:
|
|
||||||
image: ${AUTHENTIK_IMAGE}
|
image: ${AUTHENTIK_IMAGE}
|
||||||
container_name: ths-authentik-worker
|
container_name: authentik-worker
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: ["worker"]
|
command: ["worker"]
|
||||||
environment:
|
environment:
|
||||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||||
|
|
||||||
# OJO: forzamos hosts internos igual que en server
|
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_DB_HOST}
|
||||||
AUTHENTIK_POSTGRESQL__HOST: ths-authentik-postgres
|
|
||||||
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER}
|
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER}
|
||||||
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME}
|
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME}
|
||||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
|
||||||
|
|
||||||
AUTHENTIK_REDIS__HOST: ths-authentik-redis
|
AUTHENTIK_REDIS__HOST: ${AUTHENTIK_REDIS_HOST}
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- ths-authentik-postgres
|
- authentik-postgres
|
||||||
- ths-authentik-redis
|
- authentik-redis
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- ths_authentik_internal
|
- authentik_internal
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
proxy:
|
proxy:
|
||||||
external: true
|
external: true
|
||||||
ths_authentik_internal:
|
authentik_internal:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
services:
|
services:
|
||||||
portainer:
|
portainer:
|
||||||
image: portainer/portainer-ee:2.33.7
|
image: ${PORTAINER_IMAGE:-portainer/portainer-ce:latest}
|
||||||
container_name: portainer
|
container_name: portainer
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
env_file:
|
||||||
- /opt/portainer/secrets/portainer:/run/secrets/portainer:ro,Z
|
- .env
|
||||||
- /opt/portainer/secrets/portainer:/run/portainer/portainer:ro,Z
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- /opt/portainer/data:/data:Z
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Clave de cifrado: misma clave montada en las dos rutas
|
||||||
|
- ${PORTAINER_SECRET_PATH:-/opt/portainer/secrets/portainer}:/run/secrets/portainer:ro,Z
|
||||||
|
- ${PORTAINER_SECRET_PATH:-/opt/portainer/secrets/portainer}:/run/portainer/portainer:ro,Z
|
||||||
|
|
||||||
|
# Socket de Docker (NO usar :Z aquí)
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
# Datos de Portainer (DB cifrada incluida)
|
||||||
|
- ${PORTAINER_DATA_PATH:-/opt/portainer/data}:/data:Z
|
||||||
|
|
||||||
|
# SELinux: evita bloqueos con docker.sock
|
||||||
security_opt:
|
security_opt:
|
||||||
- label=disable
|
- label=disable
|
||||||
|
|
||||||
@@ -17,24 +26,28 @@ services:
|
|||||||
- proxy
|
- proxy
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
traefik.enable: "true"
|
||||||
- "traefik.docker.network=proxy"
|
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK:-proxy}"
|
||||||
|
|
||||||
# 1) UI protegida Authentik
|
############################
|
||||||
- "traefik.http.routers.portainer.rule=Host(`portainer.thehomelesssherlock.com`)"
|
# 1) UI protegida (ej: SSO)
|
||||||
- "traefik.http.routers.portainer.entrypoints=websecure"
|
############################
|
||||||
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
|
traefik.http.routers.portainer.rule: "Host(`${PORTAINER_DOMAIN:-portainer.example.com}`)"
|
||||||
- "traefik.http.routers.portainer.middlewares=ths-authentik@docker"
|
traefik.http.routers.portainer.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE:-websecure}"
|
||||||
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
|
traefik.http.routers.portainer.tls.certresolver: "${TRAEFIK_CERTRESOLVER:-letsencrypt}"
|
||||||
|
traefik.http.routers.portainer.middlewares: "${TRAEFIK_AUTH_MIDDLEWARE:-authentik@docker}"
|
||||||
|
traefik.http.services.portainer.loadbalancer.server.port: "${PORTAINER_HTTP_PORT:-9000}"
|
||||||
|
|
||||||
# 2) API/App móvil SIN Authentik, SOLO por VPN (WireGuard)
|
#########################################################
|
||||||
- "traefik.http.middlewares.portainer-api-ip.ipallowlist.sourcerange=10.8.0.0/24,172.18.0.1/32"
|
# 2) API/App móvil SIN SSO, restringida por IP (ej: VPN)
|
||||||
- "traefik.http.routers.portainer-direct.rule=Host(`portainer-api.thehomelesssherlock.com`)"
|
#########################################################
|
||||||
- "traefik.http.routers.portainer-direct.entrypoints=websecure"
|
traefik.http.middlewares.portainer-api-ip.ipwhitelist.sourcerange: "${PORTAINER_API_IP_WHITELIST:-10.8.0.0/24,172.18.0.1/32}"
|
||||||
- "traefik.http.routers.portainer-direct.tls.certresolver=letsencrypt"
|
traefik.http.routers.portainer-direct.rule: "Host(`${PORTAINER_API_DOMAIN:-portainer-api.example.com}`)"
|
||||||
- "traefik.http.routers.portainer-direct.middlewares=portainer-api-ip"
|
traefik.http.routers.portainer-direct.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE:-websecure}"
|
||||||
- "traefik.http.routers.portainer-direct.service=portainer"
|
traefik.http.routers.portainer-direct.tls.certresolver: "${TRAEFIK_CERTRESOLVER:-letsencrypt}"
|
||||||
- "traefik.http.routers.portainer-direct.priority=100"
|
traefik.http.routers.portainer-direct.middlewares: "portainer-api-ip"
|
||||||
|
traefik.http.routers.portainer-direct.service: "portainer"
|
||||||
|
traefik.http.routers.portainer-direct.priority: "100"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
proxy:
|
proxy:
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
# Mail Relay - SMTP de salida para stacks
|
|
||||||
|
|
||||||
Stack de relay SMTP interno para centralizar el envio de correos de tus apps sin montar un servidor de correo completo.
|
|
||||||
|
|
||||||
## 📋 Descripcion
|
|
||||||
|
|
||||||
Este stack despliega un relay SMTP con Postfix usando `boky/postfix` para:
|
|
||||||
- Recibir correo SMTP desde contenedores internos
|
|
||||||
- Reenviar todo a un proveedor SMTP externo (smarthost)
|
|
||||||
- Firmar con DKIM (autogenerado)
|
|
||||||
- Unificar configuracion de correo para todos los stacks
|
|
||||||
|
|
||||||
No incluye recepcion de correo (MX, IMAP, POP3, webmail).
|
|
||||||
|
|
||||||
## 🚀 Despliegue
|
|
||||||
|
|
||||||
### 1. Preparar rutas y secreto en el host
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo mkdir -p /opt/mail-relay/{queue,opendkim,secrets}
|
|
||||||
sudo chmod 700 /opt/mail-relay/secrets
|
|
||||||
printf '%s' 'CAMBIA_ESTA_PASSWORD_SMTP' | sudo tee /opt/mail-relay/secrets/relayhost_password > /dev/null
|
|
||||||
sudo chmod 600 /opt/mail-relay/secrets/relayhost_password
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Desplegar desde Portainer
|
|
||||||
|
|
||||||
1. Ve a **Stacks** -> **Add stack**
|
|
||||||
2. Nombre: `mail-relay`
|
|
||||||
3. Configura el repositorio Git
|
|
||||||
4. Compose path: `mail-relay/docker-compose.yml`
|
|
||||||
5. Carga variables desde `mail-relay/stack.env`
|
|
||||||
6. Ajusta al menos:
|
|
||||||
- `MAIL_RELAY_HOSTNAME`
|
|
||||||
- `MAIL_RELAY_ALLOWED_SENDER_DOMAINS`
|
|
||||||
- `MAIL_RELAY_MASQUERADED_DOMAINS`
|
|
||||||
- `MAIL_RELAY_SMARTHOST`
|
|
||||||
- `MAIL_RELAY_SMARTHOST_USERNAME`
|
|
||||||
7. Deploy del stack
|
|
||||||
|
|
||||||
## ⚙️ Variables importantes
|
|
||||||
|
|
||||||
```env
|
|
||||||
MAIL_RELAY_SMARTHOST=[smtp.proveedor.tld]:587
|
|
||||||
MAIL_RELAY_SMARTHOST_USERNAME=usuario-smtp
|
|
||||||
MAIL_RELAY_SMTP_TLS_SECURITY_LEVEL=encrypt
|
|
||||||
MAIL_RELAY_ALLOWED_SENDER_DOMAINS=tudominio.com
|
|
||||||
MAIL_RELAY_MASQUERADED_DOMAINS=tudominio.com
|
|
||||||
```
|
|
||||||
|
|
||||||
La password SMTP no se pone en `stack.env`; se lee desde el archivo host:
|
|
||||||
|
|
||||||
```text
|
|
||||||
/opt/mail-relay/secrets/relayhost_password
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔌 Conectar otras apps
|
|
||||||
|
|
||||||
En cada stack que deba enviar correo, añade la red externa:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
networks:
|
|
||||||
mail_internal:
|
|
||||||
external: true
|
|
||||||
```
|
|
||||||
|
|
||||||
Y en el servicio de la app:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
tu-app:
|
|
||||||
networks:
|
|
||||||
- default
|
|
||||||
- mail_internal
|
|
||||||
```
|
|
||||||
|
|
||||||
Config SMTP en la app:
|
|
||||||
|
|
||||||
```env
|
|
||||||
SMTP_HOST=mail-relay
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_FROM=noreply@tudominio.com
|
|
||||||
```
|
|
||||||
|
|
||||||
## ✅ DNS minimo recomendado
|
|
||||||
|
|
||||||
Para buena entregabilidad en Gmail/Outlook:
|
|
||||||
- SPF
|
|
||||||
- DKIM
|
|
||||||
- DMARC
|
|
||||||
|
|
||||||
### SPF (ejemplo)
|
|
||||||
|
|
||||||
```text
|
|
||||||
v=spf1 include:spf.tu-proveedor.tld ~all
|
|
||||||
```
|
|
||||||
|
|
||||||
### DKIM
|
|
||||||
|
|
||||||
Tras el primer arranque, extrae el TXT generado:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
find /opt/mail-relay/opendkim -type f -name '*.txt' -exec echo '### {}' \; -exec cat {} \;
|
|
||||||
```
|
|
||||||
|
|
||||||
Copia esos valores a tu DNS.
|
|
||||||
|
|
||||||
### DMARC (inicio en monitorizacion)
|
|
||||||
|
|
||||||
```text
|
|
||||||
v=DMARC1; p=none; adkim=s; aspf=s
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧪 Prueba rapida
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec -i mail-relay sendmail -t <<'EOF'
|
|
||||||
From: noreply@tudominio.com
|
|
||||||
To: destino@example.com
|
|
||||||
Subject: prueba mail relay
|
|
||||||
|
|
||||||
Correo de prueba del stack mail-relay.
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ Troubleshooting
|
|
||||||
|
|
||||||
Ver logs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker logs -f mail-relay
|
|
||||||
```
|
|
||||||
|
|
||||||
Errores tipicos:
|
|
||||||
- `Relay access denied`: revisa `MAIL_RELAY_ALLOWED_SENDER_DOMAINS`
|
|
||||||
- Auth fallida con proveedor: revisa usuario/password SMTP
|
|
||||||
- Rechazo por DNS: valida SPF/DKIM/DMARC
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
services:
|
|
||||||
mail-relay:
|
|
||||||
image: ${MAIL_RELAY_IMAGE}
|
|
||||||
container_name: mail-relay
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
LOG_FORMAT: ${MAIL_RELAY_LOG_FORMAT}
|
|
||||||
|
|
||||||
# Hostname del relay
|
|
||||||
POSTFIX_myhostname: ${MAIL_RELAY_HOSTNAME}
|
|
||||||
|
|
||||||
# Solo clientes internos del stack de correo
|
|
||||||
POSTFIX_mynetworks: ${MAIL_RELAY_MYNETWORKS}
|
|
||||||
|
|
||||||
# Dominios permitidos para el sender
|
|
||||||
ALLOWED_SENDER_DOMAINS: ${MAIL_RELAY_ALLOWED_SENDER_DOMAINS}
|
|
||||||
|
|
||||||
# Reescritura de dominio para hosts internos
|
|
||||||
MASQUERADED_DOMAINS: ${MAIL_RELAY_MASQUERADED_DOMAINS}
|
|
||||||
|
|
||||||
# Relay SMTP externo
|
|
||||||
RELAYHOST: ${MAIL_RELAY_SMARTHOST}
|
|
||||||
RELAYHOST_USERNAME: ${MAIL_RELAY_SMARTHOST_USERNAME}
|
|
||||||
RELAYHOST_PASSWORD_FILE: /run/secrets/relayhost_password
|
|
||||||
POSTFIX_smtp_tls_security_level: ${MAIL_RELAY_SMTP_TLS_SECURITY_LEVEL}
|
|
||||||
|
|
||||||
# DKIM
|
|
||||||
DKIM_AUTOGENERATE: ${MAIL_RELAY_DKIM_AUTOGENERATE}
|
|
||||||
DKIM_SELECTOR: ${MAIL_RELAY_DKIM_SELECTOR}
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- ${MAIL_RELAY_QUEUE_PATH}:/var/spool/postfix:Z
|
|
||||||
- ${MAIL_RELAY_DKIM_KEYS_PATH}:/etc/opendkim/keys:Z
|
|
||||||
- ${MAIL_RELAY_PASSWORD_FILE_PATH}:/run/secrets/relayhost_password:ro,Z
|
|
||||||
|
|
||||||
networks:
|
|
||||||
mail_internal:
|
|
||||||
ipv4_address: ${MAIL_RELAY_IPV4}
|
|
||||||
|
|
||||||
# No publicar puertos al exterior para uso interno entre contenedores.
|
|
||||||
# Descomenta para pruebas desde el host:
|
|
||||||
# ports:
|
|
||||||
# - "127.0.0.1:1587:587"
|
|
||||||
|
|
||||||
networks:
|
|
||||||
mail_internal:
|
|
||||||
name: ${MAIL_RELAY_NETWORK_NAME}
|
|
||||||
driver: bridge
|
|
||||||
ipam:
|
|
||||||
config:
|
|
||||||
- subnet: ${MAIL_RELAY_SUBNET}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
##### General #####
|
|
||||||
MAIL_RELAY_IMAGE=boky/postfix:v5.1.0
|
|
||||||
TZ=Europe/Madrid
|
|
||||||
MAIL_RELAY_LOG_FORMAT=plain
|
|
||||||
|
|
||||||
##### Network #####
|
|
||||||
MAIL_RELAY_NETWORK_NAME=mail_internal
|
|
||||||
MAIL_RELAY_SUBNET=10.77.0.0/24
|
|
||||||
MAIL_RELAY_IPV4=10.77.0.10
|
|
||||||
MAIL_RELAY_MYNETWORKS=127.0.0.0/8,10.77.0.0/24
|
|
||||||
|
|
||||||
##### Relay identity #####
|
|
||||||
MAIL_RELAY_HOSTNAME=mail.example.com
|
|
||||||
MAIL_RELAY_ALLOWED_SENDER_DOMAINS=example.com
|
|
||||||
MAIL_RELAY_MASQUERADED_DOMAINS=example.com
|
|
||||||
|
|
||||||
##### Upstream SMTP (smarthost) #####
|
|
||||||
MAIL_RELAY_SMARTHOST=[in-v3.mailjet.com]:587
|
|
||||||
MAIL_RELAY_SMARTHOST_USERNAME=tu-api-key-mailjet
|
|
||||||
MAIL_RELAY_SMTP_TLS_SECURITY_LEVEL=encrypt
|
|
||||||
|
|
||||||
##### DKIM #####
|
|
||||||
MAIL_RELAY_DKIM_AUTOGENERATE=true
|
|
||||||
MAIL_RELAY_DKIM_SELECTOR=mail
|
|
||||||
|
|
||||||
##### Host paths #####
|
|
||||||
MAIL_RELAY_QUEUE_PATH=/opt/mail-relay/queue
|
|
||||||
MAIL_RELAY_DKIM_KEYS_PATH=/opt/mail-relay/opendkim
|
|
||||||
MAIL_RELAY_PASSWORD_FILE_PATH=/opt/mail-relay/secrets/relayhost_password
|
|
||||||
@@ -81,7 +81,7 @@ TRAEFIK_ENABLE=true
|
|||||||
TRAEFIK_ENTRYPOINTS=websecure
|
TRAEFIK_ENTRYPOINTS=websecure
|
||||||
TRAEFIK_TLS=true
|
TRAEFIK_TLS=true
|
||||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||||
AUTH_MIDDLEWARE=ths-authentik@docker
|
AUTH_MIDDLEWARE=authentik@docker
|
||||||
|
|
||||||
# Dominios - Personaliza según tu dominio
|
# Dominios - Personaliza según tu dominio
|
||||||
DOMAIN=tudominio.com
|
DOMAIN=tudominio.com
|
||||||
@@ -188,7 +188,7 @@ En **Sonarr** y **Radarr**:
|
|||||||
|
|
||||||
### 8. Integración con Authentik (SSO)
|
### 8. Integración con Authentik (SSO)
|
||||||
|
|
||||||
Todos los servicios están protegidos con Authentik por defecto mediante el middleware `ths-authentik@docker`.
|
Todos los servicios están protegidos con Authentik por defecto mediante el middleware `authentik@docker`.
|
||||||
|
|
||||||
Para personalizar el acceso:
|
Para personalizar el acceso:
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ services:
|
|||||||
- traefik.http.routers.prowlarr.entrypoints=websecure
|
- traefik.http.routers.prowlarr.entrypoints=websecure
|
||||||
- traefik.http.routers.prowlarr.tls=true
|
- traefik.http.routers.prowlarr.tls=true
|
||||||
- traefik.http.routers.prowlarr.tls.certresolver=letsencrypt
|
- traefik.http.routers.prowlarr.tls.certresolver=letsencrypt
|
||||||
- traefik.http.routers.prowlarr.middlewares=ths-authentik@docker
|
- traefik.http.routers.prowlarr.middlewares=authentik@docker
|
||||||
- traefik.http.services.prowlarr.loadbalancer.server.port=9696
|
- traefik.http.services.prowlarr.loadbalancer.server.port=9696
|
||||||
|
|
||||||
jackett:
|
jackett:
|
||||||
@@ -61,7 +61,7 @@ services:
|
|||||||
- traefik.http.routers.jackett.entrypoints=websecure
|
- traefik.http.routers.jackett.entrypoints=websecure
|
||||||
- traefik.http.routers.jackett.tls=true
|
- traefik.http.routers.jackett.tls=true
|
||||||
- traefik.http.routers.jackett.tls.certresolver=letsencrypt
|
- traefik.http.routers.jackett.tls.certresolver=letsencrypt
|
||||||
- traefik.http.routers.jackett.middlewares=ths-authentik@docker
|
- traefik.http.routers.jackett.middlewares=authentik@docker
|
||||||
- traefik.http.services.jackett.loadbalancer.server.port=9117
|
- traefik.http.services.jackett.loadbalancer.server.port=9117
|
||||||
|
|
||||||
sonarr:
|
sonarr:
|
||||||
@@ -86,7 +86,7 @@ services:
|
|||||||
- traefik.http.routers.sonarr.entrypoints=websecure
|
- traefik.http.routers.sonarr.entrypoints=websecure
|
||||||
- traefik.http.routers.sonarr.tls=true
|
- traefik.http.routers.sonarr.tls=true
|
||||||
- traefik.http.routers.sonarr.tls.certresolver=letsencrypt
|
- traefik.http.routers.sonarr.tls.certresolver=letsencrypt
|
||||||
- traefik.http.routers.sonarr.middlewares=ths-authentik@docker
|
- traefik.http.routers.sonarr.middlewares=authentik@docker
|
||||||
- traefik.http.services.sonarr.loadbalancer.server.port=8989
|
- traefik.http.services.sonarr.loadbalancer.server.port=8989
|
||||||
|
|
||||||
radarr:
|
radarr:
|
||||||
@@ -111,7 +111,7 @@ services:
|
|||||||
- traefik.http.routers.radarr.entrypoints=websecure
|
- traefik.http.routers.radarr.entrypoints=websecure
|
||||||
- traefik.http.routers.radarr.tls=true
|
- traefik.http.routers.radarr.tls=true
|
||||||
- traefik.http.routers.radarr.tls.certresolver=letsencrypt
|
- traefik.http.routers.radarr.tls.certresolver=letsencrypt
|
||||||
- traefik.http.routers.radarr.middlewares=ths-authentik@docker
|
- traefik.http.routers.radarr.middlewares=authentik@docker
|
||||||
- traefik.http.services.radarr.loadbalancer.server.port=7878
|
- traefik.http.services.radarr.loadbalancer.server.port=7878
|
||||||
|
|
||||||
jellyseerr:
|
jellyseerr:
|
||||||
@@ -133,7 +133,7 @@ services:
|
|||||||
- traefik.http.routers.jellyseerr.entrypoints=websecure
|
- traefik.http.routers.jellyseerr.entrypoints=websecure
|
||||||
- traefik.http.routers.jellyseerr.tls=true
|
- traefik.http.routers.jellyseerr.tls=true
|
||||||
- traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt
|
- traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt
|
||||||
- traefik.http.routers.jellyseerr.middlewares=ths-authentik@docker
|
- traefik.http.routers.jellyseerr.middlewares=authentik@docker
|
||||||
- traefik.http.services.jellyseerr.loadbalancer.server.port=5055
|
- traefik.http.services.jellyseerr.loadbalancer.server.port=5055
|
||||||
|
|
||||||
# Opcional: Jellyfin en VPS (sin GPU)
|
# Opcional: Jellyfin en VPS (sin GPU)
|
||||||
@@ -161,6 +161,6 @@ services:
|
|||||||
- traefik.http.routers.jellyfin.entrypoints=websecure
|
- traefik.http.routers.jellyfin.entrypoints=websecure
|
||||||
- traefik.http.routers.jellyfin.tls=true
|
- traefik.http.routers.jellyfin.tls=true
|
||||||
- traefik.http.routers.jellyfin.tls.certresolver=letsencrypt
|
- traefik.http.routers.jellyfin.tls.certresolver=letsencrypt
|
||||||
- traefik.http.routers.jellyfin.middlewares=ths-authentik@docker
|
- traefik.http.routers.jellyfin.middlewares=authentik@docker
|
||||||
- traefik.http.services.jellyfin.loadbalancer.server.port=8096
|
- traefik.http.services.jellyfin.loadbalancer.server.port=8096
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,45 @@
|
|||||||
services:
|
services:
|
||||||
n8n:
|
n8n:
|
||||||
image: n8nio/n8n:latest
|
image: n8nio/n8n:latest
|
||||||
container_name: n8n
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
container_name: n8n
|
||||||
environment:
|
environment:
|
||||||
DB_TYPE: ${N8N_DB_TYPE}
|
- DB_TYPE=${N8N_DB_TYPE}
|
||||||
DB_POSTGRESDB_HOST: ${N8N_DB_HOST}
|
- DB_POSTGRESDB_HOST=${N8N_DB_HOST}
|
||||||
DB_POSTGRESDB_PORT: ${N8N_DB_PORT}
|
- DB_POSTGRESDB_PORT=${N8N_DB_PORT}
|
||||||
DB_POSTGRESDB_DATABASE: ${N8N_DB_NAME}
|
- DB_POSTGRESDB_DATABASE=${N8N_DB_NAME}
|
||||||
DB_POSTGRESDB_USER: ${N8N_DB_USER}
|
- DB_POSTGRESDB_USER=${N8N_DB_USER}
|
||||||
DB_POSTGRESDB_PASSWORD: ${N8N_DB_PASSWORD}
|
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
|
||||||
|
|
||||||
N8N_HOST: ${N8N_HOST}
|
- N8N_HOST=${N8N_HOST}
|
||||||
N8N_PORT: ${N8N_PORT}
|
- N8N_PORT=${N8N_PORT}
|
||||||
N8N_PROTOCOL: ${N8N_PROTOCOL}
|
- N8N_PROTOCOL=${N8N_PROTOCOL}
|
||||||
WEBHOOK_URL: ${N8N_WEBHOOK_URL}
|
- WEBHOOK_URL=${N8N_WEBHOOK_URL}
|
||||||
|
|
||||||
GENERIC_TIMEZONE: ${N8N_TIMEZONE}
|
- GENERIC_TIMEZONE=${N8N_TIMEZONE}
|
||||||
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
|
|
||||||
|
|
||||||
NODE_ENV: ${N8N_NODE_ENV}
|
# Clave para cifrar credenciales
|
||||||
N8N_DIAGNOSTICS_ENABLED: ${N8N_DIAGNOSTICS_ENABLED}
|
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
|
||||||
|
|
||||||
# Correo saliente
|
- NODE_ENV=${N8N_NODE_ENV}
|
||||||
N8N_EMAIL_MODE: ${N8N_EMAIL_MODE}
|
- N8N_DIAGNOSTICS_ENABLED=${N8N_DIAGNOSTICS_ENABLED}
|
||||||
N8N_SMTP_HOST: ${N8N_SMTP_HOST}
|
|
||||||
N8N_SMTP_PORT: ${N8N_SMTP_PORT}
|
|
||||||
N8N_SMTP_USER: ${N8N_SMTP_USER}
|
|
||||||
N8N_SMTP_PASS: ${N8N_SMTP_PASS}
|
|
||||||
N8N_SMTP_SENDER: ${N8N_SMTP_SENDER}
|
|
||||||
N8N_SMTP_SSL: ${N8N_SMTP_SSL}
|
|
||||||
N8N_SMTP_STARTTLS: ${N8N_SMTP_STARTTLS}
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- proxy
|
- proxy
|
||||||
- n8n
|
- n8n
|
||||||
- mail_internal
|
|
||||||
|
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
traefik.docker.network: "proxy"
|
traefik.docker.network: "proxy"
|
||||||
|
|
||||||
# UI (protegida por Authentik)
|
|
||||||
traefik.http.routers.n8n-ui.rule: "Host(`${N8N_DOMAIN}`)"
|
traefik.http.routers.n8n-ui.rule: "Host(`${N8N_DOMAIN}`)"
|
||||||
traefik.http.routers.n8n-ui.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
traefik.http.routers.n8n-ui.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||||
traefik.http.routers.n8n-ui.tls: "true"
|
traefik.http.routers.n8n-ui.tls: "true"
|
||||||
traefik.http.routers.n8n-ui.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
traefik.http.routers.n8n-ui.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||||
traefik.http.routers.n8n-ui.service: "n8n"
|
traefik.http.routers.n8n-ui.service: "n8n"
|
||||||
traefik.http.routers.n8n-ui.priority: "10"
|
traefik.http.routers.n8n-ui.priority: "10"
|
||||||
# traefik.http.routers.n8n-ui.middlewares: "${TRAEFIK_AUTH_MIDDLEWARE}"
|
traefik.http.routers.n8n-ui.middlewares: "${TRAEFIK_AUTH_MIDDLEWARE}"
|
||||||
|
|
||||||
# Webhooks (NO protegidos, para que terceros puedan llamar)
|
|
||||||
traefik.http.routers.n8n-webhook.rule: "Host(`${N8N_DOMAIN}`) && (PathPrefix(`/webhook`) || PathPrefix(`/webhook-test`))"
|
traefik.http.routers.n8n-webhook.rule: "Host(`${N8N_DOMAIN}`) && (PathPrefix(`/webhook`) || PathPrefix(`/webhook-test`))"
|
||||||
traefik.http.routers.n8n-webhook.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
traefik.http.routers.n8n-webhook.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||||
traefik.http.routers.n8n-webhook.tls: "true"
|
traefik.http.routers.n8n-webhook.tls: "true"
|
||||||
@@ -58,7 +47,6 @@ services:
|
|||||||
traefik.http.routers.n8n-webhook.service: "n8n"
|
traefik.http.routers.n8n-webhook.service: "n8n"
|
||||||
traefik.http.routers.n8n-webhook.priority: "20"
|
traefik.http.routers.n8n-webhook.priority: "20"
|
||||||
|
|
||||||
# Puerto interno de n8n
|
|
||||||
traefik.http.services.n8n.loadbalancer.server.port: "${N8N_PORT}"
|
traefik.http.services.n8n.loadbalancer.server.port: "${N8N_PORT}"
|
||||||
|
|
||||||
n8n-db:
|
n8n-db:
|
||||||
@@ -66,9 +54,9 @@ services:
|
|||||||
container_name: n8n-pg
|
container_name: n8n-pg
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
- POSTGRES_USER=${POSTGRES_USER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
- POSTGRES_DB=${POSTGRES_DB}
|
||||||
volumes:
|
volumes:
|
||||||
- ${N8N_DB_DATA_PATH}:/var/lib/postgresql/data:Z
|
- ${N8N_DB_DATA_PATH}:/var/lib/postgresql/data:Z
|
||||||
networks:
|
networks:
|
||||||
@@ -77,9 +65,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
proxy:
|
proxy:
|
||||||
external: true
|
external: true
|
||||||
# authentik_internal:
|
|
||||||
# driver: bridge
|
|
||||||
n8n:
|
n8n:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
mail_internal:
|
|
||||||
external: true
|
|
||||||
|
|||||||
@@ -20,16 +20,6 @@ N8N_ENCRYPTION_KEY=
|
|||||||
N8N_NODE_ENV=
|
N8N_NODE_ENV=
|
||||||
N8N_DIAGNOSTICS_ENABLED=
|
N8N_DIAGNOSTICS_ENABLED=
|
||||||
|
|
||||||
##### n8n - Correo (via mail-relay interno) #####
|
|
||||||
N8N_EMAIL_MODE=smtp
|
|
||||||
N8N_SMTP_HOST=mail-relay
|
|
||||||
N8N_SMTP_PORT=587
|
|
||||||
N8N_SMTP_USER=
|
|
||||||
N8N_SMTP_PASS=
|
|
||||||
N8N_SMTP_SENDER=n8n@thehomelesssherlock.com
|
|
||||||
N8N_SMTP_SSL=false
|
|
||||||
N8N_SMTP_STARTTLS=false
|
|
||||||
|
|
||||||
##### PostgreSQL interno #####
|
##### PostgreSQL interno #####
|
||||||
POSTGRES_USER=
|
POSTGRES_USER=
|
||||||
POSTGRES_PASSWORD=
|
POSTGRES_PASSWORD=
|
||||||
@@ -43,3 +33,4 @@ N8N_DOMAIN=
|
|||||||
TRAEFIK_ENTRYPOINT_SECURE=
|
TRAEFIK_ENTRYPOINT_SECURE=
|
||||||
TRAEFIK_CERTRESOLVER=
|
TRAEFIK_CERTRESOLVER=
|
||||||
TRAEFIK_AUTH_MIDDLEWARE=
|
TRAEFIK_AUTH_MIDDLEWARE=
|
||||||
|
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
services:
|
|
||||||
nextcloud-db:
|
|
||||||
image: mariadb:lts
|
|
||||||
container_name: nextcloud-db
|
|
||||||
restart: unless-stopped
|
|
||||||
command: >
|
|
||||||
--transaction-isolation=READ-COMMITTED
|
|
||||||
--binlog-format=ROW
|
|
||||||
--character-set-server=utf8mb4
|
|
||||||
--collation-server=utf8mb4_general_ci
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
|
||||||
MYSQL_USER: ${MYSQL_USER}
|
|
||||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
||||||
volumes:
|
|
||||||
- /opt/nextcloud/db:/var/lib/mysql:Z
|
|
||||||
networks:
|
|
||||||
- nextcloud_internal
|
|
||||||
|
|
||||||
nextcloud-redis:
|
|
||||||
image: redis:7-alpine
|
|
||||||
container_name: nextcloud-redis
|
|
||||||
restart: unless-stopped
|
|
||||||
command: redis-server --save 60 1 --loglevel warning
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
volumes:
|
|
||||||
- /opt/nextcloud/redis:/data:Z
|
|
||||||
networks:
|
|
||||||
- nextcloud_internal
|
|
||||||
|
|
||||||
nextcloud:
|
|
||||||
image: nextcloud:33-apache
|
|
||||||
container_name: nextcloud
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- nextcloud-db
|
|
||||||
- nextcloud-redis
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
MYSQL_HOST: nextcloud-db
|
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
|
||||||
MYSQL_USER: ${MYSQL_USER}
|
|
||||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
||||||
REDIS_HOST: nextcloud-redis
|
|
||||||
|
|
||||||
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
|
|
||||||
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
|
|
||||||
NEXTCLOUD_TRUSTED_DOMAINS: ${NC_DOMAIN} nextcloud localhost
|
|
||||||
|
|
||||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES}
|
|
||||||
OVERWRITEHOST: ${NC_DOMAIN}
|
|
||||||
OVERWRITEPROTOCOL: https
|
|
||||||
OVERWRITECLIURL: https://${NC_DOMAIN}
|
|
||||||
|
|
||||||
PHP_MEMORY_LIMIT: 2048M
|
|
||||||
PHP_UPLOAD_LIMIT: 16G
|
|
||||||
SMTP_HOST: ${SMTP_HOST}
|
|
||||||
SMTP_PORT: ${SMTP_PORT}
|
|
||||||
SMTP_SECURE: ${SMTP_SECURE}
|
|
||||||
SMTP_AUTHTYPE: ${SMTP_AUTHTYPE}
|
|
||||||
SMTP_NAME: ${SMTP_NAME}
|
|
||||||
SMTP_PASSWORD: ${SMTP_PASSWORD}
|
|
||||||
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
|
|
||||||
MAIL_DOMAIN: ${MAIL_DOMAIN}
|
|
||||||
volumes:
|
|
||||||
- /opt/nextcloud/html:/var/www/html:Z
|
|
||||||
- /opt/nextcloud/config:/var/www/html/config:Z
|
|
||||||
- /opt/nextcloud/data:/var/www/html/data:Z
|
|
||||||
- /opt/nextcloud/custom_apps:/var/www/html/custom_apps:Z
|
|
||||||
- /opt/nextcloud/themes:/var/www/html/themes:Z
|
|
||||||
|
|
||||||
# Opcional: exponer archivo final de Paperless en Nextcloud como solo lectura
|
|
||||||
- /opt/paperless/media:/mnt/paperless-media:ro,Z
|
|
||||||
networks:
|
|
||||||
- nextcloud_internal
|
|
||||||
- proxy
|
|
||||||
- mail_internal
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=proxy
|
|
||||||
|
|
||||||
- traefik.http.routers.nextcloud.rule=Host(`${NC_DOMAIN}`)
|
|
||||||
- traefik.http.routers.nextcloud.entrypoints=websecure
|
|
||||||
- traefik.http.routers.nextcloud.tls=true
|
|
||||||
- traefik.http.routers.nextcloud.tls.certresolver=${TRAEFIK_CERTRESOLVER}
|
|
||||||
- traefik.http.routers.nextcloud.middlewares=nc-dav,nc-secure-headers
|
|
||||||
|
|
||||||
- traefik.http.middlewares.nc-dav.redirectregex.permanent=true
|
|
||||||
- traefik.http.middlewares.nc-dav.redirectregex.regex=https://(.*)/.well-known/(?:card|cal)dav
|
|
||||||
- traefik.http.middlewares.nc-dav.redirectregex.replacement=https://$${1}/remote.php/dav
|
|
||||||
|
|
||||||
- traefik.http.middlewares.nc-secure-headers.headers.stsSeconds=31536000
|
|
||||||
- traefik.http.middlewares.nc-secure-headers.headers.stsIncludeSubdomains=true
|
|
||||||
- traefik.http.middlewares.nc-secure-headers.headers.stsPreload=true
|
|
||||||
- traefik.http.middlewares.nc-secure-headers.headers.contentTypeNosniff=true
|
|
||||||
- traefik.http.middlewares.nc-secure-headers.headers.browserXssFilter=true
|
|
||||||
|
|
||||||
- traefik.http.services.nextcloud.loadbalancer.server.port=80
|
|
||||||
|
|
||||||
nextcloud-cron:
|
|
||||||
image: nextcloud:33-apache
|
|
||||||
container_name: nextcloud-cron
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- nextcloud
|
|
||||||
entrypoint: /cron.sh
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
MYSQL_HOST: nextcloud-db
|
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
|
||||||
MYSQL_USER: ${MYSQL_USER}
|
|
||||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
|
||||||
REDIS_HOST: nextcloud-redis
|
|
||||||
SMTP_HOST: ${SMTP_HOST}
|
|
||||||
SMTP_PORT: ${SMTP_PORT}
|
|
||||||
SMTP_SECURE: ${SMTP_SECURE}
|
|
||||||
SMTP_AUTHTYPE: ${SMTP_AUTHTYPE}
|
|
||||||
SMTP_NAME: ${SMTP_NAME}
|
|
||||||
SMTP_PASSWORD: ${SMTP_PASSWORD}
|
|
||||||
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
|
|
||||||
MAIL_DOMAIN: ${MAIL_DOMAIN}
|
|
||||||
volumes:
|
|
||||||
- /opt/nextcloud/html:/var/www/html:Z
|
|
||||||
- /opt/nextcloud/config:/var/www/html/config:Z
|
|
||||||
- /opt/nextcloud/data:/var/www/html/data:Z
|
|
||||||
- /opt/nextcloud/custom_apps:/var/www/html/custom_apps:Z
|
|
||||||
- /opt/nextcloud/themes:/var/www/html/themes:Z
|
|
||||||
|
|
||||||
# Opcional: exponer archivo final de Paperless en Nextcloud como solo lectura
|
|
||||||
- /opt/paperless/media:/mnt/paperless-media:ro,Z
|
|
||||||
networks:
|
|
||||||
- nextcloud_internal
|
|
||||||
- mail_internal
|
|
||||||
|
|
||||||
onlyoffice-documentserver:
|
|
||||||
image: onlyoffice/documentserver:9.3.1
|
|
||||||
container_name: onlyoffice-documentserver
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
JWT_ENABLED: "true"
|
|
||||||
JWT_SECRET: ${OO_JWT_SECRET}
|
|
||||||
JWT_HEADER: Authorization
|
|
||||||
SECURE_LINK_SECRET: ${OO_SECURE_LINK_SECRET}
|
|
||||||
ALLOW_PRIVATE_IP_ADDRESS: "true"
|
|
||||||
volumes:
|
|
||||||
- /opt/onlyoffice/logs:/var/log/onlyoffice:Z
|
|
||||||
- /opt/onlyoffice/data:/var/www/onlyoffice/Data:Z
|
|
||||||
- /opt/onlyoffice/lib:/var/lib/onlyoffice:Z
|
|
||||||
- /opt/onlyoffice/postgresql:/var/lib/postgresql:Z
|
|
||||||
#- /opt/onlyoffice/plugins:/var/www/onlyoffice/documentserver/sdkjs-plugins:Z
|
|
||||||
networks:
|
|
||||||
- nextcloud_internal
|
|
||||||
- proxy
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=proxy
|
|
||||||
|
|
||||||
- traefik.http.routers.onlyoffice.rule=Host(`${OO_DOMAIN}`)
|
|
||||||
- traefik.http.routers.onlyoffice.entrypoints=websecure
|
|
||||||
- traefik.http.routers.onlyoffice.tls=true
|
|
||||||
- traefik.http.routers.onlyoffice.tls.certresolver=${TRAEFIK_CERTRESOLVER}
|
|
||||||
- traefik.http.routers.onlyoffice.middlewares=oo-secure-headers,oo-forwarded
|
|
||||||
|
|
||||||
- traefik.http.middlewares.oo-secure-headers.headers.stsSeconds=31536000
|
|
||||||
- traefik.http.middlewares.oo-secure-headers.headers.stsIncludeSubdomains=true
|
|
||||||
- traefik.http.middlewares.oo-secure-headers.headers.stsPreload=true
|
|
||||||
- traefik.http.middlewares.oo-secure-headers.headers.contentTypeNosniff=true
|
|
||||||
|
|
||||||
- traefik.http.middlewares.oo-forwarded.headers.customRequestHeaders.X-Forwarded-Proto=https
|
|
||||||
- traefik.http.middlewares.oo-forwarded.headers.customRequestHeaders.X-Forwarded-Host=${OO_DOMAIN}
|
|
||||||
- traefik.http.middlewares.oo-forwarded.headers.customRequestHeaders.X-Forwarded-Port=443
|
|
||||||
- traefik.http.middlewares.oo-forwarded.headers.customRequestHeaders.X-Forwarded-Ssl=on
|
|
||||||
|
|
||||||
- traefik.http.services.onlyoffice.loadbalancer.server.port=80
|
|
||||||
|
|
||||||
networks:
|
|
||||||
nextcloud_internal:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
proxy:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
mail_internal:
|
|
||||||
external: true
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
TZ=Europe/Madrid
|
|
||||||
NC_DOMAIN=nextcloud.example.com
|
|
||||||
OO_DOMAIN=onlyoffice.example.com
|
|
||||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
|
||||||
TRUSTED_PROXIES=10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
|
|
||||||
MYSQL_ROOT_PASSWORD=change_me_mysql_root_password_long_and_secure
|
|
||||||
MYSQL_DATABASE=nextcloud
|
|
||||||
MYSQL_USER=nextcloud
|
|
||||||
MYSQL_PASSWORD=change_me_nextcloud_db_password_long_and_secure
|
|
||||||
NEXTCLOUD_ADMIN_USER=admin
|
|
||||||
NEXTCLOUD_ADMIN_PASSWORD=change_me_nextcloud_admin_password_long_and_secure
|
|
||||||
OO_JWT_SECRET=change_me_onlyoffice_jwt_secret_long_and_random
|
|
||||||
OO_SECURE_LINK_SECRET=change_me_onlyoffice_secure_link_secret_long_and_random
|
|
||||||
SMTP_HOST=mail-relay
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_SECURE=tls
|
|
||||||
SMTP_AUTHTYPE=
|
|
||||||
SMTP_NAME=
|
|
||||||
SMTP_PASSWORD=
|
|
||||||
MAIL_FROM_ADDRESS=nextcloud
|
|
||||||
MAIL_DOMAIN=example.com
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
services:
|
|
||||||
paperless-db:
|
|
||||||
image: postgres:18
|
|
||||||
container_name: paperless-db
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
POSTGRES_DB: ${PAPERLESS_DBNAME}
|
|
||||||
POSTGRES_USER: ${PAPERLESS_DBUSER}
|
|
||||||
POSTGRES_PASSWORD: ${PAPERLESS_DBPASS}
|
|
||||||
volumes:
|
|
||||||
- /opt/paperless/pgdata:/var/lib/postgresql:Z
|
|
||||||
networks:
|
|
||||||
- paperless_internal
|
|
||||||
|
|
||||||
paperless-redis:
|
|
||||||
image: redis:8
|
|
||||||
container_name: paperless-redis
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /opt/paperless/redis:/data:Z
|
|
||||||
networks:
|
|
||||||
- paperless_internal
|
|
||||||
|
|
||||||
paperless-gotenberg:
|
|
||||||
image: gotenberg/gotenberg:8.27
|
|
||||||
container_name: paperless-gotenberg
|
|
||||||
restart: unless-stopped
|
|
||||||
command:
|
|
||||||
- "gotenberg"
|
|
||||||
- "--chromium-disable-javascript=true"
|
|
||||||
- "--chromium-allow-list=file:///tmp/.*"
|
|
||||||
networks:
|
|
||||||
- paperless_internal
|
|
||||||
|
|
||||||
paperless-tika:
|
|
||||||
image: apache/tika:latest
|
|
||||||
container_name: paperless-tika
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
- paperless_internal
|
|
||||||
|
|
||||||
paperless:
|
|
||||||
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
|
||||||
container_name: paperless
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- paperless-db
|
|
||||||
- paperless-redis
|
|
||||||
- paperless-gotenberg
|
|
||||||
- paperless-tika
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
|
|
||||||
PAPERLESS_REDIS: redis://paperless-redis:6379
|
|
||||||
PAPERLESS_DBHOST: paperless-db
|
|
||||||
PAPERLESS_DBENGINE: postgresql
|
|
||||||
PAPERLESS_DBNAME: ${PAPERLESS_DBNAME}
|
|
||||||
PAPERLESS_DBUSER: ${PAPERLESS_DBUSER}
|
|
||||||
PAPERLESS_DBPASS: ${PAPERLESS_DBPASS}
|
|
||||||
|
|
||||||
PAPERLESS_URL: https://${PAPERLESS_DOMAIN}
|
|
||||||
PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY}
|
|
||||||
PAPERLESS_ALLOWED_HOSTS: ${PAPERLESS_ALLOWED_HOSTS}
|
|
||||||
PAPERLESS_CSRF_TRUSTED_ORIGINS: https://${PAPERLESS_DOMAIN}
|
|
||||||
PAPERLESS_TRUSTED_PROXIES: ${TRUSTED_PROXIES}
|
|
||||||
|
|
||||||
PAPERLESS_ADMIN_USER: ${PAPERLESS_ADMIN_USER}
|
|
||||||
PAPERLESS_ADMIN_PASSWORD: ${PAPERLESS_ADMIN_PASSWORD}
|
|
||||||
PAPERLESS_ADMIN_MAIL: ${PAPERLESS_ADMIN_MAIL}
|
|
||||||
|
|
||||||
PAPERLESS_TIKA_ENABLED: 1
|
|
||||||
PAPERLESS_TIKA_ENDPOINT: http://paperless-tika:9998
|
|
||||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://paperless-gotenberg:3000
|
|
||||||
|
|
||||||
# Más robusto cuando los ficheros llegan por sync/mount y no por inotify puro
|
|
||||||
PAPERLESS_CONSUMER_POLLING: ${PAPERLESS_CONSUMER_POLLING}
|
|
||||||
volumes:
|
|
||||||
- /opt/paperless/data:/usr/src/paperless/data:Z
|
|
||||||
- /opt/paperless/media:/usr/src/paperless/media:Z
|
|
||||||
- /opt/paperless/export:/usr/src/paperless/export:Z
|
|
||||||
- /opt/paperless/consume:/usr/src/paperless/consume:Z
|
|
||||||
networks:
|
|
||||||
- paperless_internal
|
|
||||||
- proxy
|
|
||||||
- mail_internal
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=proxy
|
|
||||||
|
|
||||||
- traefik.http.routers.paperless.rule=Host(`${PAPERLESS_DOMAIN}`)
|
|
||||||
- traefik.http.routers.paperless.entrypoints=websecure
|
|
||||||
- traefik.http.routers.paperless.tls=true
|
|
||||||
- traefik.http.routers.paperless.tls.certresolver=${TRAEFIK_CERTRESOLVER}
|
|
||||||
- traefik.http.routers.paperless.middlewares=paperless-secure-headers
|
|
||||||
|
|
||||||
- traefik.http.middlewares.paperless-secure-headers.headers.stsSeconds=31536000
|
|
||||||
- traefik.http.middlewares.paperless-secure-headers.headers.stsIncludeSubdomains=true
|
|
||||||
- traefik.http.middlewares.paperless-secure-headers.headers.stsPreload=true
|
|
||||||
- traefik.http.middlewares.paperless-secure-headers.headers.contentTypeNosniff=true
|
|
||||||
- traefik.http.middlewares.paperless-secure-headers.headers.browserXssFilter=true
|
|
||||||
|
|
||||||
- traefik.http.services.paperless.loadbalancer.server.port=8000
|
|
||||||
|
|
||||||
paperless-ai:
|
|
||||||
image: clusterzx/paperless-ai:latest
|
|
||||||
container_name: paperless-ai
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- paperless
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
volumes:
|
|
||||||
- /opt/paperless-ai/data:/app/data:Z
|
|
||||||
networks:
|
|
||||||
- paperless_internal
|
|
||||||
- proxy
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.docker.network=proxy
|
|
||||||
|
|
||||||
- traefik.http.routers.paperless-ai.rule=Host(`${PAPERLESS_AI_DOMAIN}`)
|
|
||||||
- traefik.http.routers.paperless-ai.entrypoints=websecure
|
|
||||||
- traefik.http.routers.paperless-ai.tls=true
|
|
||||||
- traefik.http.routers.paperless-ai.tls.certresolver=${TRAEFIK_CERTRESOLVER}
|
|
||||||
- traefik.http.routers.paperless-ai.middlewares=paperless-ai-secure-headers
|
|
||||||
|
|
||||||
- traefik.http.middlewares.paperless-ai-secure-headers.headers.stsSeconds=31536000
|
|
||||||
- traefik.http.middlewares.paperless-ai-secure-headers.headers.stsIncludeSubdomains=true
|
|
||||||
- traefik.http.middlewares.paperless-ai-secure-headers.headers.stsPreload=true
|
|
||||||
- traefik.http.middlewares.paperless-ai-secure-headers.headers.contentTypeNosniff=true
|
|
||||||
|
|
||||||
- traefik.http.services.paperless-ai.loadbalancer.server.port=3000
|
|
||||||
|
|
||||||
# Sync unidireccional: Nextcloud/Paperless-Inbox -> paperless/consume
|
|
||||||
paperless-inbox-sync:
|
|
||||||
image: rclone/rclone:latest
|
|
||||||
container_name: paperless-inbox-sync
|
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
- paperless
|
|
||||||
entrypoint:
|
|
||||||
- /bin/sh
|
|
||||||
- /rclone-sync.sh
|
|
||||||
environment:
|
|
||||||
TZ: ${TZ}
|
|
||||||
|
|
||||||
RCLONE_CONFIG_NC_TYPE: webdav
|
|
||||||
RCLONE_CONFIG_NC_URL: https://${NC_DOMAIN}/remote.php/dav/files/${NC_WEBDAV_USER}
|
|
||||||
RCLONE_CONFIG_NC_VENDOR: nextcloud
|
|
||||||
RCLONE_CONFIG_NC_USER: ${NC_WEBDAV_USER}
|
|
||||||
RCLONE_CONFIG_NC_PASS: ${NC_WEBDAV_PASS}
|
|
||||||
|
|
||||||
RCLONE_SYNC_INTERVAL: ${RCLONE_SYNC_INTERVAL}
|
|
||||||
PAPERLESS_INBOX_DIR: ${PAPERLESS_INBOX_DIR}
|
|
||||||
volumes:
|
|
||||||
- /opt/paperless/consume:/consume:Z
|
|
||||||
- /opt/rclone:/config/rclone:Z
|
|
||||||
- /opt/paperless/rclone-sync.sh:/rclone-sync.sh:ro,Z
|
|
||||||
networks:
|
|
||||||
- paperless_internal
|
|
||||||
|
|
||||||
networks:
|
|
||||||
paperless_internal:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
proxy:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
mail_internal:
|
|
||||||
external: true
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
TZ=Europe/Madrid
|
|
||||||
NC_DOMAIN=nextcloud.example.com
|
|
||||||
PAPERLESS_DOMAIN=paperless.example.com
|
|
||||||
PAPERLESS_AI_DOMAIN=paperless-ai.example.com
|
|
||||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
|
||||||
TRUSTED_PROXIES=10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
|
|
||||||
PAPERLESS_DBNAME=paperless
|
|
||||||
PAPERLESS_DBUSER=paperless
|
|
||||||
PAPERLESS_DBPASS=change_me_paperless_db_password_long_and_secure
|
|
||||||
PAPERLESS_SECRET_KEY=change_me_paperless_secret_key_long_and_random_string
|
|
||||||
PAPERLESS_ADMIN_USER=admin
|
|
||||||
PAPERLESS_ADMIN_PASSWORD=change_me_paperless_admin_password_long_and_secure
|
|
||||||
PAPERLESS_ADMIN_MAIL=admin@example.com
|
|
||||||
PAPERLESS_CONSUMER_POLLING=60
|
|
||||||
PAPERLESS_ALLOWED_HOSTS=paperless.example.com,paperless,localhost
|
|
||||||
NC_WEBDAV_USER=paperless
|
|
||||||
NC_WEBDAV_PASS=change_me_nextcloud_webdav_password_long_and_random
|
|
||||||
PAPERLESS_INBOX_DIR=Paperless-Inbox
|
|
||||||
RCLONE_SYNC_INTERVAL=60
|
|
||||||
438
ruleta/README.md
Normal file
438
ruleta/README.md
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
# Ruleta - Aplicación Next.js
|
||||||
|
|
||||||
|
Aplicación web personalizada construida con Next.js.
|
||||||
|
|
||||||
|
## 📋 Descripción
|
||||||
|
|
||||||
|
Este stack despliega una aplicación Next.js con:
|
||||||
|
- Dos rutas de acceso: subdominio y path
|
||||||
|
- Integración con Traefik para HTTPS
|
||||||
|
- Soporte para múltiples dominios
|
||||||
|
- Configuración opcional de Authentik (SSO)
|
||||||
|
|
||||||
|
## 🚀 Despliegue
|
||||||
|
|
||||||
|
### Prerequisitos
|
||||||
|
|
||||||
|
1. **Red Docker**: Asegúrate de que la red `proxy` existe
|
||||||
|
2. **Registros DNS**: Configura los registros A para tus dominios
|
||||||
|
3. **Imagen Docker**: La aplicación debe estar construida como imagen Docker
|
||||||
|
|
||||||
|
### Desde Portainer
|
||||||
|
|
||||||
|
1. Ve a **Stacks** → **Add stack**
|
||||||
|
2. Nombre: `ruleta`
|
||||||
|
3. Selecciona **Repository** o **Git repository**
|
||||||
|
4. Configura:
|
||||||
|
- Repository URL: `<tu-repositorio>`
|
||||||
|
- Repository reference: `main`
|
||||||
|
- Compose path: `ruleta/docker-compose.yml`
|
||||||
|
5. Carga el archivo de variables de entorno: `ruleta/stack.env`
|
||||||
|
6. Haz clic en **Deploy the stack**
|
||||||
|
|
||||||
|
### Variables de Entorno
|
||||||
|
|
||||||
|
Edita el archivo `stack.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Imagen de la aplicación (construida previamente)
|
||||||
|
RULETA_IMAGE=tu-usuario/ruleta:latest
|
||||||
|
|
||||||
|
# Entorno de Node.js
|
||||||
|
RULETA_NODE_ENV=production
|
||||||
|
RULETA_NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
# Puerto interno de Next.js
|
||||||
|
RULETA_APP_PORT=3000
|
||||||
|
|
||||||
|
# Dominios
|
||||||
|
# Opción 1: Subdominio dedicado
|
||||||
|
RULETA_SUBDOMAIN=ruleta.tudominio.com
|
||||||
|
|
||||||
|
# Opción 2: Path en dominio principal
|
||||||
|
RULETA_MAIN_DOMAIN=tudominio.com
|
||||||
|
|
||||||
|
# Traefik
|
||||||
|
TRAEFIK_DOCKER_NETWORK=proxy
|
||||||
|
TRAEFIK_ENTRYPOINT_SECURE=websecure
|
||||||
|
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||||
|
|
||||||
|
# Si usas Supabase, descomenta y configura:
|
||||||
|
# NEXT_PUBLIC_SUPABASE_URL=https://tu-proyecto.supabase.co
|
||||||
|
# NEXT_PUBLIC_SUPABASE_ANON_KEY=tu-clave-publica
|
||||||
|
# SUPABASE_SERVICE_ROLE_KEY=tu-clave-privada
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Rutas de Acceso
|
||||||
|
|
||||||
|
Esta aplicación tiene dos formas de acceso configuradas:
|
||||||
|
|
||||||
|
### 1. Subdominio Dedicado
|
||||||
|
|
||||||
|
Accede mediante: `https://ruleta.tudominio.com`
|
||||||
|
|
||||||
|
- URL completa del subdominio
|
||||||
|
- Next.js recibe las peticiones en el path raíz `/`
|
||||||
|
- No requiere configuración especial en Next.js
|
||||||
|
|
||||||
|
### 2. Path en Dominio Principal
|
||||||
|
|
||||||
|
Accede mediante: `https://tudominio.com/ruleta`
|
||||||
|
|
||||||
|
- Path dentro del dominio principal
|
||||||
|
- Traefik elimina el prefijo `/ruleta` antes de enviar a Next.js
|
||||||
|
- Next.js recibe las peticiones como si estuvieran en `/`
|
||||||
|
|
||||||
|
> **Nota**: Puedes usar ambas rutas simultáneamente o deshabilitar una editando el `docker-compose.yml`.
|
||||||
|
|
||||||
|
## 🔧 Configuración de Next.js
|
||||||
|
|
||||||
|
### Configurar basePath (si usas path)
|
||||||
|
|
||||||
|
Si quieres que Next.js sea consciente del path `/ruleta`, edita `next.config.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
basePath: '/ruleta',
|
||||||
|
assetPrefix: '/ruleta',
|
||||||
|
// ... otras opciones
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Nota**: Con la configuración actual (middleware de stripprefix en Traefik), esto NO es necesario. Traefik se encarga de eliminar el prefijo.
|
||||||
|
|
||||||
|
### Variables de Entorno en Next.js
|
||||||
|
|
||||||
|
Para exponer variables al cliente (navegador):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// next.config.js
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
CUSTOM_VAR: process.env.CUSTOM_VAR,
|
||||||
|
},
|
||||||
|
// o usa NEXT_PUBLIC_ prefix
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
En `stack.env`:
|
||||||
|
```env
|
||||||
|
NEXT_PUBLIC_API_URL=https://api.tudominio.com
|
||||||
|
CUSTOM_VAR=valor-personalizado
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Proteger con Authentik (Opcional)
|
||||||
|
|
||||||
|
Por defecto, la aplicación NO está protegida con SSO. Para protegerla:
|
||||||
|
|
||||||
|
### Opción 1: Proteger Todo
|
||||||
|
|
||||||
|
Edita el `docker-compose.yml` y descomenta:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
# Para subdominio
|
||||||
|
traefik.http.routers.ruleta-sub.middlewares: "authentik@docker"
|
||||||
|
|
||||||
|
# Para path (requiere cadena de middlewares)
|
||||||
|
traefik.http.routers.ruleta-path.middlewares: "authentik@docker,ruleta-strip@docker"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opción 2: Proteger Solo Ciertas Rutas
|
||||||
|
|
||||||
|
Crea routers adicionales en Traefik:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Router para rutas públicas
|
||||||
|
traefik.http.routers.ruleta-public.rule: "Host(`ruleta.tudominio.com`) && PathPrefix(`/api`, `/public`)"
|
||||||
|
traefik.http.routers.ruleta-public.priority: "20"
|
||||||
|
|
||||||
|
# Router para rutas protegidas
|
||||||
|
traefik.http.routers.ruleta-private.rule: "Host(`ruleta.tudominio.com`) && PathPrefix(`/admin`)"
|
||||||
|
traefik.http.routers.ruleta-private.middlewares: "authentik@docker"
|
||||||
|
traefik.http.routers.ruleta-private.priority: "30"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Construir la Imagen Docker
|
||||||
|
|
||||||
|
### Dockerfile de Ejemplo
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Build stage
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Copiar archivos necesarios
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "server.js"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### next.config.js para Standalone
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: 'standalone',
|
||||||
|
// ... otras opciones
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
### Construir y Publicar
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Construir
|
||||||
|
docker build -t tu-usuario/ruleta:latest .
|
||||||
|
|
||||||
|
# Probar localmente
|
||||||
|
docker run -p 3000:3000 tu-usuario/ruleta:latest
|
||||||
|
|
||||||
|
# Publicar en Docker Hub
|
||||||
|
docker push tu-usuario/ruleta:latest
|
||||||
|
|
||||||
|
# O publicar en GitHub Container Registry
|
||||||
|
docker tag tu-usuario/ruleta:latest ghcr.io/tu-usuario/ruleta:latest
|
||||||
|
docker push ghcr.io/tu-usuario/ruleta:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Configuración Avanzada
|
||||||
|
|
||||||
|
### Integración con Supabase
|
||||||
|
|
||||||
|
Si tu aplicación usa Supabase:
|
||||||
|
|
||||||
|
1. Crea un proyecto en [supabase.com](https://supabase.com)
|
||||||
|
2. Obtén las credenciales:
|
||||||
|
- **URL del proyecto**: Settings → API → Project URL
|
||||||
|
- **Anon key**: Settings → API → anon public
|
||||||
|
- **Service role key**: Settings → API → service_role (secreto)
|
||||||
|
|
||||||
|
3. Añade a `stack.env`:
|
||||||
|
```env
|
||||||
|
NEXT_PUBLIC_SUPABASE_URL=https://tu-proyecto.supabase.co
|
||||||
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbG...
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=eyJhbG...
|
||||||
|
```
|
||||||
|
|
||||||
|
4. En tu código Next.js:
|
||||||
|
```javascript
|
||||||
|
import { createClient } from '@supabase/supabase-js'
|
||||||
|
|
||||||
|
const supabase = createClient(
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables de Entorno Sensibles
|
||||||
|
|
||||||
|
Para secrets (API keys, tokens):
|
||||||
|
|
||||||
|
1. No las incluyas en `stack.env` si el repositorio es público
|
||||||
|
2. Configúralas manualmente en Portainer:
|
||||||
|
- Edita el stack
|
||||||
|
- Ve a la pestaña **Environment variables**
|
||||||
|
- Añade variables adicionales
|
||||||
|
|
||||||
|
3. O usa Docker secrets:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
secrets:
|
||||||
|
- api_key
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
api_key:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Volúmenes para Datos Persistentes
|
||||||
|
|
||||||
|
Si necesitas persistir datos (uploads, cache):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
volumes:
|
||||||
|
- ${RULETA_DATA_PATH}:/app/data:Z
|
||||||
|
- ${RULETA_UPLOADS_PATH}:/app/public/uploads:Z
|
||||||
|
```
|
||||||
|
|
||||||
|
Añade a `stack.env`:
|
||||||
|
```env
|
||||||
|
RULETA_DATA_PATH=/opt/ruleta/data
|
||||||
|
RULETA_UPLOADS_PATH=/opt/ruleta/uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Troubleshooting
|
||||||
|
|
||||||
|
### La aplicación no inicia
|
||||||
|
|
||||||
|
1. Verifica los logs:
|
||||||
|
```bash
|
||||||
|
docker logs ruleta-app
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verifica que la imagen existe:
|
||||||
|
```bash
|
||||||
|
docker images | grep ruleta
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Verifica variables de entorno:
|
||||||
|
```bash
|
||||||
|
docker inspect ruleta-app | grep -A 20 Env
|
||||||
|
```
|
||||||
|
|
||||||
|
### No puedo acceder por dominio
|
||||||
|
|
||||||
|
1. Verifica DNS:
|
||||||
|
```bash
|
||||||
|
nslookup ruleta.tudominio.com
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verifica Traefik:
|
||||||
|
```bash
|
||||||
|
docker logs traefik | grep ruleta
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Verifica labels de Traefik:
|
||||||
|
```bash
|
||||||
|
docker inspect ruleta-app | grep -A 30 Labels
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error 502 Bad Gateway
|
||||||
|
|
||||||
|
1. Verifica que el puerto interno es correcto:
|
||||||
|
- Next.js por defecto usa puerto 3000
|
||||||
|
- Verifica `RULETA_APP_PORT=3000`
|
||||||
|
|
||||||
|
2. Verifica que la aplicación está escuchando:
|
||||||
|
```bash
|
||||||
|
docker exec ruleta-app netstat -tulpn
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Verifica conectividad desde Traefik:
|
||||||
|
```bash
|
||||||
|
docker exec traefik ping ruleta-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Las rutas no funcionan con basePath
|
||||||
|
|
||||||
|
Si configuraste `basePath` en Next.js:
|
||||||
|
|
||||||
|
1. **Opción A**: Elimina el middleware `stripprefix` de Traefik
|
||||||
|
2. **Opción B**: Elimina `basePath` de Next.js (Traefik se encarga)
|
||||||
|
|
||||||
|
### Recursos estáticos (CSS/JS) no cargan
|
||||||
|
|
||||||
|
Verifica:
|
||||||
|
1. El `assetPrefix` en `next.config.js` (debe coincidir con basePath)
|
||||||
|
2. Los logs del navegador (F12 → Console)
|
||||||
|
3. Las rutas en el inspector de red (F12 → Network)
|
||||||
|
|
||||||
|
### Variables de entorno no disponibles
|
||||||
|
|
||||||
|
Recuerda:
|
||||||
|
- Solo variables con prefijo `NEXT_PUBLIC_` están disponibles en el navegador
|
||||||
|
- Variables sin prefijo solo están disponibles en el servidor (API routes, getServerSideProps)
|
||||||
|
|
||||||
|
## 📚 Recursos Adicionales
|
||||||
|
|
||||||
|
- [Documentación de Next.js](https://nextjs.org/docs)
|
||||||
|
- [Deploying Next.js](https://nextjs.org/docs/deployment)
|
||||||
|
- [Next.js con Docker](https://github.com/vercel/next.js/tree/canary/examples/with-docker)
|
||||||
|
- [Traefik con Next.js](https://doc.traefik.io/traefik/routing/routers/)
|
||||||
|
|
||||||
|
## 🔄 Actualizaciones
|
||||||
|
|
||||||
|
### Actualizar la Aplicación
|
||||||
|
|
||||||
|
1. Construye una nueva versión de la imagen:
|
||||||
|
```bash
|
||||||
|
docker build -t tu-usuario/ruleta:v2.0 .
|
||||||
|
docker push tu-usuario/ruleta:v2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Actualiza en `stack.env`:
|
||||||
|
```env
|
||||||
|
RULETA_IMAGE=tu-usuario/ruleta:v2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Actualiza el stack en Portainer
|
||||||
|
|
||||||
|
### Rolling Updates
|
||||||
|
|
||||||
|
Para actualizaciones sin downtime, usa múltiples réplicas (requiere Docker Swarm o Kubernetes).
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
|
||||||
|
Si algo sale mal:
|
||||||
|
|
||||||
|
1. Vuelve a la versión anterior en `stack.env`
|
||||||
|
2. Actualiza el stack en Portainer
|
||||||
|
|
||||||
|
## 📊 Monitoreo
|
||||||
|
|
||||||
|
### Logs de la Aplicación
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Logs en tiempo real
|
||||||
|
docker logs -f ruleta-app
|
||||||
|
|
||||||
|
# Últimas 100 líneas
|
||||||
|
docker logs --tail 100 ruleta-app
|
||||||
|
|
||||||
|
# Logs con timestamps
|
||||||
|
docker logs -t ruleta-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs de Next.js
|
||||||
|
|
||||||
|
Next.js registra en stdout/stderr, accesibles con `docker logs`.
|
||||||
|
|
||||||
|
Para logs estructurados, considera usar:
|
||||||
|
- [Pino](https://github.com/pinojs/pino)
|
||||||
|
- [Winston](https://github.com/winstonjs/winston)
|
||||||
|
|
||||||
|
### Métricas
|
||||||
|
|
||||||
|
Para monitorear la aplicación:
|
||||||
|
1. Implementa un endpoint `/api/health`
|
||||||
|
2. Usa herramientas como Prometheus + Grafana
|
||||||
|
3. O servicios de APM como New Relic, Datadog
|
||||||
|
|
||||||
|
## 🎯 Casos de Uso
|
||||||
|
|
||||||
|
Este stack es ideal para:
|
||||||
|
- Aplicaciones Next.js personalizadas
|
||||||
|
- Landing pages
|
||||||
|
- Dashboards administrativos
|
||||||
|
- Aplicaciones full-stack con API routes
|
||||||
|
- JAMstack con SSR/SSG
|
||||||
|
|
||||||
|
## 💡 Tips
|
||||||
|
|
||||||
|
- Usa `output: 'standalone'` en Next.js para imágenes Docker más pequeñas
|
||||||
|
- Implementa caching con Redis para mejor rendimiento
|
||||||
|
- Usa ISR (Incremental Static Regeneration) para contenido dinámico
|
||||||
|
- Implementa rate limiting en API routes
|
||||||
|
- Usa CDN para assets estáticos (imágenes, CSS, JS)
|
||||||
57
ruleta/docker-compose.yml
Normal file
57
ruleta/docker-compose.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
services:
|
||||||
|
# Aplicación Next.js
|
||||||
|
app:
|
||||||
|
image: ${RULETA_IMAGE}
|
||||||
|
container_name: ruleta-app
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
environment:
|
||||||
|
NODE_ENV: ${RULETA_NODE_ENV}
|
||||||
|
NEXT_TELEMETRY_DISABLED: ${RULETA_NEXT_TELEMETRY_DISABLED}
|
||||||
|
# Si usas Supabase, descomenta y configura:
|
||||||
|
# NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL}
|
||||||
|
# NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
||||||
|
# SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
labels:
|
||||||
|
traefik.enable: "true"
|
||||||
|
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# 1) Router EXISTENTE (subdominio)
|
||||||
|
# https://ruleta.example.com
|
||||||
|
# ---------------------------
|
||||||
|
traefik.http.routers.ruleta-sub.rule: "Host(`${RULETA_SUBDOMAIN}`)"
|
||||||
|
traefik.http.routers.ruleta-sub.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||||
|
traefik.http.routers.ruleta-sub.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||||
|
traefik.http.routers.ruleta-sub.service: "ruleta"
|
||||||
|
|
||||||
|
# ---------------------------
|
||||||
|
# 2) Router NUEVO (.net + path)
|
||||||
|
# https://sherlockhomeless.net/ruleta
|
||||||
|
# ---------------------------
|
||||||
|
traefik.http.routers.ruleta-path.rule: "Host(`${RULETA_MAIN_DOMAIN}`) && PathPrefix(`/ruleta`)"
|
||||||
|
traefik.http.routers.ruleta-path.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||||
|
traefik.http.routers.ruleta-path.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||||
|
traefik.http.routers.ruleta-path.service: "ruleta"
|
||||||
|
traefik.http.routers.ruleta-path.middlewares: "ruleta-strip@docker"
|
||||||
|
|
||||||
|
# Quita /ruleta antes de llegar a Next.js
|
||||||
|
traefik.http.middlewares.ruleta-strip.stripprefix.prefixes: "/ruleta"
|
||||||
|
traefik.http.middlewares.ruleta-strip.stripprefix.forceSlash: "true"
|
||||||
|
|
||||||
|
# Servicio interno (Next.js escucha en 3000)
|
||||||
|
traefik.http.services.ruleta.loadbalancer.server.port: "${RULETA_APP_PORT}"
|
||||||
|
|
||||||
|
# Proteger con Authentik (si quieres habilitarlo)
|
||||||
|
# OJO: si lo activas, ponlo en ambos routers o usa una cadena.
|
||||||
|
# traefik.http.routers.ruleta-sub.middlewares: "authentik@docker"
|
||||||
|
# traefik.http.routers.ruleta-path.middlewares: "authentik@docker,ruleta-strip@docker"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
|
||||||
18
ruleta/stack.env
Normal file
18
ruleta/stack.env
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
##### Ruleta - App #####
|
||||||
|
RULETA_IMAGE=
|
||||||
|
RULETA_NODE_ENV=
|
||||||
|
RULETA_NEXT_TELEMETRY_DISABLED=
|
||||||
|
RULETA_APP_PORT=
|
||||||
|
|
||||||
|
# Supabase (opcional)
|
||||||
|
NEXT_PUBLIC_SUPABASE_URL=
|
||||||
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=
|
||||||
|
|
||||||
|
##### Traefik / dominios #####
|
||||||
|
TRAEFIK_DOCKER_NETWORK=
|
||||||
|
RULETA_SUBDOMAIN=
|
||||||
|
RULETA_MAIN_DOMAIN=
|
||||||
|
TRAEFIK_ENTRYPOINT_SECURE=
|
||||||
|
TRAEFIK_CERTRESOLVER=
|
||||||
|
|
||||||
@@ -21,19 +21,12 @@ services:
|
|||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
|
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
|
||||||
|
|
||||||
# Router HTTPS - dominio principal
|
# Router HTTPS (dos dominios válidos, usando OR)
|
||||||
traefik.http.routers.trilium.rule: "Host(`${TRILIUM_DOMAIN_1}`)"
|
traefik.http.routers.trilium.rule: "Host(`${TRILIUM_DOMAIN_1}`) || Host(`${TRILIUM_DOMAIN_2}`)"
|
||||||
traefik.http.routers.trilium.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
traefik.http.routers.trilium.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||||
traefik.http.routers.trilium.tls: "true"
|
traefik.http.routers.trilium.tls: "true"
|
||||||
traefik.http.routers.trilium.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
traefik.http.routers.trilium.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||||
|
|
||||||
# Router HTTPS - dominio secundario (sin redirección)
|
|
||||||
traefik.http.routers.trilium-alt.rule: "Host(`${TRILIUM_DOMAIN_2}`)"
|
|
||||||
traefik.http.routers.trilium-alt.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
|
||||||
traefik.http.routers.trilium-alt.tls: "true"
|
|
||||||
traefik.http.routers.trilium-alt.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
|
||||||
traefik.http.routers.trilium-alt.service: "trilium@docker"
|
|
||||||
|
|
||||||
# Servicio interno
|
# Servicio interno
|
||||||
traefik.http.services.trilium.loadbalancer.server.port: "${TRILIUM_HTTP_PORT}"
|
traefik.http.services.trilium.loadbalancer.server.port: "${TRILIUM_HTTP_PORT}"
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ WG_DOMAIN=vpn-admin.tudominio.com
|
|||||||
TRAEFIK_DOCKER_NETWORK=proxy
|
TRAEFIK_DOCKER_NETWORK=proxy
|
||||||
TRAEFIK_ENTRYPOINT_SECURE=websecure
|
TRAEFIK_ENTRYPOINT_SECURE=websecure
|
||||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||||
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
|
TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
|
||||||
```
|
```
|
||||||
|
|
||||||
> **⚠️ Importante**:
|
> **⚠️ Importante**:
|
||||||
|
|||||||
Reference in New Issue
Block a user