Royal Clean CRM · Documentación
Despliegue

Panorama del despliegue

Qué piezas hay, cómo se conectan y qué hace falta antes de empezar.

Para el paso a paso en la VPS, ver DOKPLOY.md. Este documento explica cómo se compila y arranca la aplicación, y qué necesita para funcionar.

Flujo

flowchart LR
    A[git push a main] --> B[Dokploy detecta el cambio]
    B --> C[docker build]
    C --> D[Arranca el contenedor]
    D --> E[prisma migrate deploy]
    E --> F{¿Migraciones OK?}
    F -->|| G[node server.js]
    F -->|No| H[El contenedor no arranca<br/>Dokploy conserva la versión anterior]
    G --> I[/api/health]
    I -->|sano| J[Traefik enruta el tráfico]

Que el contenedor no arranque si fallan las migraciones es deliberado: es preferible seguir sirviendo la versión anterior a servir la nueva contra un esquema incompleto.

La imagen

Dockerfile multi-etapa:

EtapaQué hace
baseNode 22 Alpine con pnpm y libc6-compat para binarios nativos
depspnpm install --frozen-lockfile
builderprisma generate y next build
migratorInstala la CLI de Prisma con npm, en árbol plano (DEC-017)
runnerSólo lo necesario para ejecutar. Usuario sin privilegios

Resultado: ~640 MB. La mayor parte es la CLI de Prisma, necesaria para aplicar las migraciones al arrancar (ver DECISIONS.md DEC-017).

Detalles que importan:

  • output: "standalone" deja en .next/standalone un servidor con sólo las dependencias de ejecución.
  • Corre como nextjs (uid 1001), no como root.
  • TZ=UTC en el contenedor a propósito: la zona del negocio se aplica de forma explícita en el código, nunca por ambiente.
  • El HEALTHCHECK consulta /api/health, que verifica proceso y base de datos.
  • El build no necesita secretos: la validación del entorno es perezosa.

Variables de entorno

Obligatorias

VariableEjemploNotas
DATABASE_URLpostgresql://user:pass@db:5432/royalclean?schema=publicHost interno de Dokploy, nunca público
AUTH_SECRET48 bytes aleatoriosopenssl rand -base64 48
APP_URLhttps://crm.royalcleancr.comURL pública canónica

Almacenamiento de evidencia

VariableNotas
R2_ACCOUNT_IDId de la cuenta de Cloudflare
R2_ACCESS_KEY_IDToken de API de R2
R2_SECRET_ACCESS_KEYSecreto del token
R2_BUCKET_NAMEroyal-clean-evidence
R2_ENDPOINTOpcional; se deriva de R2_ACCOUNT_ID si se omite

Sin estas variables la aplicación arranca igual, pero las funciones de evidencia devuelven un error claro en lugar de romper el arranque.

Opcionales

VariablePara qué
JOBS_SECRETToken del endpoint /api/jobs/run
BUSINESS_TIMEZONEPor defecto America/Costa_Rica
LOG_LEVELdebug, info, warn, error
RESEND_API_KEYReservado; sin integración activa

NODE_ENV=production lo fija la propia imagen.

Migraciones

En producción siempre prisma migrate deploy. Nunca migrate dev, que puede reescribir el historial.

Lo ejecuta docker/entrypoint.sh antes de levantar el servidor.

Migraciones compatibles

Para no romper durante el despliegue, cuando la versión anterior y la nueva conviven unos segundos:

  • Una columna nueva obligatoria necesita valor por defecto, o se añade en dos pasos: primero opcional, se rellena, luego se hace obligatoria.
  • No se renombran columnas en uso: se añade la nueva, se migran los datos, y la antigua se retira en un despliegue posterior.
  • Los índices sobre tablas grandes se crean con CONCURRENTLY en una migración manual.

Rollback

Prisma no revierte migraciones automáticamente. Ante un problema:

  1. Volver a la versión anterior de la aplicación desde Dokploy. Si la migración era compatible, la versión anterior sigue funcionando.
  2. Si la migración era destructiva, restaurar el respaldo previo (ver OPERATIONS.md).
  3. Escribir una migración correctiva hacia adelante y desplegarla.

Por eso las migraciones destructivas se evitan salvo necesidad real.

Antes de dar por bueno un despliegue

  • pnpm verify en verde
  • pnpm test:integration en verde
  • La imagen se construye
  • /api/health responde {"status":"ok","database":"ok"}
  • Se puede iniciar sesión
  • /api/jobs/run sin token responde 401
  • El bucket de R2 es privado
  • PostgreSQL no está expuesto a Internet
  • Ningún secreto en el repositorio
  • HTTPS activo y renovación automática configurada

On this page