Database migrations: self-migrate on boot
:::note Decision of record lives with the code
Summary + pointer page. The full decision (context, the per-service table, the revisit trigger, and the
target initContainer pattern) is the ADR in the GitOps repo β this page is the discoverable map entry.
Snapshot: 2026-10-06.
:::
Every custom DB-owning service (ktayl-policy-service, ktayl-underwriting, ktayl-iam,
retrieva-backend, ktayl-core) runs its schema migrations as a side-effect of process startup β
an explicit, documented platform standard, surfaced by a 12-factor review.
Decision (Accepted 2026-10-06): keep self-migrate-on-boot as the default for now. It is pragmatic for a mostly 1β2-replica lab β the migration always matches the running image, no extra orchestration, and all four migrators take an advisory lock so concurrent-replica boots serialise safely.
The 12-factor tension is real (migrations are an admin/release process #12, and coupling them to boot
hurts disposability #9). The concrete cost we actually paid was an in-process side-effect, not a
race: alembic's fileConfig() disabled every logger after the startup migration (zero live logs) β see
the testing strategy and the QA-gate write-up.
Revisit trigger β switch when any of: a service needs many replicas and boot-migration adds real latency/contention Β· another in-process-side-effect bug of the alembic-logging class (underwriting is the first candidate to move) Β· a long-running/destructive migration that should be gated separately from the deploy.
Target pattern when we switch: a same-image initContainer running a migrate entrypoint (iam's
standalone npm run db:migrate is the reference shape) β keeps "migration matches the image + env",
removes migration from the app process, and avoids the ArgoCD PreSync-Job-wedge class.
Decision of record (the ADR):
minicloud-gitops/docs/migrations-on-boot.md.