Your own prisma migrate deploy, against production's shape
Connect a repository and DbProof finds the folder with migration_lock.toml, the nearest package.json and your package manager, and opens a pull request with the workflow.
Restore production's shape
The check starts a throwaway Postgres on production's major version in your runner and restores the latest snapshot of production's schema and migration history.
Apply one migration at a time
Prisma has no target option, so DbProof moves later migration folders aside and runs prisma migrate deploy once per migration, then puts them back.
Weigh it against production
DbProof compares the result with production's statistics and reports on the pull request as a check and a comment, with the file, the line and a suggested fix.
The check sets DATABASE_URL to its throwaway database, so the step that runs it is your own command:
- uses: dbproof/check-action@v1 with: project: your-org/your-app migrations-dir: prisma/migrations # The check sets DATABASE_URL to its throwaway database. migrate-command: npx prisma migrate deploy
What the check reports
Each finding names the migration file and line, says what production would do, and suggests a fix. Errors fail the check; warnings don’t.
A required field over nulls
You make a field required and Prisma generates SET NOT NULL. Production's statistics show nulls, so migrate deploy fails mid-release and later deploys stop until someone runs prisma migrate resolve.
Migration fails to apply
A hotfix already added the column in production. The migration passes on the shadow database and fails on deploy.
An index that blocks writes
An @@index becomes CREATE INDEX, which blocks writes to a large table while it builds. DbProof suggests building it concurrently, alone in a migration of its own.
A type change that rewrites a table
Int to BigInt rewrites every row while holding the table's lock. DbProof flags it on tables over 1M rows.
A relation without an index
On Postgres, Prisma creates the foreign key but no index on it, so every delete from the parent scans the child table.
A dropped column
The Prisma Client still deployed selects every field, so removing one breaks it the moment the migration runs.
An applied migration edited
Someone changed a migration.sql production already ran, so the repository no longer matches production's history.
Production changed outside migrations
DbProof opens a pull request with a migration folder that adopts the change, and reminds you to add it to schema.prisma, so the next migration doesn't undo it.
Structure and statistics, never rows
A capture step in your deploy job reads production with the connection string your migrations already use, and uploads a snapshot.
What capture reads
- The catalog: tables, columns, indexes, constraints, views and functions
- Planner estimates: row counts, and each column's share of nulls, distinct values and width
_prisma_migrations, with each failed migration'slogsreplaced before upload, since an error can quote a row
What it never reads
- Rows from any of your tables
- Sample values: no most-common values, no histograms
- Your connection string: it stays in your GitHub secrets
Prisma questions
Does it work with prisma db push?
No. DbProof tests the migrations in your prisma/migrations folder, and db push changes the database without writing any.
My datasource reads a variable other than DATABASE_URL.
Set that variable from $DBPROOF_CHECK_DSN in the workflow's migrate-command, e.g. DIRECT_URL="$DBPROOF_CHECK_DSN" npx prisma migrate deploy.
Do I need a shadow database in CI?
No. prisma migrate deploy doesn't use one, and the check brings its own throwaway Postgres.
Monorepos, pnpm or Yarn?
DbProof uses the package.json nearest the migrations folder, and installs with npm, pnpm or Yarn depending on the lockfile it finds.
Does DbProof connect to my database?
No. Capture runs in your deploy job, and checks run against a throwaway Postgres in your runner. DbProof only receives the snapshot.
Also for Drizzle Kit Flyway Atlas