Why Prisma Migrations Fail in Production
Prisma migrations work perfectly in development. Then you deploy and everything breaks. Here is why production is a different beast, and what to do about it.
Prisma is one of the most popular ways to manage a database in a JavaScript or TypeScript app. AI tools reach for it constantly. It has a clean API, good type safety, and a migration system that feels almost magical when you are working locally.
Then you deploy to production and the migration fails. Or worse, it does not fail at all. It just does not run, and your app crashes on startup because the database schema does not match what the code expects.
Here is why this keeps happening and what you can actually do about it.
What migrations are and why they matter
When you change your database schema in Prisma, you update a file called schema.prisma. This file describes your tables, columns, and relationships in a clean readable format. But Prisma does not automatically apply those changes to your database. Instead, it generates a migration file, a SQL script that describes exactly how to go from the current state of your database to the new one.
These migration files live in a folder called prisma/migrations in your project. Every time you change your schema and run prisma migrate dev, a new file gets added. Over time, this folder builds up a complete history of every change ever made to your database, in the right order.
This system works beautifully in development. The problem is that on a production server, nobody runs those migrations automatically. The code ships with a new schema. The database stays on the old one. And the app starts making queries against columns or tables that do not exist yet.
The most common way it breaks
The typical failure looks like this. You add a new column to your schema in development, run your migrations locally, everything works. You deploy the new code to production. The app crashes immediately with an error about an unknown column or a missing table.
What happened is that the migration ran on your local database but never ran on the production database. The code knows about the new column. The database does not. Every query that touches that column fails.
The fix is to run prisma migrate deploy on your production database before or during your deployment. Unlike prisma migrate dev, which is designed for development and will ask questions and generate new files, prisma migrate deploy only applies migrations that have not been applied yet. It is safe to run in production.
The tricky part is that most hosting setups do not do this automatically. You have to wire it in yourself, either as a step in your deployment script, a startup command, or a one-off command you run manually after each deploy.
The drift problem
A subtler issue is schema drift. This happens when your production database gets out of sync with your migration history in a way that Prisma does not expect.
The most common cause is making changes directly to the production database without going through migrations. Maybe you added a column manually through a database GUI to fix an urgent issue. Maybe someone ran a raw SQL script. Maybe a previous failed migration left the database in a partially changed state. Whatever the cause, the database is now in a state that does not match what Prisma's migration history says it should be.
When this happens, prisma migrate deploy will refuse to run because it detects the drift. Prisma compares what it expects the database to look like against what it actually looks like, and if they do not match, it stops.
The solution is prisma migrate resolve, which lets you tell Prisma to either mark a migration as already applied (if you applied the changes manually) or roll back to a known state. It requires understanding exactly what state your database is in, which can be difficult if things went wrong in the middle of a previous migration.
The failed migration state
If a migration fails halfway through, you can end up in a state that is hard to recover from. The first half of the migration has been applied to the database. The second half has not. Prisma records the migration as failed in a table it maintains called _prisma_migrations.
Running prisma migrate deploy again will not retry the failed migration. Prisma sees it as failed and stops. You need to either fix the underlying issue and mark the migration as rolled back, or manually complete whatever the migration was trying to do and mark it as applied.
This is one of the reasons database migrations need to be written carefully. A migration that adds a column and then tries to backfill data across a large table can fail partway through the backfill, leaving you with a half-populated column and a stuck migration history.
Why prisma db push is not the answer
When you are prototyping locally, prisma db push feels great. It just applies your current schema to the database without generating migration files. It is fast and frictionless.
The problem is that it bypasses the migration system entirely. There is no history of what changed. There is no way to apply the same changes to another database in a controlled way. If you use prisma db push in development and then try to use prisma migrate deploy in production, Prisma has no record of the changes and cannot figure out what to apply.
prisma db push is fine for early local experimentation. The moment you have a production database with real data, you need proper migrations.
The transaction trap
By default, Prisma wraps each migration in a database transaction. This means if any part of the migration fails, the whole thing rolls back cleanly. That is usually a good thing.
The exception is certain types of operations that cannot run inside a transaction in some databases. Adding an index to a large table in PostgreSQL, for example, cannot be done transactionally with CREATE INDEX CONCURRENTLY, which is the version that does not lock the table while the index is being built. If you try to run that inside a transaction, it fails.
For these cases, Prisma lets you mark a migration as non-transactional by adding -- This migration was generated with the --create-only flag at the top and then manually editing the SQL. It is an edge case, but it comes up on larger tables.
The practical checklist
If your Prisma migrations are failing in production, work through this in order.
First, make sure you are running prisma migrate deploy at all. It needs to run against your production database connection string, before your app starts serving traffic.
Second, check the _prisma_migrations table in your database. It records every migration and its status. A failed migration will be marked there and that is why subsequent runs are blocked.
Third, if you have schema drift, use prisma migrate resolve to get back to a consistent state. This requires understanding exactly what your database looks like right now versus what Prisma expects.
Fourth, if you are using prisma db push anywhere in your production workflow, stop. Switch to proper migrations.
Migrations are not glamorous. They are one of those things that feel like unnecessary friction until the day you lose production data because something went wrong during a schema change. The system exists to protect you.
Deploy your app in minutes
Jetpacked handles Docker, HTTPS, databases, and deployments — so you can focus on building.
Launch your app free