panels-origin/docs/coolify.md
Cursor Agent c2ea43818c
api: compatibilidad S3 con Cloudflare R2 (reemplazo de Contabo)
El SDK de AWS v3 manda checksums CRC32 que R2 no acepta; se calculan
solo cuando el API lo exige. Docs y .env.example apuntan a R2.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-03 04:40:54 +00:00

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 Cloudflare R2 (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 Cloudflare R2. Endpoint https://<ACCOUNT_ID>.r2.cloudflarestorage.com, S3_REGION=auto. 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

  1. Provisionar Postgres, Redis y bucket — 1 de cada por ambiente (no por módulo; no compartir prod con staging).
  2. Crear accesos y validar: ./db/provision/create-accesses.sh --apply --verify --out .env.<env>.local contra esos hosts (ver db/provision/README.md). Coolify no crea los roles panels_* ni los ACL de Redis solo.
  3. 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).
  4. Bootstrap del primer admin: deno run ... api/scripts/bootstrap-admin.ts platform y ... tenant --tenant-id=... --company-code=... (ver db/README.md).
  5. Push este repo (sin .env ni data/).
  6. Coolify → New Resource → Docker Compose → docker-compose.yml (solo para api/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).
  7. Persistent storage: volumen panel-data → /app/data en api (solo fallback de archivos si no hay Contabo).
  8. Dominios: web-panel → panels; web-saas → saas; api sin FQDN (proxy /v1).
  9. Cargar en Coolify todas las variables obligatorias de la tabla de arriba + COOKIE_SECURE=true.
  10. Deploy.
  11. Verificar https://app…/v1/health → ok, core, platform, redis.iam, redis.core y storage (en prod backend:"s3"). Si algo falla, la API no arranca.
  12. Login SaaS admin / tu SEED_PASSWORD.
  13. SMTP en /smtp o por SMTP_*.

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

  • .env y data/ fuera del git
  • Secrets distintos a los de desarrollo (incluye los 6 roles Postgres + 2 Redis)
  • Backup de panels_platform Y panels_product (ver db/backups/) -- no solo una
  • Backup del bucket de Contabo (documentos cifrados)
  • COOKIE_SECURE=true en HTTPS
  • Healthcheck API OK (/v1/health con las 5 conexiones en true)
  • --context-filter explí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)