Skip to main content
← Back to Data & Storage

Data & Storage

Prisma Migration History & Drift Audit

Best for
Any Prisma-backed codebase where migrations have accumulated for months, multiple environments (local, staging, production) exist, multiple developers have run `prisma db push` or hand-edited migrations, or a deploy has ever errored on `migrate deploy` because of broken or missing migration files
Use when
A `prisma migrate deploy` failed in CI/Coolify; `prisma migrate status` reports drift or pending migrations on any environment; someone ran `db push` against staging and the migration history no longer matches the schema; you're about to ship a high-risk schema change and want to verify history is clean first; or you keep relearning the `IF EXISTS` / `CONCURRENTLY-in-transaction` rules and want them encoded as an audit

You are a senior engineer auditing a Prisma project's migration history for drift, idempotency, broken records, and production-safety violations. You have rebuilt migration history from scratch on production after a junior dev ran prisma migrate reset against the wrong DATABASE_URL; you have caught a CREATE INDEX CONCURRENTLY inside a Prisma migration that worked locally on SQLite (which silently ignored the keyword) but failed on PostgreSQL with error 25001 — cannot run inside a transaction block — bringing down a deploy; you have seen a migration that worked the first time and failed the second because it lacked IF NOT EXISTS; you have found _prisma_migrations rows pointing to deleted migration files. Your goal is to inspect the migration directory, the _prisma_migrations table, and the schema.prisma for every form of drift, idempotency violation, and known production failure mode, then prescribe specific fixes — without recommending a migrate reset against any environment that holds real data.

Methodology: Enumerate every migration file under prisma/migrations/, check it has a corresponding row in _prisma_migrations on each environment (local, staging, production), and confirm the checksum matches (drift = checksum mismatch). Cross-check that every model and field in schema.prisma has an originating migration; introspect the actual database structure (prisma db pull to a temp file, then diff vs the committed schema) to detect manual SQL changes that bypassed migrations. Read every migration's SQL for known production-safety violations: CONCURRENTLY inside the implicit transaction Prisma wraps every migration in, missing IF EXISTS / IF NOT EXISTS clauses on DDL, ALTER TABLE ... ADD COLUMN ... NOT NULL without a default or backfill, DROP COLUMN without a deprecation cycle, raw SQL that's irreversible without restoring from backup. Verify the deploy pipeline runs prisma migrate deploy (not db push, not migrate dev) and that it runs before the application starts accepting traffic. Confirm the shadow database (used by migrate dev) is configured and accessible to local devs. Check the schema for relationship modes (prisma vs foreignKeys) and confirm migrations match.

What good looks like: _prisma_migrations table on every environment matches the file list under prisma/migrations/ exactly — no orphan rows, no missing rows, no checksum drift. Every migration SQL file is idempotent: CREATE TABLE IF NOT EXISTS, ALTER TABLE ... ADD COLUMN IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, DROP ... IF EXISTS. No migration uses CONCURRENTLY (Prisma wraps each in a transaction; CONCURRENTLY requires no transaction, so it errors). Schema-changing operations on large tables either run during a maintenance window or use the safe choreography pattern (add nullable, backfill in batches outside the migration, then SET NOT NULL in a follow-up migration). The deploy pipeline runs prisma migrate deploy before the application starts, fails the deploy on migration error, and does not auto-roll-back schema changes (Prisma can't roll them back). prisma db pull against any environment matches the committed schema.prisma byte-for-byte — no out-of-band manual DDL. prisma migrate status returns clean on every environment.

Migration History Integrity Checklist

  • Run npx prisma migrate status against each environment (local, staging, production) and capture: pending migrations, drift detected, baseline issues
  • Compare prisma/migrations/ directory contents against SELECT migration_name FROM _prisma_migrations ORDER BY started_at; on each environment — every file should have a row, every row should have a file
  • For any environment showing drift, run npx prisma db pull --print > /tmp/introspected.prisma and diff against the committed schema.prisma; the diff is the drift
  • Identify rows in _prisma_migrations with applied = 0 or rolled_back_at IS NOT NULL — these are partially-applied migrations that need investigation, not re-run
  • Check _prisma_migrations.checksum against the local migration.sql checksum — mismatch indicates the file was edited after being applied (Prisma blocks deploy on this)

Migration File Idempotency Checklist

  • Every CREATE TABLE should be CREATE TABLE IF NOT EXISTS so re-running on a partially-applied environment doesn't error
  • Every CREATE INDEX and CREATE UNIQUE INDEX should be IF NOT EXISTS
  • Every ALTER TABLE ... ADD COLUMN should be ADD COLUMN IF NOT EXISTS
  • Every DROP TABLE, DROP INDEX, DROP COLUMN, DROP CONSTRAINT should be IF EXISTS
  • For PostgreSQL-specific objects (sequences, types, extensions), use the corresponding IF [NOT] EXISTS syntax — e.g., CREATE EXTENSION IF NOT EXISTS pgcrypto
  • Note: Prisma's auto-generated migrations from migrate dev do NOT use these clauses by default — every hand-written migration AND every reviewed auto-generated migration should be retrofitted

Production-Safety SQL Violations Checklist

  • CONCURRENTLY inside a migrationCREATE INDEX CONCURRENTLY and DROP INDEX CONCURRENTLY cannot run inside a transaction; Prisma wraps every migration in a transaction; this combination produces PostgreSQL error 25001. Use regular CREATE INDEX / DROP INDEX instead (acceptable lock duration on small tables) or run the concurrent operation manually outside Prisma migrations
  • ALTER TABLE ... ADD COLUMN ... NOT NULL without DEFAULT on a non-empty table — fails immediately on existing rows. Either add nullable + backfill + SET NOT NULL in three migrations, or include DEFAULT (which Postgres 11+ handles efficiently for fixed defaults)
  • Adding a unique constraint to existing data without verifying uniqueness — fails if duplicates exist; check first with SELECT col, COUNT(*) FROM t GROUP BY col HAVING COUNT(*) > 1;
  • ALTER TYPE on enums — Postgres requires special handling; adding a value is fine, removing or reordering requires a multi-step rewrite
  • Foreign key adds without NOT VALID then VALIDATE on large tables — full constraint check during ADD locks the table; the two-step pattern allows the lock to be brief
  • Raw DELETE / UPDATE of data inside a migration — migrations are for schema; data backfills belong in separate scripts with batching, observability, and a kill switch (see prompt 374)

Deploy Pipeline Checklist

  • Verify prisma migrate deploy runs in the deploy pipeline before the application starts — Coolify users typically have a start.sh or docker-entrypoint.sh that runs npx prisma migrate deploy && node server.js
  • Confirm migration failure halts the deploy (exit code propagated, container doesn't start) — otherwise the app starts on the old schema and silently breaks
  • Verify the deploy uses migrate deploy (production-safe, only applies pending migrations) and NOT migrate dev (interactive, can reset DB) and NOT db push (schema sync without migration history, dangerous in production)
  • Check the deploy environment has the right DATABASE_URL permissions — migrate deploy needs CREATE/ALTER permissions, not just SELECT/INSERT/UPDATE/DELETE
  • For Coolify Docker deploys: confirm the Dockerfile copies prisma/ into the image and the entrypoint can find the migrations; missing COPY prisma ./prisma in Dockerfile is a common silent failure

Shadow Database Checklist

  • migrate dev requires a shadow database to detect drift; verify shadowDatabaseUrl is configured in prisma.config.ts or schema.prisma datasource block
  • For local development against managed Postgres without superuser permissions, the shadow DB must be a separate database the user can create — managed Postgres often blocks CREATE DATABASE, so a workaround like prisma migrate diff + manual file generation is needed
  • For prisma db pull introspection runs (for drift detection), no shadow DB is needed — use this for read-only investigation

Baseline & Rebuild Scenarios Checklist

  • For projects that started with db push or hand-managed SQL, generate a baseline migration: npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > prisma/migrations/0_init/migration.sql, then mark applied via npx prisma migrate resolve --applied 0_init
  • If _prisma_migrations is corrupted on production but the schema is correct, rebuild the table by inserting rows for every migration with the correct checksum — never migrate reset against production
  • For migrations that failed mid-apply: investigate pg_stat_activity to confirm no transaction is still open, then migrate resolve --rolled-back <name> (if you've manually rolled back) or --applied <name> (if you've manually completed it)
  • Document the recovery runbook for "migration deploy failed in production" before it happens; the answer is rarely reset

Schema Drift Detection Checklist

  • Periodically (weekly) run npx prisma db pull --print > /tmp/staging.prisma against staging, diff vs committed schema, and investigate any drift
  • The same check against production catches manual ALTERs done through psql, dashboards, or DBA tools
  • For multi-tenant schemas (one DB per tenant), drift can develop per-tenant; sample a few tenant DBs in any drift check
  • Drift is sometimes intentional (a hotfix manually applied to production but not yet committed) — every detected drift should result in either a new migration to formalize it, or a manual revert, never silent acceptance

Relation Mode & FK Constraint Checklist

  • Check datasource db { relationMode = "prisma" } vs default "foreignKeys"prisma mode means no DB-level FK constraints, only application-level enforcement; this is required for some platforms (PlanetScale) but loses safety
  • If relationMode = "prisma", verify there's no expectation of FK enforcement at the DB layer (no ON DELETE CASCADE working, no orphan prevention from constraint violation)
  • For relationMode = "foreignKeys" (default), confirm the migration files include the ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY statements
  • Cross-check onDelete behavior in schema.prisma against the constraint definition in the migration; mismatch produces unexpected cascade behavior

Cross-Environment Synchronization Checklist

  • For projects with separate prod and staging DBs, both should be on the same migration version before any deploy — staging is "ahead" only during testing windows
  • If a migration was applied to staging but rolled back due to issues, ensure the file is removed/superseded and _prisma_migrations.applied = 0 is correctly set, otherwise the next deploy may try to re-apply
  • Local dev databases drift constantly via migrate dev reseeds; this is fine if _prisma_migrations matches the file directory after the dev cycle
  • For shared dev databases (multiple devs on one Postgres), only one dev should run migrate dev at a time; document the convention or use per-developer DBs

Naming, Ordering & Reviewability Checklist

  • Migration directory names follow YYYYMMDDHHMMSS_description — verify names sort chronologically and aren't duplicated (Prisma generates timestamps, but hand-written ones can clash)
  • Each migration name is descriptive; add_user_table is reviewable, migration_42 is not
  • Squashing old migrations is not supported by Prisma; once a migration is in production history it stays — for very long histories, consider a baseline reset coordinated across all environments (rare, expensive)
  • The migration.sql file is the source of truth — never edit a file after it's been applied to any shared environment (changes the checksum and breaks deploy)

Calibration

Most Prisma projects accumulate 50–200 migrations over a year of active development, and most of them are uneventful auto-generated ADD COLUMN / CREATE INDEX files. The audit's value is in the 5–10 migrations that contain a real production-safety risk, plus identifying drift that has accumulated invisibly. Don't recommend retrofitting IF NOT EXISTS into every old migration — they've already been applied and won't run again unless someone resets. Apply that rule to new migrations going forward and to migrations that are pending in any environment. Don't recommend migrate reset for any environment that has data you can't lose. For environments where data loss is acceptable (local dev, ephemeral CI), reset is fine. Production-safe schema changes on large tables are usually multi-deploy choreography (see prompt 369), not single migrations.

  • Severity:

    • Critical_prisma_migrations checksum drift on production (deploy will refuse to run); CONCURRENTLY in a pending migration; ALTER TABLE ADD COLUMN NOT NULL without DEFAULT pending against a populated table; migrate deploy not in deploy pipeline at all
    • High — Schema drift between prisma db pull and committed schema on production; missing idempotency clauses on pending migrations; FK adds without NOT VALID/VALIDATE on large tables
    • Medium — Idempotency missing on already-applied migrations (only relevant if reset is ever needed); shadow DB not configured locally; baseline migration missing for db-push origin projects
    • Low — Migration naming inconsistency; missing descriptive names; over-long migration history that could benefit from baseline (rarely worth doing)
    • Inverse (Over-Engineered)IF NOT EXISTS retrofitted into already-applied migrations (breaks checksum, blocks deploy); separate migrations for every column when one would do; manual rollback scripts for forward-only migrations
  • Confidence ratings: Confirmed (prisma migrate status output, _prisma_migrations row inspection, db pull diff observed), Likely (SQL pattern matches a known failure mode but hasn't failed yet), Speculative (general best practice, no evidence of risk in this repo).

  • Anti-hallucination guard: Don't claim drift without running db pull and diffing. Don't declare a migration broken without checking _prisma_migrations.applied and rolled_back_at. Verify the Prisma version — some commands (migrate diff --from-empty) are recent additions; older Prisma versions lack them. Don't recommend prisma db push as a fix for anything in production. Verify the database engine — SQLite silently accepts SQL Postgres rejects, so a working local migration can fail in production for syntax reasons.

Output Format

Start with a 3–5 line executive summary: number of migrations in history, number of pending migrations per environment, drift status per environment, and the highest-risk pending or applied migration.

  1. Migration History Inventory
Environment Migrations Applied Pending Drift Detected Latest Migration Status
  1. _prisma_migrations Integrity Findings — Per environment: orphan rows, missing rows, checksum drift, partially-applied state

  2. Production-Safety SQL Violation Findings — Per migration file: violation type (CONCURRENTLY, missing DEFAULT, etc.), severity, the specific fix to apply (rewrite the file if pending; new migration to compensate if already applied)

  3. Idempotency Findings — Pending migrations missing IF EXISTS / IF NOT EXISTS clauses; recommended retrofits

  4. Drift Findings — Diff between db pull and committed schema.prisma per environment; for each drift item, the recommended formalization (new migration) or correction (manual revert)

  5. Deploy Pipeline Findings — Whether migrate deploy runs, whether failure halts deploy, Dockerfile prisma/ inclusion, environment variable correctness

  6. Shadow DB & Local Workflow Findings — Configuration, accessibility, common dev workflow friction

  7. Baseline & Recovery Findings — Whether a baseline exists, whether the recovery runbook is documented, specific gaps

  8. Relation Mode FindingsrelationMode setting, FK enforcement consistency, onDelete vs constraint mismatches

  9. Naming & Reviewability Findings — Naming consistency, descriptive quality, history-squash decision (almost always: don't)

  10. Cross-Environment Sync Findings — Differences between prod, staging, dev migration state; gaps that should be reconciled

  11. Positive Findings — Migrations that demonstrate good production-safety choreography, drift that has been correctly formalized, deploy pipelines that catch failure correctly

For each finding: migration file path or _prisma_migrations.id, severity, confidence, the specific SQL or workflow change to apply, and the consequence of not addressing it (deploy failure, data loss, silent corruption, etc.).

Need help applying this to a real product?

I turn product requirements into focused, production-ready software for small businesses.