Baselining Prisma Migrate on an Existing Database

The production database was built with prisma db push, or by an older tool, or by hand, and the team now wants proper migration history with prisma migrate deploy. The first attempt fails: Error: P3005 — The database schema is not empty. Prisma Migrate refuses to apply its first migration to a database that already has tables, because it has no record of how they got there and would try to create them again. Baselining is the one-time step that tells Prisma “this database already contains everything up to migration zero”. Done correctly, every environment ends up with identical history and the next change is an ordinary migration; done carelessly, the baseline describes a schema production does not actually have. This guide walks through the safe sequence using prisma migrate diff and prisma migrate resolve. It belongs to Prisma Migration Strategies.

Baselining Sequence Five steps. Introspect or confirm schema.prisma matches production; generate prisma/migrations/0_init/migration.sql with migrate diff from empty to the schema; prove it matches production by applying it to a scratch database and diffing; run migrate resolve --applied 0_init on production; from now on use migrate deploy. Baselining Sequence STEP 1 Align schema.prisma db pull if unsure STEP 2 Generate 0_init migrate diff --from-empty STEP 3 Prove parity scratch DB vs prod STEP 4 Mark applied migrate resolve --applied STEP 5 Normal deploys migrate deploy
migrate resolve --applied records the baseline without running it — which is only safe after the parity check.

Symptom / Error Signatures

These messages mean a database needs a baseline:

Error: P3005
The database schema is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline

Related situations: prisma migrate deploy in a new pipeline tries to CREATE TABLE "User" against a database that has it (42P07 relation "User" already exists); the project has been using prisma db push in production and has no prisma/migrations folder; or a legacy database managed by Flyway, Knex or hand-written scripts is being moved to Prisma.

Root Cause Analysis

Prisma Migrate tracks applied migrations in the _prisma_migrations table, with each row holding the migration name, a checksum of its SQL and timestamps. A database without that table, or with an empty one, is treated as not managed by Migrate; if it also contains tables, applying the first migration would conflict, so Prisma stops with P3005. prisma migrate resolve --applied <name> inserts a row saying a migration has been applied without executing its SQL — the mechanism for adopting an existing database.

The risk is entirely in the baseline’s content. prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script produces the SQL that would create what schema.prisma describes. If schema.prisma differs from production — a missing index, a column the Prisma schema does not model, a different default — the baseline and production disagree, and every environment built from migrations will differ from production in the same way. Checking parity before resolving closes that gap.

Step Command Touches production?
Align the Prisma schema prisma db pull (introspection) reads only
Generate baseline SQL prisma migrate diff --from-empty --to-schema-datamodel ... --script no
Prove parity apply to scratch DB, compare dumps reads only
Record baseline prisma migrate resolve --applied 0_init writes one row in _prisma_migrations
Future changes prisma migrate deploy yes, reviewed migrations

The exact flag names of migrate diff have changed between Prisma major versions (for example, how the target schema file is passed); check npx prisma migrate diff --help for your version.

Where the Baseline Is Executed vs Recorded The 0_init migration file is executed on new environments such as CI, developer and preview databases, which start empty. On production and existing staging it is recorded as applied with migrate resolve, never executed. The parity check links the file to production. Where the Baseline Is Executed vs Recorded 0_init/migration.sql full existing schema Production / staging migrate resolve --applied CI / dev / preview migrate deploy executes it Parity check scratch build vs prod recorded executed
Existing databases record the baseline; new databases execute it — both then share one history.

Immediate Mitigation

1. Make schema.prisma describe production. If you are unsure, introspect production (read-only) and review the result.

# Shell · read-only credentials to production or a replica · rewrites schema.prisma from the database
DATABASE_URL="$PROD_READONLY_URL" npx prisma db pull
git diff prisma/schema.prisma   # review: this is what production actually looks like

2. Generate the baseline migration. Use a directory name that sorts first, such as 0_init.

# Shell · project root · no database connection needed
mkdir -p prisma/migrations/0_init
npx prisma migrate diff \
  --from-empty \
  --to-schema-datamodel prisma/schema.prisma \
  --script > prisma/migrations/0_init/migration.sql

3. Prove parity against production. Apply the baseline to an empty scratch database and compare its schema dump with production’s.

# Shell · scratch database only · compares structure
psql "$SCRATCH_URL" -v ON_ERROR_STOP=1 -f prisma/migrations/0_init/migration.sql
diff <(pg_dump --schema-only --no-owner --no-privileges "$SCRATCH_URL" | grep -v '^--') \
     <(pg_dump --schema-only --no-owner --no-privileges "$PROD_READONLY_URL" | grep -v '^--' | grep -v '_prisma_migrations')

Differences mean schema.prisma does not yet describe production (for example, raw-SQL indexes or triggers Prisma does not model). Add unmodelled objects to the end of migration.sql by hand, or accept them as documented exceptions.

4. Record the baseline on each existing database.

# Shell · migration credentials · writes one row to _prisma_migrations; runs no DDL
# WARNING: run only after the parity check passes for this database.
DATABASE_URL="$PROD_MIGRATION_URL" npx prisma migrate resolve --applied 0_init
DATABASE_URL="$PROD_MIGRATION_URL" npx prisma migrate status

Permanent Fix / Long-Term Pattern

Once baselined, the database changes only through reviewed migrations: generate with prisma migrate dev --create-only, edit for zero-downtime concerns as described in customizing Prisma migrations for zero downtime, and apply with prisma migrate deploy from the pipeline. Remove prisma db push from every non-development workflow — it bypasses migrations entirely, the same problem described for Drizzle in Drizzle push vs migrate in production.

Keep the parity check as a scheduled job rather than a one-off. prisma migrate diff --from-migrations prisma/migrations --to-url "$PROD_READONLY_URL" --shadow-database-url "$SHADOW_URL" reports any difference between what the migration history produces and what production contains — a direct drift detector, complementary to the general approach in detecting production schema drift against a desired state. Shadow database problems that can block migrate dev after adoption are covered in fixing Prisma shadow database failures.

Before and After Baselining Matrix comparing the database before and after baselining across history table, how changes are applied, reproducibility of new environments, and drift detection. Before and After Baselining Aspect Before (db push / legacy) After baseline _prisma_migrations missing or empty 0_init recorded how changes ship db push / manual SQL reviewed migrate deploy new environments db push from a branch migrations from 0_init drift detection none migrate diff vs history
Baselining changes nothing in production's schema — it changes everything about how the next change is made.

Plan the adoption like any other production change, even though it runs no DDL. Announce a short freeze on schema changes from other paths — console fixes, db push from a branch, another tool’s migrations — between the parity check and migrate resolve, because a change in that window makes the recorded baseline wrong the moment it is written. Take the parity dump and record the baseline within minutes of each other, and keep both dumps as artefacts of the adoption so that any later question about what production looked like at baseline time has a definitive answer. If the application runs several databases (per region or per tenant), baseline each one with its own check rather than assuming they are identical; drift between supposedly identical databases is common in systems that were managed with db push.

Verification Checklist

Frequently Asked Questions

What does prisma migrate resolve --applied do? It inserts a row into _prisma_migrations marking the named migration as applied, without running its SQL. It is how you tell Prisma that an existing database already contains what the migration would create.

Should the baseline be generated from schema.prisma or from the database? From schema.prisma, after making sure it describes the database (introspecting with prisma db pull if necessary). The migration history must reproduce the schema Prisma expects, and the parity check confirms that it also matches production.

What about objects Prisma cannot model, such as triggers? Append them to the baseline SQL by hand so new environments get them too, or manage them in later hand-written migrations. Prisma ignores unsupported objects in the schema but executes any SQL you put in migration files.

Do I baseline staging separately? Yes. Every existing database needs its own parity check and its own migrate resolve --applied. New, empty databases simply run the baseline with migrate deploy.