Blog
4 min read

Prisma Migrations in Production: migrate dev vs migrate deploy

How to run Prisma migrations safely: migrate dev locally, migrate deploy in production, why db push and migrate reset don't belong near real data, where to run migrations in CI/CD, handling failed migrations with migrate resolve, baselining an existing database, and avoiding destructive changes.

Prisma has several commands that change your database schema. Using the wrong one in production is one of the most common ways AI-built apps lose data. Here's which to use where. (Database migrations explained, What is an ORM?)

The commands

Command What it does Where
prisma migrate dev Generates a new migration from schema changes, applies it, may reset the dev database Development only
prisma migrate deploy Applies pending migration files, in order. Never generates, never resets Production, staging, CI
prisma db push Syncs the database to the schema without migration files; may drop data Prototyping only
prisma migrate reset Drops the database and re-runs all migrations Development only
prisma migrate status Shows which migrations are applied/pending Anywhere
prisma migrate resolve Marks a migration as applied or rolled back Fixing failed deploys

Production gets migrate deploy. Nothing else.

The workflow

1. Develop

Change schema.prisma, then:

npx prisma migrate dev --name add_invoice_due_date

This creates prisma/migrations/20261002_add_invoice_due_date/migration.sql. Read the SQL. It's what will run against production.

2. Commit the migration files

The migrations/ folder is part of your code. Review it in pull requests like any other change. (What is a pull request?)

3. Deploy

In your deployment, before the new app version starts:

npx prisma migrate deploy

It applies any migrations not yet recorded in the _prisma_migrations table, and does nothing if there are none.

Where to run it:

  • a release step in your deploy pipeline, or
  • a separate job before switching traffic, or
  • at container start (simple, but if several instances start at once, let one run it — Prisma takes an advisory lock to avoid concurrent runs). (Postgres advisory locks)

(Deploy to a VPS with GitHub Actions)

Configuration

Recent Prisma versions configure the datasource URL in prisma.config.ts rather than only in schema.prisma. Make sure production commands see the production connection string and, if you use a connection pooler, that migrations use a direct connection — migrations don't work well through transaction-mode poolers. (Postgres connection pooling)

Why db push doesn't belong in production

db push makes the database match the schema by whatever means necessary. Rename a field in the schema and it may drop the old column and create a new one — data gone. It also leaves no migration history to review or replay. Great for prototypes; dangerous for real data.

AI coding agents sometimes reach for db push --accept-data-loss or migrate reset to "fix" a migration error. Block those commands for agents working against anything but a throwaway database. (How to stop an AI agent deleting your production database, Claude Code settings)

Dangerous changes to watch for

Read every generated migration for:

  • DROP COLUMN / DROP TABLE — irreversible without a backup.
  • Renames — Prisma may generate drop + add; edit the SQL to ALTER TABLE ... RENAME COLUMN.
  • New NOT NULL columns without defaults on tables with data — fails or needs a backfill.
  • Type changes that rewrite large tables and lock them.
  • New indexes on big tables — use CREATE INDEX CONCURRENTLY in a custom migration.

For large tables, use the expand-and-contract pattern: add the new column, backfill, switch code, then remove the old one in a later deploy. (Postgres migrations on large tables, Zero-downtime deploys)

You can edit the generated migration.sql before committing, or create an empty migration with --create-only and write the SQL yourself.

When a migration fails in production

migrate deploy stops and records the failure. Further deploys refuse to run until you resolve it.

  1. Look at the error and the database state (migrate status).
  2. Fix the database manually or fix the migration.
  3. Tell Prisma what happened:
npx prisma migrate resolve --rolled-back 20261002_add_invoice_due_date
# or, if you completed it manually:
npx prisma migrate resolve --applied 20261002_add_invoice_due_date

Never edit a migration that has already run successfully anywhere — create a new one.

Existing database without migrations (baselining)

If production was built with db push or by hand, create a baseline migration from the current schema and mark it as applied, so future migrate deploy runs start from there. Prisma's docs describe this as "baselining".

Always have a backup first

Take (or confirm) a backup before running migrations that change or remove data, and know how to restore it. (Postgres backup and restore)


EasySpawn runs your app and its Postgres database with daily backups already in place, and preview deployments let you try a migration on a branch before it touches production. See how it works or join the waitlist.

Related: Database Migrations Explained · Postgres Migrations on Large Tables · How to Stop an AI Agent From Deleting Your Production Database · What Is an ORM?

Keep reading