13 Commits

Author SHA1 Message Date
Eduardo David Paredes Vara
1698de3738 delete old ruleta bot 2026-02-17 10:49:01 +00:00
Eduardo David Paredes Vara
f22842052a portainer wrn fix 2026-02-17 09:16:59 +00:00
Eduardo David Paredes Vara
f29208cfa2 middelware fix 2026-02-17 08:57:58 +00:00
Eduardo David Paredes Vara
f8cd4c2df1 ak update 2026-02-17 08:36:30 +00:00
Eduardo David Paredes Vara
db807dcf6f authentik update 2026-02-15 16:40:17 +00:00
Eduardo David Paredes Vara
02b9685804 authentik update 2026-02-15 16:38:12 +00:00
8246bff8a1 Refactor Traefik configuration for Authentik
Updated Traefik router rules and added new callback for www.thehomelesssherlock.com.
2026-02-15 16:27:18 +00:00
Eduardo David Paredes Vara
43c24b4b86 media server 2026-02-15 16:27:18 +00:00
Eduardo David Paredes Vara
43d10ea7cf media server 2026-02-15 16:27:18 +00:00
Eduardo David Paredes Vara
becce96ede media-server 2026-02-15 16:27:18 +00:00
Eduardo David Paredes Vara
a97c88454f Update docker-compose.yml 2025-12-22 16:27:54 +01:00
Eduardo David Paredes Vara
d05d783244 Update Traefik router rule for authentik outpost 2025-12-18 11:27:18 +01:00
Eduardo David Paredes Vara
35bb14028f Refactor Traefik configuration for Authentik
Updated Traefik router rules and added new callback for www.thehomelesssherlock.com.
2025-12-18 10:50:07 +01:00
14 changed files with 105 additions and 622 deletions

2
.env
View File

@@ -19,7 +19,7 @@
# TRAEFIK_CERTRESOLVER=letsencrypt
# Middleware de autenticación (SSO, etc.)
# TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
# TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
# Dominios de ejemplo (cámbialos por los tuyos)
# PORTAINER_DOMAIN=portainer.example.com

View File

@@ -165,7 +165,7 @@ Variables principales a configurar:
- `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_IP_WHITELIST`: IPs permitidas para acceso directo a la API
- `TRAEFIK_AUTH_MIDDLEWARE`: Middleware de autenticación (ej: `authentik@docker`)
- `TRAEFIK_AUTH_MIDDLEWARE`: Middleware de autenticación (ej: `ths-authentik@docker`)
### Paso 10: Actualizar Stack de Portainer (Opcional)
@@ -250,7 +250,7 @@ PORTAINER_API_DOMAIN=portainer-api.example.com
# Seguridad
PORTAINER_API_IP_WHITELIST=10.8.0.0/24,172.18.0.1/32
TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
```
### Configuraciones por Stack

View File

@@ -121,7 +121,7 @@ labels:
traefik.http.routers.dashboard.entrypoints: "websecure"
traefik.http.routers.dashboard.tls.certresolver: "letsencrypt"
traefik.http.routers.dashboard.service: "api@internal"
traefik.http.routers.dashboard.middlewares: "authentik@docker"
traefik.http.routers.dashboard.middlewares: "ths-authentik@docker"
```
**Opción 2: Acceso local (inseguro - solo desarrollo)**
@@ -165,7 +165,7 @@ services:
traefik.http.services.mi-servicio.loadbalancer.server.port: "80"
# Middleware (opcional)
traefik.http.routers.mi-servicio.middlewares: "authentik@docker"
traefik.http.routers.mi-servicio.middlewares: "ths-authentik@docker"
networks:
proxy:
@@ -180,7 +180,7 @@ labels:
traefik.http.routers.app-ui.rule: "Host(`app.tudominio.com`)"
traefik.http.routers.app-ui.entrypoints: "websecure"
traefik.http.routers.app-ui.tls.certresolver: "letsencrypt"
traefik.http.routers.app-ui.middlewares: "authentik@docker"
traefik.http.routers.app-ui.middlewares: "ths-authentik@docker"
traefik.http.routers.app-ui.priority: "10"
# API pública sin protección

View File

@@ -89,7 +89,7 @@ ADGUARD_CERT_KEY_PATH=/opt/adguard/certs/adguard.key
TRAEFIK_DOCKER_NETWORK=proxy
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
```
## ⚙️ Configuración Post-Instalación

View File

@@ -203,7 +203,7 @@ Una vez configurado el middleware, añade la label a los servicios que quieras p
```yaml
labels:
traefik.http.routers.portainer.middlewares: "authentik@docker"
traefik.http.routers.portainer.middlewares: "ths-authentik@docker"
```
O si definiste el middleware en archivo:

View File

@@ -1,7 +1,7 @@
services:
authentik-postgres:
ths-authentik-postgres:
image: ${AUTHENTIK_POSTGRES_IMAGE}
container_name: authentik-postgres
container_name: ths-authentik-postgres
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD}
@@ -10,95 +10,103 @@ services:
volumes:
- ${AUTHENTIK_POSTGRES_PATH}:/var/lib/postgresql/data:Z
networks:
- authentik_internal
- ths_authentik_internal
authentik-redis:
ths-authentik-redis:
image: ${AUTHENTIK_REDIS_IMAGE}
container_name: authentik-redis
container_name: ths-authentik-redis
restart: unless-stopped
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
volumes:
- ${AUTHENTIK_REDIS_PATH}:/data:Z
networks:
- authentik_internal
- ths_authentik_internal
authentik-server:
ths-authentik-server:
image: ${AUTHENTIK_IMAGE}
container_name: authentik-server
container_name: ths-authentik-server
restart: unless-stopped
command: ["server"]
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_DB_HOST}
# OJO: forzamos hosts internos para evitar colisiones y depender del .env
AUTHENTIK_POSTGRESQL__HOST: ths-authentik-postgres
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_REDIS__HOST: ${AUTHENTIK_REDIS_HOST}
AUTHENTIK_REDIS__HOST: ths-authentik-redis
# Bootstrap inicial (primera vez)
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN}
depends_on:
- authentik-postgres
- authentik-redis
- ths-authentik-postgres
- ths-authentik-redis
expose:
- "${AUTHENTIK_HTTP_PORT}"
networks:
- authentik_internal
- ths_authentik_internal
- proxy
labels:
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
# Router del panel de Authentik
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}"
# Service Authentik (panel + endpoints)
traefik.http.services.ths-authentik.loadbalancer.server.port: "${AUTHENTIK_HTTP_PORT}"
# Middleware de forwardAuth que usaremos en Portainer, Pi-hole, etc.
traefik.http.middlewares.authentik.forwardauth.address: "http://authentik-server:${AUTHENTIK_HTTP_PORT}/outpost.goauthentik.io/auth/traefik"
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: "true"
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Uid,X-Authentik-Jwt"
# Panel Authentik (auth.thehomelesssherlock.com)
traefik.http.routers.ths-authentik.rule: "Host(`${AUTHENTIK_DOMAIN}`)"
traefik.http.routers.ths-authentik.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.ths-authentik.tls: "true"
traefik.http.routers.ths-authentik.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
traefik.http.routers.ths-authentik.service: "ths-authentik"
# Callback del outpost en gitea hacia Authentik
traefik.http.routers.authentik-outpost-gitea.rule: "Host(`${GITEA_DOMAIN}`) && PathPrefix(`/outpost.goauthentik.io/`)"
traefik.http.routers.authentik-outpost-gitea.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
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"
# Middleware forwardAuth (para proteger otros servicios) -> usar ths-ths-authentik@docker en tus stacks THS
traefik.http.middlewares.ths-authentik.forwardauth.address: "http://ths-authentik-server:${AUTHENTIK_HTTP_PORT}/outpost.goauthentik.io/auth/traefik"
traefik.http.middlewares.ths-authentik.forwardauth.trustForwardHeader: "true"
traefik.http.middlewares.ths-authentik.forwardauth.authResponseHeaders: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Uid,X-Authentik-Jwt"
authentik-worker:
# OUTPOST genérico para TODO el dominio THS (subdominios + apex + www)
# ✅ 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}
container_name: authentik-worker
container_name: ths-authentik-worker
restart: unless-stopped
command: ["worker"]
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_DB_HOST}
# OJO: forzamos hosts internos igual que en server
AUTHENTIK_POSTGRESQL__HOST: ths-authentik-postgres
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER}
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME}
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
AUTHENTIK_REDIS__HOST: ${AUTHENTIK_REDIS_HOST}
AUTHENTIK_REDIS__HOST: ths-authentik-redis
depends_on:
- authentik-postgres
- authentik-redis
- ths-authentik-postgres
- ths-authentik-redis
networks:
- authentik_internal
- ths_authentik_internal
networks:
proxy:
external: true
authentik_internal:
ths_authentik_internal:
driver: bridge

View File

@@ -1,24 +1,15 @@
services:
portainer:
image: ${PORTAINER_IMAGE:-portainer/portainer-ce:latest}
image: portainer/portainer-ee:2.33.7
container_name: portainer
restart: unless-stopped
env_file:
- .env
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í)
- /opt/portainer/secrets/portainer:/run/secrets/portainer:ro,Z
- /opt/portainer/secrets/portainer:/run/portainer/portainer:ro,Z
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/portainer/data:/data:Z
# Datos de Portainer (DB cifrada incluida)
- ${PORTAINER_DATA_PATH:-/opt/portainer/data}:/data:Z
# SELinux: evita bloqueos con docker.sock
security_opt:
- label=disable
@@ -26,28 +17,24 @@ services:
- proxy
labels:
traefik.enable: "true"
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK:-proxy}"
- "traefik.enable=true"
- "traefik.docker.network=proxy"
############################
# 1) UI protegida (ej: SSO)
############################
traefik.http.routers.portainer.rule: "Host(`${PORTAINER_DOMAIN:-portainer.example.com}`)"
traefik.http.routers.portainer.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE:-websecure}"
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}"
# 1) UI protegida Authentik
- "traefik.http.routers.portainer.rule=Host(`portainer.thehomelesssherlock.com`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
- "traefik.http.routers.portainer.middlewares=ths-authentik@docker"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
#########################################################
# 2) API/App móvil SIN SSO, restringida por IP (ej: VPN)
#########################################################
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.rule: "Host(`${PORTAINER_API_DOMAIN:-portainer-api.example.com}`)"
traefik.http.routers.portainer-direct.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE:-websecure}"
traefik.http.routers.portainer-direct.tls.certresolver: "${TRAEFIK_CERTRESOLVER:-letsencrypt}"
traefik.http.routers.portainer-direct.middlewares: "portainer-api-ip"
traefik.http.routers.portainer-direct.service: "portainer"
traefik.http.routers.portainer-direct.priority: "100"
# 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"
- "traefik.http.routers.portainer-direct.rule=Host(`portainer-api.thehomelesssherlock.com`)"
- "traefik.http.routers.portainer-direct.entrypoints=websecure"
- "traefik.http.routers.portainer-direct.tls.certresolver=letsencrypt"
- "traefik.http.routers.portainer-direct.middlewares=portainer-api-ip"
- "traefik.http.routers.portainer-direct.service=portainer"
- "traefik.http.routers.portainer-direct.priority=100"
networks:
proxy:

View File

@@ -81,7 +81,7 @@ TRAEFIK_ENABLE=true
TRAEFIK_ENTRYPOINTS=websecure
TRAEFIK_TLS=true
TRAEFIK_CERTRESOLVER=letsencrypt
AUTH_MIDDLEWARE=authentik@docker
AUTH_MIDDLEWARE=ths-authentik@docker
# Dominios - Personaliza según tu dominio
DOMAIN=tudominio.com
@@ -188,7 +188,7 @@ En **Sonarr** y **Radarr**:
### 8. Integración con Authentik (SSO)
Todos los servicios están protegidos con Authentik por defecto mediante el middleware `authentik@docker`.
Todos los servicios están protegidos con Authentik por defecto mediante el middleware `ths-authentik@docker`.
Para personalizar el acceso:

View File

@@ -38,7 +38,7 @@ services:
- traefik.http.routers.prowlarr.entrypoints=websecure
- traefik.http.routers.prowlarr.tls=true
- traefik.http.routers.prowlarr.tls.certresolver=letsencrypt
- traefik.http.routers.prowlarr.middlewares=authentik@docker
- traefik.http.routers.prowlarr.middlewares=ths-authentik@docker
- traefik.http.services.prowlarr.loadbalancer.server.port=9696
jackett:
@@ -61,7 +61,7 @@ services:
- traefik.http.routers.jackett.entrypoints=websecure
- traefik.http.routers.jackett.tls=true
- traefik.http.routers.jackett.tls.certresolver=letsencrypt
- traefik.http.routers.jackett.middlewares=authentik@docker
- traefik.http.routers.jackett.middlewares=ths-authentik@docker
- traefik.http.services.jackett.loadbalancer.server.port=9117
sonarr:
@@ -86,7 +86,7 @@ services:
- traefik.http.routers.sonarr.entrypoints=websecure
- traefik.http.routers.sonarr.tls=true
- traefik.http.routers.sonarr.tls.certresolver=letsencrypt
- traefik.http.routers.sonarr.middlewares=authentik@docker
- traefik.http.routers.sonarr.middlewares=ths-authentik@docker
- traefik.http.services.sonarr.loadbalancer.server.port=8989
radarr:
@@ -111,7 +111,7 @@ services:
- traefik.http.routers.radarr.entrypoints=websecure
- traefik.http.routers.radarr.tls=true
- traefik.http.routers.radarr.tls.certresolver=letsencrypt
- traefik.http.routers.radarr.middlewares=authentik@docker
- traefik.http.routers.radarr.middlewares=ths-authentik@docker
- traefik.http.services.radarr.loadbalancer.server.port=7878
jellyseerr:
@@ -133,7 +133,7 @@ services:
- traefik.http.routers.jellyseerr.entrypoints=websecure
- traefik.http.routers.jellyseerr.tls=true
- traefik.http.routers.jellyseerr.tls.certresolver=letsencrypt
- traefik.http.routers.jellyseerr.middlewares=authentik@docker
- traefik.http.routers.jellyseerr.middlewares=ths-authentik@docker
- traefik.http.services.jellyseerr.loadbalancer.server.port=5055
# Opcional: Jellyfin en VPS (sin GPU)
@@ -161,6 +161,6 @@ services:
- traefik.http.routers.jellyfin.entrypoints=websecure
- traefik.http.routers.jellyfin.tls=true
- traefik.http.routers.jellyfin.tls.certresolver=letsencrypt
- traefik.http.routers.jellyfin.middlewares=authentik@docker
- traefik.http.routers.jellyfin.middlewares=ths-authentik@docker
- traefik.http.services.jellyfin.loadbalancer.server.port=8096

View File

@@ -1,28 +1,26 @@
services:
n8n:
image: n8nio/n8n:latest
restart: unless-stopped
container_name: n8n
restart: unless-stopped
environment:
- DB_TYPE=${N8N_DB_TYPE}
- DB_POSTGRESDB_HOST=${N8N_DB_HOST}
- DB_POSTGRESDB_PORT=${N8N_DB_PORT}
- DB_POSTGRESDB_DATABASE=${N8N_DB_NAME}
- DB_POSTGRESDB_USER=${N8N_DB_USER}
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
DB_TYPE: ${N8N_DB_TYPE}
DB_POSTGRESDB_HOST: ${N8N_DB_HOST}
DB_POSTGRESDB_PORT: ${N8N_DB_PORT}
DB_POSTGRESDB_DATABASE: ${N8N_DB_NAME}
DB_POSTGRESDB_USER: ${N8N_DB_USER}
DB_POSTGRESDB_PASSWORD: ${N8N_DB_PASSWORD}
- N8N_HOST=${N8N_HOST}
- N8N_PORT=${N8N_PORT}
- N8N_PROTOCOL=${N8N_PROTOCOL}
- WEBHOOK_URL=${N8N_WEBHOOK_URL}
N8N_HOST: ${N8N_HOST}
N8N_PORT: ${N8N_PORT}
N8N_PROTOCOL: ${N8N_PROTOCOL}
WEBHOOK_URL: ${N8N_WEBHOOK_URL}
- GENERIC_TIMEZONE=${N8N_TIMEZONE}
GENERIC_TIMEZONE: ${N8N_TIMEZONE}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
# Clave para cifrar credenciales
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- NODE_ENV=${N8N_NODE_ENV}
- N8N_DIAGNOSTICS_ENABLED=${N8N_DIAGNOSTICS_ENABLED}
NODE_ENV: ${N8N_NODE_ENV}
N8N_DIAGNOSTICS_ENABLED: ${N8N_DIAGNOSTICS_ENABLED}
networks:
- proxy
@@ -32,6 +30,7 @@ services:
traefik.enable: "true"
traefik.docker.network: "proxy"
# UI (protegida por Authentik)
traefik.http.routers.n8n-ui.rule: "Host(`${N8N_DOMAIN}`)"
traefik.http.routers.n8n-ui.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.n8n-ui.tls: "true"
@@ -40,6 +39,7 @@ services:
traefik.http.routers.n8n-ui.priority: "10"
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.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
traefik.http.routers.n8n-webhook.tls: "true"
@@ -47,6 +47,7 @@ services:
traefik.http.routers.n8n-webhook.service: "n8n"
traefik.http.routers.n8n-webhook.priority: "20"
# Puerto interno de n8n
traefik.http.services.n8n.loadbalancer.server.port: "${N8N_PORT}"
n8n-db:
@@ -54,9 +55,9 @@ services:
container_name: n8n-pg
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ${N8N_DB_DATA_PATH}:/var/lib/postgresql/data:Z
networks:
@@ -65,7 +66,7 @@ services:
networks:
proxy:
external: true
authentik_internal:
driver: bridge
n8n:
driver: bridge

View File

@@ -1,438 +0,0 @@
# 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)

View File

@@ -1,57 +0,0 @@
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

View File

@@ -1,18 +0,0 @@
##### 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=

View File

@@ -83,7 +83,7 @@ WG_DOMAIN=vpn-admin.tudominio.com
TRAEFIK_DOCKER_NETWORK=proxy
TRAEFIK_ENTRYPOINT_SECURE=websecure
TRAEFIK_CERTRESOLVER=letsencrypt
TRAEFIK_AUTH_MIDDLEWARE=authentik@docker
TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker
```
> **⚠️ Importante**: