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
- En Dokploy: Create Project →
academico-<colegio>. - Dentro del proyecto: Create Service → Database → PostgreSQL 17.
- Anote el nombre interno del servicio (ej.
academico-db), el usuario, la contraseña y la base — con eso se arma elDATABASE_URL.
2. Servicio acad-core
- Create Service → Application → Provider: GitHub → repo
acad-core, branchmain, 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 usuarioadminy 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→ puerto80, 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→ puerto80+ Let's Encrypt.
6. Orden del primer despliegue
- PostgreSQL (esperar a que quede healthy).
acad-core→ revisar logs: debe decirAll migrations have been successfully appliedycuenta admin inicial creada.acad-bff→ probar desde el core: el log no debe mostrar errores 502.acad-web→ abrir el dominio e ingresar conadminy laBOOTSTRAP_ADMIN_PASSWORD(el sistema exige cambiarla).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.shdel repoacad-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.