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.
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.
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.
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.