Your own drizzle-kit migrate, against production's shape
Connect a repository and DbProof finds the folder with meta/_journal.json, 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
Drizzle Kit has no target option, so DbProof cuts the journal after each migration in turn and runs drizzle-kit migrate, then puts the journal back as it was.
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-api migrations-dir: drizzle # The check sets DATABASE_URL to its throwaway database. migrate-command: npx drizzle-kit migrate
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 .notNull() over nulls
The generated SET NOT NULL meets nulls production's statistics show, and fails mid-deploy.
Migration fails to apply
A hotfix already added the column in production. The migration passes in CI on an empty database and fails on deploy.
An index that blocks writes
Drizzle Kit runs migrations in a transaction, where Postgres can't build an index concurrently. DbProof flags the lock on large tables and says how to build it outside drizzle-kit migrate.
A type change that rewrites a table
integer to bigint rewrites every row while holding the table's lock. DbProof flags it on tables over 1M rows.
A reference without an index
.references() creates the foreign key but no index on it, so every delete from the parent scans the child table.
A dropped column
The code still deployed may read it, and breaks the moment the migration runs.
An applied migration edited
Someone changed a migration production already ran, so the repository no longer matches production's history.
Production changed outside migrations
DbProof opens a pull request with the migration and its journal entry, and reminds you to add the change to your Drizzle schema and regenerate.
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
drizzle.__drizzle_migrations: each applied migration's hash and timestamp
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
Drizzle questions
Does it work with drizzle-kit push?
No. DbProof tests the migrations drizzle-kit generate writes, and push changes the database without writing any.
My drizzle.config.ts reads a variable other than DATABASE_URL.
Set that variable from $DBPROOF_CHECK_DSN in the workflow's migrate-command, e.g. PG_URL="$DBPROOF_CHECK_DSN" npx drizzle-kit migrate.
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.