Skip to main content
O Midaz aplica as migrações de schema do PostgreSQL por meio de imagens de runner dedicadas — uma para o Ledger, outra para o Tracer — desacopladas dos binários das aplicações. As apps não migram mais no boot: elas sobem contra um schema que já foi migrado pelo runner.
Esse modelo é o que roda com make up no desenvolvimento local e com o chart de Helm no Kubernetes. Se você usa make up, não precisa fazer nada manual — a stack do Compose bloqueia a app até o runner de migração terminar.

Por que um runner dedicado


O migrador in-process anterior rodava a cada boot da app a partir do diretório de trabalho. Sob um pod security context endurecido (distroless:nonroot, runAsUser: 1000, readOnlyRootFilesystem: true, capabilities: drop [ALL]), esse caminho falhava com permission denied e causava crash-loop no pod. O runner dedicado elimina isso:
  • As migrações rodam uma única vez, como uma etapa separada, antes da app subir.
  • A imagem da app não empacota mais SQL de migração e nunca escreve no schema.
  • A própria imagem do runner roda limpa sob o mesmo security context endurecido (veja Postura de segurança).

Imagens do runner


Duas imagens são publicadas por release: As duas imagens compartilham o mesmo formato:
  • Base: migrate/migrate v4.19.1 (pinado).
  • Roda como USER 65532:65532 — não-root, agnóstica ao UID.
  • Entrypoint POSIX shell que monta um DSN a partir de variáveis de ambiente, executa migrate ... up e sai.
  • Não escreve nada em disco — o progresso é rastreado na tabela schema_migrations do Postgres, então readOnlyRootFilesystem: true é seguro.
O runner do Ledger aplica onboarding primeiro, depois transaction, em uma única invocação. O runner do Tracer aplica seu banco único em uma passada.

Contrato de ambiente


Cada entrypoint aceita um override de URL pronto ou as variáveis DB_* individuais. O override de URL tem prioridade quando definido.

Runner do Ledger

Runner do Tracer

Formato do DSN

Quando o entrypoint monta o DSN a partir das variáveis DB_*, ele produz:
A senha é codificada em percent-encoding para que caracteres reservados de URI (% @ : / ? # & + space [ ]) não quebrem o DSN. % é codificado primeiro para que escapes já inseridos não sejam duplo-codificados. O entrypoint só registra marcadores de fase (applying onboarding migrations, applying transaction migrations, applying tracer migrations, migrations complete). Ele nunca imprime credenciais nem o DSN montado.

Desenvolvimento local


make up

Na raiz do repositório, make up:
  1. Sobe a infra e aguarda até o Postgres ficar healthy.
  2. Executa os serviços one-shot ledger-migrate e tracer-migrate no Compose.
  3. Sobe cada app, que declara depends_on do seu serviço de migração com condition: service_completed_successfully.
A app nunca sobe contra um banco não migrado.

Targets make no host

Se você quiser rodar migrações direto contra um banco local — por exemplo enquanto desenvolve uma migração nova — cada componente expõe targets Make que usam o CLI pinado do golang-migrate e leem as configurações de conexão de .env. Ledger (components/ledger): Tracer (components/tracer):

Exemplo: rodar o runner do Ledger manualmente

Você raramente precisa invocar a imagem do runner manualmente — a gate do Compose faz isso por você. Quando precisar, aponte o runner para seus bancos com DB_* (ou *_DATABASE_URL) e execute uma vez:
O runner sai com 0 em caso de sucesso (incluindo o caso idempotente de no-op) e com não-zero em caso de falha.

Deploy no Kubernetes


Em produção, execute cada runner de migração como um Job do Kubernetes (tipicamente um PreSync Job do Argo CD ou um hook do Helm) que termine antes do rollout da app. O chart de Helm já conecta isso para você; as seções a seguir cobrem propriedades que você deve manter em mente se escrever seus próprios manifests.
Para deploys multi-tenant, o runner é intencionalmente agnóstico ao tenant: ele migra os bancos apontados pelo seu ambiente, uma vez. O fan-out por tenant é responsabilidade do deploy — rode o Job uma vez por banco de tenant, passando os valores DB_* (ou o override de URL) correspondentes.

Postura de segurança

O runner foi desenhado para um pod security context endurecido:
  • Não-root — a imagem fixa USER 65532:65532.
  • Agnóstica ao UID — os arquivos de migração pertencem a root e são de leitura pública, então qualquer UID pode lê-los.
  • Compatível com readOnlyRootFilesystemmigrate não escreve estado no filesystem.
A mesma imagem roda limpa sob runAsNonRoot: true, readOnlyRootFilesystem: true e capabilities: drop [ALL].

TLS

Os entrypoints honram o sslmode do ambiente (DB_ONBOARDING_SSLMODE, DB_TRANSACTION_SSLMODE ou DB_SSL_MODE), com default disable. Deploys de produção devem configurar require (ou mais estrito). O runner shell não reproduz o enforcement de TLS em código, a classificação de erros nem a telemetria que as apps usam para as próprias conexões — o TLS das migrações é controlado inteiramente via sslmode, em troca de um runner mínimo e sem dependências.

Convenções e idempotência


  • Pares up/down — toda migração tem um arquivo .up.sql e um .down.sql.
  • Idempotente — rodar migrate up quando o schema já está na versão mais recente é no-op. Re-executar o runner após um apply parcial ou completo é seguro.
  • CLI pinado — as imagens do runner e os targets Make no host pinam golang-migrate v4.19.1.

Atualização a partir de versões anteriores


Se você vinha de uma release do Midaz em que a app migrava no boot, nenhuma migração manual de dados é necessária — o schema em disco é o mesmo. Para adotar o novo modelo:
  1. Traga a release que inclui as imagens do runner.
  2. No Kubernetes, adicione os Jobs midaz-ledger-migrations e midaz-tracer-migrations ao seu pipeline de deploy para rodarem antes do rollout da app. Se você usa o chart oficial de Helm, isso já é feito para você.
  3. No desenvolvimento local, make up continua funcionando igual — a stack do Compose agora bloqueia as apps nos novos serviços one-shot de migração automaticamente.

Páginas relacionadas