Skip to main content
Midaz aplica las migraciones de esquema de PostgreSQL a través de imágenes de runner dedicadas — una para Ledger, otra para Tracer — desacopladas de los binarios de la aplicación. Las apps ya no migran en el arranque: se inician contra un esquema que ya fue migrado por el runner.
Este modelo es el que corre con make up en desarrollo local y con el chart de Helm en Kubernetes. Si usas make up, no necesitas hacer nada manual — el stack de Compose bloquea la app hasta que el runner de migración termine.

Por qué un runner dedicado


El antiguo migrador in-process se ejecutaba en cada arranque de la app desde el directorio de trabajo. Bajo un contexto de seguridad de pod endurecido (distroless:nonroot, runAsUser: 1000, readOnlyRootFilesystem: true, capabilities: drop [ALL]), esa ruta fallaba con permission denied y provocaba crash-loop en el pod. El runner dedicado evita eso por completo:
  • Las migraciones corren una sola vez, como un paso separado, antes de que arranque la app.
  • La imagen de la app ya no incluye SQL de migración y nunca escribe en el esquema.
  • La propia imagen del runner corre limpia bajo el mismo contexto de seguridad endurecido (ver Postura de seguridad).

Imágenes del runner


Se publican dos imágenes por release: Ambas imágenes comparten la misma forma:
  • Base: migrate/migrate v4.19.1 (pineada).
  • Se ejecuta como USER 65532:65532 — no-root, agnóstica al UID.
  • Entrypoint POSIX shell que ensambla un DSN a partir de variables de entorno, ejecuta migrate ... up y sale.
  • No escribe nada en disco — el progreso se guarda en la tabla schema_migrations de Postgres, por lo que readOnlyRootFilesystem: true es seguro.
El runner de Ledger aplica primero onboarding y luego transaction, en una sola invocación. El runner de Tracer aplica su única base en un solo paso.

Contrato de entorno


Cada entrypoint acepta o bien un override de URL prebuilt o las variables DB_* individuales. El override de URL tiene prioridad cuando está definido.

Runner de Ledger

Runner de Tracer

Forma del DSN

Cuando el entrypoint ensambla el DSN a partir de las variables DB_*, produce:
El password se codifica en porcentajes para que los caracteres reservados de URI (% @ : / ? # & + space [ ]) no rompan el DSN. % se codifica primero para no doble-codificar escapes ya insertados. El entrypoint solo registra marcadores de fase (applying onboarding migrations, applying transaction migrations, applying tracer migrations, migrations complete). Nunca imprime credenciales ni el DSN ensamblado.

Desarrollo local


make up

Desde la raíz del repositorio, make up:
  1. Levanta la infra y espera hasta que Postgres esté healthy.
  2. Arranca los servicios one-shot ledger-migrate y tracer-migrate en Compose.
  3. Arranca cada app, que declara depends_on sobre su servicio de migración con condition: service_completed_successfully.
La app nunca se inicia contra una base sin migrar.

Targets make en el host

Si quieres ejecutar migraciones directamente contra una base local — por ejemplo mientras desarrollas una migración nueva — cada componente expone targets de Make que usan el CLI pineado de golang-migrate y leen la configuración de conexión desde .env. Ledger (components/ledger): Tracer (components/tracer):

Ejemplo: ejecutar el runner de Ledger a mano

Rara vez necesitas invocar la imagen del runner manualmente — la gate de Compose lo hace por ti. Cuando lo hagas, apunta el runner a tus bases con DB_* (o *_DATABASE_URL) y ejecútalo una vez:
El runner sale con 0 si tiene éxito (incluido el caso idempotente sin cambios) y con no-cero si falla.

Despliegue en Kubernetes


En producción, ejecuta cada runner de migración como un Job de Kubernetes (típicamente un PreSync Job de Argo CD o un hook de Helm) que termine antes del rollout de la app. El chart de Helm ya lo conecta por ti; las secciones siguientes cubren las propiedades que debes tener presentes si escribes tus propios manifests.
Para despliegues multi-tenant, el runner es intencionadamente agnóstico al tenant: migra las bases apuntadas por su entorno, una vez. El fan-out por tenant es una preocupación del deploy — ejecuta el Job una vez por base de tenant, pasando los valores DB_* (o el override de URL) correspondientes.

Postura de seguridad

El runner está diseñado para un contexto de seguridad de pod endurecido:
  • No-root — la imagen fija USER 65532:65532.
  • Agnóstica al UID — los archivos de migración son propiedad de root y de lectura pública, por lo que cualquier UID puede leerlos.
  • Compatible con readOnlyRootFilesystemmigrate no escribe estado en el filesystem.
La misma imagen corre limpia bajo runAsNonRoot: true, readOnlyRootFilesystem: true y capabilities: drop [ALL].

TLS

Los entrypoints honran sslmode desde el entorno (DB_ONBOARDING_SSLMODE, DB_TRANSACTION_SSLMODE o DB_SSL_MODE), con valor por defecto disable. Los despliegues productivos deberían configurar require (o más estricto). El runner shell no reproduce la aplicación en código de TLS, la clasificación de errores ni la telemetría que las apps usan para sus propias conexiones — el TLS para las migraciones se controla enteramente vía sslmode, a cambio de un runner mínimo y sin dependencias.

Convenciones e idempotencia


  • Pares up/down — cada migración tiene un archivo .up.sql y uno .down.sql.
  • Idempotente — ejecutar migrate up cuando el esquema ya está en la última versión es un no-op. Re-ejecutar el runner tras un apply parcial o completo es seguro.
  • CLI pineado — las imágenes del runner y los targets Make en host pinean golang-migrate v4.19.1.

Actualización desde versiones anteriores


Si venías de una release de Midaz donde la app migraba al arrancar, no se requiere migración manual de datos — el esquema en disco es el mismo. Para adoptar el nuevo modelo:
  1. Trae la release que incluye las imágenes del runner.
  2. En Kubernetes, agrega los Jobs midaz-ledger-migrations y midaz-tracer-migrations a tu pipeline de deploy para que corran antes del rollout de la app. Si usas el chart oficial de Helm, esto ya está hecho.
  3. En desarrollo local, make up sigue funcionando igual — el stack de Compose ahora bloquea las apps sobre los nuevos servicios one-shot de migración de forma automática.

Páginas relacionadas