Fase 5 (migracion de datos): - api/scripts/migrate-sqlite-to-postgres.ts: ETL unico SQLite -> Postgres. Pre-flight (CURP/RFC duplicados case-insensitive, FKs huerfanas, tenant_id invalido, fechas mal formateadas) -> carga por base/esquema con credenciales _owner (setval de secuencias, OVERRIDING SYSTEM VALUE, ON CONFLICT DO NOTHING idempotente) -> verificacion de conteos y sumas de dinero con tolerancia. Probado end-to-end contra un dataset SQLite legacy sintetico y un Postgres limpio (solo schema+catalogos, sin contexto dev): 100% de las filas migradas, sumas de dinero exactas, snapshot de uploaded_by/created_by resuelto correctamente. - db/RUNBOOK-corte.md: checklist go/no-go para el corte por ambiente. - Fix de bug real encontrado al probar: sin --context-filter explicito, Liquibase corre TAMBIEN los changesets context=dev (comportamiento por defecto, no "modo seguro") -- documentado en db/README.md con el ejemplo correcto (--context-filter='!dev' para staging/produccion). Fase 5b: patron de compensacion para createTenant ya resuelto en el rewrite de saas.ts (fase 3/4). Fase 6 (backups): db/backups/ con plantillas pgBackRest por base, politica de retencion, nota de persistencia minima de Redis (RDB, sin retencion de negocio), checklist de simulacro de restauracion mensual, y la validacion pendiente de si Coolify permite WAL archiving antes de comprometerse a PITR real. Fase 7 (limpieza y CI): - Dockerfile.api simplificado (sin JRE/Liquibase/FFI). Dockerfile.migrate y Dockerfile.provision nuevos, de un solo uso, para el paso explicito de deploy (nunca sirven trafico). - docker-compose.yml: postgres+redis+provision+migrate para dev local end-to-end; api ya no arranca hasta que migrate termina bien. - docs/coolify.md y .env.example actualizados al modelo de 2 bases + Redis + Contabo. - api/schema.sql eliminado (desincronizado, competia con Liquibase como fuente de verdad). - .github/workflows/ci.yml: Postgres+Redis de servicio, aprovisiona roles/ACLs, dry-run de Liquibase (updateSQL) antes de aplicar, verify-isolation.sh, deno check + deno test, build de los dos frontends. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
7.4 KiB
Deploy PANELS en Coolify (Docker Compose + Postgres + Redis)
Resumen
| Qué | Cuánto |
|---|---|
| Postgres gestionado en Coolify | 1 instancia por ambiente, con 2 bases: panels_platform, panels_product (esquemas iam/core) |
| Redis gestionado en Coolify | 1 instancia por ambiente, con 2 usuarios ACL (panels_iam_redis, panels_core_redis) |
| Object storage | Contabo Object Storage (S3-compatible) para expedientes/PDFs/logos cifrados |
| Contenedores de la app | 3 (api, web-panel, web-saas) |
| Volumen persistente | 1 → /app/data en api (solo fallback local si no hay Contabo configurado -- no usar así en producción) |
Los fronts (Alpine + nginx) hacen proxy de /v1 al servicio api, así las cookies de sesión van same-origin.
Ver el plan de migración para el detalle de arquitectura: por qué
panels_platform es una base separada, por qué iam/core son esquemas
distintos dentro de panels_product, y las reglas del monolito modular.
Datos sensibles (qué NO va al git)
| Ítem | Estado |
|---|---|
.env |
gitignored — no commitear |
data/ (fallback local de archivos, si no hay Contabo) |
gitignored |
| SMTP password en SaaS | vive en panels_platform.smtp_settings — proteger backups |
| Defaults de desarrollo en código | SESSION_SECRET/DOCS_KEY solo tienen fallback si DENO_ENV no es production -- fuera de eso, la app falla al arrancar si faltan |
Variables de entorno (servicio api)
Obligatorias
| Variable | Formato | Uso |
|---|---|---|
SESSION_SECRET |
string largo aleatorio | Firma interna (no ya la cookie -- la sesión vive en Redis, ver Fase 4) |
DOCS_KEY |
64 caracteres hex (32 bytes) | Cifrado AES-GCM de documentos |
SEED_PASSWORD |
string | Password inicial usado por api/scripts/bootstrap-admin.ts |
PANEL_LOGIN_URL |
URL absoluta | Link en correos de acceso |
DATABASE_URL_PLATFORM |
postgresql://panels_platform_app:...@host:5432/panels_platform |
Runtime, rol _app |
DATABASE_URL_IAM |
postgresql://panels_iam_app:...@host:5432/panels_product |
Runtime, rol _app, esquema iam |
DATABASE_URL_CORE |
postgresql://panels_core_app:...@host:5432/panels_product |
Runtime, rol _app, esquema core |
DATABASE_URL_IAM_OWNER |
igual, rol _owner |
Solo para el lookup de login por username (bypassa RLS a propósito, ver api/iam_db.ts) |
DATABASE_URL_CORE_OWNER |
igual, rol _owner |
Solo para resoluciones administrativas puntuales (ver api/db.ts#getCoreDb) |
REDIS_URL_IAM |
redis://panels_iam_redis:...@host:6379 |
Sesiones |
REDIS_URL_CORE |
redis://panels_core_redis:...@host:6379 |
Cache |
Generar secretos:
openssl rand -hex 32 # SESSION_SECRET o DOCS_KEY
openssl rand -hex 24 # passwords de roles Postgres/Redis
Recomendadas (HTTPS / Coolify)
| Variable | Default compose | Uso |
|---|---|---|
COOKIE_SECURE |
true |
Cookie solo por HTTPS |
PORT |
8000 |
Puerto interno API |
CORS_ORIGINS |
vacío | Lista https://a,https://b si la API se llama cross-origin; con proxy /v1 en nginx no hace falta |
Opcionales
| Variable | Uso |
|---|---|
API_KEY |
Auth alternativa por header X-API-Key + X-Tenant-Id obligatorio (ya no ve todos los tenants, ver revisión de seguridad) |
VCARD_BASE |
Prefijo QR/vCard gafetes |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM |
Correo por env (alternativa al panel /smtp) |
S3_ENDPOINT / S3_BUCKET / S3_REGION / S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY |
Contabo Object Storage (Fase 4c). Sin esto, cae a disco local -- no recomendado en producción |
web-panel y web-saas no necesitan variables de entorno en runtime (estáticos + proxy nginx).
Imágenes
- api: Deno 2.9, sin FFI ni JRE (Fase 2/7 del plan de migración).
- migrate: JRE 21 + Liquibase -- imagen aparte, de un solo uso, NO sirve tráfico (ver
Dockerfile.migrate). - web-panel / web-saas: build Node Alpine → nginx Alpine.
Archivos: Dockerfile.api, Dockerfile.migrate, Dockerfile.provision, Dockerfile.web-panel, Dockerfile.web-saas, docker-compose.yml, web-panel/nginx.conf, web-saas/nginx.conf.
En Coolify el servicio de PANELS se llama web-panel (FQDN ej. panels.mrdev.mx). La carpeta web/ queda libre para una website futura.
Paso a paso en Coolify
- Provisionar Postgres y Redis como recursos gestionados de Coolify para el ambiente (uno de cada, no por módulo).
- Aprovisionar roles/esquemas/ACLs: correr los scripts de
db/provision/contra ese Postgres/Redis (una vez, desde tu máquina o un job manual -- Coolify no lo hace por ti). - Aplicar Liquibase (paso explícito, NO ocurre al arrancar la app):
./db/update.sh all --context-filter='!dev'con las credenciales_owner. En staging/producción, nunca olvidar el--context-filter-- sin él, Liquibase corre TAMBIÉN los changesets de demo (context=dev). - Bootstrap del primer admin:
deno run ... api/scripts/bootstrap-admin.ts platformy... tenant --tenant-id=... --company-code=...(verdb/README.md). - Push este repo (sin
.envnidata/). - Coolify → New Resource → Docker Compose →
docker-compose.yml(solo paraapi/web-panel/web-saas-- si Postgres/Redis ya son recursos gestionados aparte, quitar esos servicios del compose antes de desplegar, o apuntar sus variables a los recursos gestionados en vez de los contenedores locales del compose). - Persistent storage: volumen
panel-data→/app/dataenapi(solo fallback de archivos si no hay Contabo). - Dominios:
web-panel→ panels;web-saas→ saas;apisin FQDN (proxy/v1). - Cargar en Coolify todas las variables obligatorias de la tabla de arriba +
COOKIE_SECURE=true. - Deploy.
- Verificar
https://app…/v1/health→ debe responder{"ok":true,"core":true,"platform":true,"redis":{"iam":true,"core":true}}. Si algo esfalse, la app ni siquiera debería haber arrancado (fail-fast, Fase 7). - Login SaaS
admin/ tuSEED_PASSWORD. - SMTP en
/smtpo porSMTP_*.
Local (todo en docker-compose, incluyendo Postgres/Redis propios)
cp .env.example .env # rellenar TODAS las variables (incluye los passwords de roles Postgres/Redis)
docker compose up --build
El compose local incluye: provision (roles/esquemas/ACLs, un solo uso) →
migrate (Liquibase con datos de demo, un solo uso) → api/web-panel/web-saas.
Para producción, provision/migrate NO se corren así -- ver
db/provision/README.md y db/RUNBOOK-corte.md.
Sin las variables obligatorias, compose no arranca (:? en docker-compose.yml).
Checklist ops
.envydata/fuera del git- Secrets distintos a los de desarrollo (incluye los 6 roles Postgres + 2 Redis)
- Backup de
panels_platformYpanels_product(verdb/backups/) -- no solo una - Backup del bucket de Contabo (documentos cifrados)
COOKIE_SECURE=trueen HTTPS- Healthcheck API OK (
/v1/healthcon las 5 conexiones entrue) --context-filterexplícito en cada corrida de Liquibase contra staging/producción
Qué no hace falta
- Contenedor monolítico Node+Deno+Java sirviendo tráfico (Liquibase vive en su propia imagen, de un solo uso)
- FFI de SQLite (ya no hay SQLite en ningún ambiente)