Saltar a contenido

ADR-0006 · Las migraciones se aplican al arrancar el contenedor

  • Estado: aceptado
  • Decisores: hquintero
  • Fecha de la decisión: 2026-08-27

Contexto y problema

El deploy necesita aplicar las migraciones de src/migrations/ antes de servir la versión nueva. Pero la imagen (output: standalone) empaqueta solo lo que el servidor usa: el CLI payload migrate no viaja en la imagen, así que el job de deploy no tiene con qué ejecutarlo.

Contexto de infraestructura que pesa en la decisión: la base PostgreSQL no vive en el compose — está en una VM dedicada, fuera del host del CMS. El pipeline nunca gestiona el ciclo de vida de la base; solo se conecta.

Opciones consideradas

  1. prodMigrations en el config: Payload aplica las migraciones al inicializar (elegida). Es el mecanismo oficial de Payload para despliegues sin CLI.
  2. Que el runner clone el repo e instale dependencias para correr payload migrate desde afuera — reinstala medio proyecto en cada deploy, exactamente lo que la imagen evita.
  3. Publicar una segunda imagen «con herramientas» solo para migrar — dos artefactos por versión.

Decisión

Opción 1, con una compuerta explícita: prodMigrations se activa solo si PAYLOAD_MIGRATE_ON_INIT=true, variable que definen los servidores vía compose y nadie más.

La compuerta no es opcional, y la prueba lo demostró: next build corre con NODE_ENV=production, así que sin la variable de por medio el build local intentaba migrar contra la base de dev (gestionada por push), detectaba el conflicto y quedaba colgado en un prompt interactivo — el mismo patrón del push sin TTY.

Verificado el 27/08/2026 contra una base creada vacía: al primer acceso, Payload aplicó las 3 migraciones en orden, construyó el esquema completo (77 tablas) y /admin respondió 200.

Consecuencias

Positivas

  • El paso migrate desaparece del job de deploy: pull → up -d y el arranque hace el resto.
  • Si una migración falla, la versión nueva no sirve tráfico de Payload; el error queda en los logs del contenedor.

Negativas / deuda asumida

  • La credencial de la app necesita DDL. La idea de un usuario de base sin DDL para la app (protección contra un next dev apuntado a producción) deja de ser posible: quien migra es la app. La protección se muda a la red: el firewall / pg_hba.conf de la VM de base acepta conexiones solo desde los hosts del CMS, así una máquina de desarrollo ni siquiera alcanza la base de producción. Esa VM dedicada existe; la regla hay que configurarla (definición pendiente del plan).
  • La inicialización de Payload es perezosa: ocurre con la primera petición que lo necesita, no al arrancar el proceso. /api/health no la dispara (a propósito). El smoke test del deploy es en dos pasos: /api/health (la app vive) y /admin (fuerza la inicialización → aplica y verifica migraciones).
  • El modelo asume una sola instancia del CMS por entorno. Dos réplicas arrancando a la vez migrarían en simultáneo; si algún día hay réplicas, esta decisión se revisa.
  • Un rollback de imagen vieja sobre esquema ya migrado sigue rigiéndose por la regla del runbook: revertir el down a mano o convivir con el esquema adelantado si es compatible.