You enforce it with process + CI + runtime gating so there’s no “optional” path.
1) CI: fail if schema isn’t produced only by migrations
Golden rule: tests must start from an empty DB and run migrations.
- In CI, spin up DB (Testcontainers / docker-compose)
- Run
migrate - Run app integration tests
If a dev “skips” a migration locally (manual DB change), CI won’t have it → tests fail.
2) Use validation on every build/deploy
- Flyway: run
validatein CI and on app startup (or pipeline step). - Liquibase: checksum validation is default; treat mismatches as build failures.
This prevents “I changed DB manually instead of writing a migration” and “I edited an old migration”.
3) Make the app refuse to start if DB isn’t at expected version
At startup:
- run migrations automatically (common in Spring Boot), or
- at least check schema version and fail fast if behind.
Practical pattern:
- app expects
schema_version >= X - if not, crash with clear message
This kills “deploy app without applying migrations”.
4) Branch protection + PR rules around migrations
- Require DB-migration owners/reviewers for PRs that touch
db/migrationor changelogs. - Require a CI check called “Migrations apply cleanly” to pass before merge.
5) Forbid “manual schema changes” by policy + audit
- No shared writable DBs for devs except ephemeral
- Production DB permissions: devs can’t run DDL
- Use audited roles for schema changes (only pipeline/DBA role)
6) Naming/versioning discipline (prevents “parallel skip”)
- Flyway: use timestamped versions to avoid collisions, so nobody “just picks V12 and hopes”.
- Liquibase: enforce globally unique changeset IDs (lint rule).
7) Add a linter / guardrails
- Pre-commit hook: block editing of already-applied migrations (best-effort)
- CI rule: forbid
clearCheckSumsusage / forbid changing old migration files - Optional: check that new migrations are appended and immutable
Interview-ready answer (2–3 sentences)
We enforce “no skipping” by running migrations from a clean database in CI and making
validate/checksum checks mandatory, so manual DB changes or missing migrations break the build. We also gate deployments by running migrations in the pipeline (or failing app startup if the schema is behind) and restrict DDL permissions so production changes can only happen through the migration process.