Saltar a contenido

Despliegue en Dokploy (multi-repo)

El sistema se despliega como un proyecto de Dokploy con cuatro servicios independientes (cada uno con su repositorio) más la base de datos. Cada servicio se construye y actualiza por separado: un cambio en el frontend no reconstruye el core.

Repositorios

Servicio Repositorio Imagen Puerto Público
Core académico acad-core Dockerfile propio 3001 ❌ solo red interna
BFF acad-bff Dockerfile propio 3000 ❌ solo red interna
Panel web (SPA + proxy) acad-web nginx 80 ✅ dominio del colegio
Documentación acad-docs nginx (MkDocs) 80 ✅ dominio de docs
PostgreSQL servicio de Dokploy 5432 ❌ solo red interna

Solo el panel web se expone a internet

El BFF no necesita dominio propio: el nginx del panel web hace proxy /api → BFF por la red interna. Así la sesión sigue siendo same-origin (cookies httpOnly con SameSite=strict, sin CORS) y el core queda a dos saltos de internet.

flowchart LR
    U[Navegador] -->|https://colegio.edu.ec| W[acad-web<br/>nginx + SPA]
    D[Navegador] -->|https://docs.colegio.edu.ec| DOC[acad-docs]
    W -->|/api/* red interna| B[acad-bff :3000]
    B -->|red interna| C[acad-core :3001]
    C --> P[(PostgreSQL)]

1. Crear el proyecto y la base

  1. En Dokploy: Create Projectacademico-<colegio>.
  2. Dentro del proyecto: Create Service → Database → PostgreSQL 17.
  3. Anote el nombre interno del servicio (ej. academico-db), el usuario, la contraseña y la base — con eso se arma el DATABASE_URL.

2. Servicio acad-core

  • Create Service → Application → Provider: GitHub → repo acad-core, branch main, Build Type: Dockerfile.
  • Sin dominio (no publicar).
  • Variables de entorno:
# ── Obligatorias ──────────────────────────────────────────────
DATABASE_URL=postgresql://USUARIO:CLAVE@academico-db:5432/academic_db
# El arranque ABORTA si falta o mide menos de 32 caracteres.
# Generar con: openssl rand -base64 48
JWT_ACCESS_SECRET=<48 bytes aleatorios en base64>

# ── Recomendadas ──────────────────────────────────────────────
NODE_ENV=production
PORT=3001
FILES_DIR=/data/files
# Clave del usuario `admin` del primer arranque (se cambia al entrar).
# Si se omite, el core genera una y la imprime una vez en el log.
BOOTSTRAP_ADMIN_PASSWORD=<clave temporal>

# ── Opcionales ────────────────────────────────────────────────
JWT_ACCESS_TTL=900          # sesión: 15 min
JWT_REFRESH_TTL=1209600     # refresh: 14 días
SMTP_URL=                   # email real (sin esto: modo stub)
SMTP_FROM=notificaciones@colegio.edu.ec
FCM_CREDENTIALS_PATH=       # push real (service account de Firebase)
SENTRY_DSN=                 # monitoreo de errores
LOG_LEVEL=info

No existe JWT_REFRESH_SECRET

El refresh token no es un JWT: es un valor aleatorio opaco cuyo hash se guarda en la base (por eso se puede revocar). Si vio esa variable en documentación antigua, ignórela — no se usa.

Secretos débiles

Con NODE_ENV=production, el core se niega a arrancar si JWT_ACCESS_SECRET falta o es corto, y lo dice en el log. Es deliberado: un secreto conocido permitiría a cualquiera firmar tokens de administrador.

  • Volumen persistente: montar /data/files (documentos de matrícula, adjuntos y boletas generadas).
  • Las migraciones corren solas en cada arranque (prisma migrate deploy). En el primer despliegue con base vacía, el core crea el usuario admin y lo registra en el log.

3. Servicio acad-bff

  • Application → repo acad-bff, Dockerfile. Sin dominio.
CORE_URL=http://acad-core:3001    # nombre interno del servicio del core
PORT=3000
NODE_ENV=production

4. Servicio acad-web (el único con dominio)

  • Application → repo acad-web, Dockerfile.
  • Domain: colegio.edu.ec → puerto 80, con Let's Encrypt activado.
BFF_URL=http://acad-bff:3000      # nombre interno del servicio del BFF

Nombres internos

BFF_URL y CORE_URL usan el nombre del servicio en Dokploy, no un dominio. Si nombró los servicios distinto (ej. bff-colegio), use ese nombre. El entrypoint del contenedor imprime en el log a dónde está proxeando: [web] proxy /api → http://acad-bff:3000.

5. Servicio acad-docs

  • Application → repo acad-docs, Dockerfile.
  • Domain: docs.colegio.edu.ec → puerto 80 + Let's Encrypt.

6. Orden del primer despliegue

  1. PostgreSQL (esperar a que quede healthy).
  2. acad-core → revisar logs: debe decir All migrations have been successfully applied y cuenta admin inicial creada.
  3. acad-bff → probar desde el core: el log no debe mostrar errores 502.
  4. acad-web → abrir el dominio e ingresar con admin y la BOOTSTRAP_ADMIN_PASSWORD (el sistema exige cambiarla).
  5. acad-docs.

7. Actualizaciones

Cada repo se actualiza solo: push a main → Deploy en su servicio (o webhook automático). El core corre sus migraciones pendientes al reiniciar; el resto son despliegues sin estado.

Antes de una migración grande

Ejecute un respaldo manual desde Dokploy (o el backup programado) y valide el restore drill — ver Arquitectura.

8. Respaldos

Dokploy permite programar backups del servicio PostgreSQL hacia un destino externo (S3 compatible). Configure:

  • Frecuencia: diaria.
  • Retención: 30 días + conservar manualmente los de cierre de período.
  • Restore drill: mensual, restaurando a una base de verificación y comparando conteos (script scripts/restore.sh del repo acad-core).

9. Verificación post-despliegue

# Salud del panel (a través del proxy interno)
curl -s https://colegio.edu.ec/api/health          # {"status":"ok"}

# Login del administrador
curl -s -X POST https://colegio.edu.ec/api/auth/login \
  -H 'content-type: application/json' \
  -d '{"username":"admin","password":"<BOOTSTRAP_ADMIN_PASSWORD>"}'

Si /api/health responde pero el login falla con 502, revise BFF_URL en acad-web y CORE_URL en acad-bff: casi siempre es un nombre de servicio distinto al esperado.