В продакшене вы запускаете prisma migrate deploy и больше ничего. Команда применяет файлы миграций, уже лежащие в prisma/migrations, по порядку, записывает каждую в таблицу _prisma_migrations и останавливается. prisma migrate dev - инструмент разработки: он создаёт новые миграции, требует вторую одноразовую базу (shadow database) и, обнаружив расхождение, предложит сбросить ваши данные. Направить его на боевую базу - самый частый способ её потерять.
Остальное - детали, и именно в них ломаются деплои: где запускается команда, что делать, если она упала на полпути, как взять под управление базу, в которой уже есть таблицы, и почему в Prisma нет «отмены». В примерах здесь PostgreSQL; MySQL и SQL Server ведут себя так же, кроме оговорённых мест.
Что делают migrate dev и migrate deploy#
Миграции Prisma - это обычные SQL-файлы. Вы правите schema.prisma, Prisma вычисляет разницу с последней миграцией и пишет prisma/migrations/<timestamp>_<name>/migration.sql. Эти файлы коммитятся в Git и служат источником истины. Две команды предназначены для разных ситуаций:
| Команда | Для чего | Что затрагивает |
|---|---|---|
prisma migrate dev | Ваш ноутбук | Dev-база и shadow-база |
prisma migrate deploy | CI, staging, продакшен | Только целевая база |
prisma migrate status | Где угодно, только чтение | Читает _prisma_migrations |
prisma db push | Прототипы | Схема, без истории миграций |
migrate dev делает пять вещей за раз: прогоняет историю на shadow-базе, чтобы найти расхождения, применяет ожидающие миграции, создаёт новую миграцию, если schema.prisma изменилась, запускает prisma generate и запускает seed-скрипт, если он настроен. Команда интерактивная и задаёт вопросы вроде «нужно сбросить базу, все данные будут потеряны», когда dev-база перестала совпадать с историей.
migrate deploy делает одно. Читает папку, сравнивает с _prisma_migrations и выполняет то, чего не хватает. Она не создаёт миграции, ничего не сбрасывает, не использует shadow-базу, не проверяет расхождения и не запускает generate и seed. В этой узости и смысл: её безопасно запускать без присмотра и безопасно запускать дважды, потому что второй запуск ничего не находит.
$ npx prisma migrate deployPrisma schema loaded from prisma/schema.prismaDatasource "db": PostgreSQL database "shop", schema "public" at "db.internal:5432"3 migrations found in prisma/migrationsApplying migration `20261012093011_add_orders`The following migration(s) have been applied:migrations/ └─ 20261012093011_add_orders/ └─ migration.sqlAll migrations have been successfully applied.Два момента стоит выделить. Во-первых, prisma db push - не облегчённая версия того же. Он синхронизирует схему напрямую, без истории, поэтому последующему migrate deploy не на что опереться. Используйте его для одноразовых прототипов и никогда - на базе, которую собираетесь хранить. Во-вторых, migrate deploy доверяет папке. Если кто-то отредактировал уже применённый migration.sql, Prisma заметит несовпадение контрольной суммы в dev, а deploy просто продолжит работу с тем, что записано. Никогда не правьте миграцию, которая где-то уже применена; добавьте новую.
Shadow database#
Shadow database - временная база, которую migrate dev создаёт, заполняет, прогоняя все миграции с нуля, сравнивает с вашей текущей схемой и снова удаляет. Так он замечает, что кто-то изменил dev-базу вручную, и строит корректную разницу для следующей миграции.
Для хостинга это важно, потому что пользователю базы нужно право создавать базы данных. На управляемой или общей базе такого права обычно нет, и вы видите:
Error: P3014Prisma Migrate could not create the shadow database. Please make sure thedatabase user has permission to create databases.Решение - дать migrate dev вторую базу, указав её в schema.prisma:
datasource db { provider = "postgresql" url = env("DATABASE_URL") shadowDatabaseUrl = env("SHADOW_DATABASE_URL")}Один раз создайте для неё пустую базу, направьте SHADOW_DATABASE_URL на неё и используйте только для разработки. Prisma очищает её при каждом запуске. Отсюда два правила:
- Продакшену shadow-база не нужна.
migrate deployне читаетshadowDatabaseUrl. Если ваш боевой деплой жалуется на неё, вы запускаете тамmigrate dev. - Shadow-база не должна быть dev-базой. Если оба URL указывают на одну базу, при первом запуске ваши dev-данные будут стёрты.
В Prisma 7 URL подключения перенесены из schema.prisma в файл prisma.config.ts, поэтому то, где именно объявляются url и shadowDatabaseUrl, зависит от установленной мажорной версии. Поведение описано то же; если schema.prisma отвергает строку url, загляните в справочник по конфигурации для вашей версии.
Настройка боевого запуска#
Рецепт короткий. В CI или на сервере, после установки зависимостей и до старта приложения:
- Установите ровно то, что написано в
package-lock.json:npm ci. - Сгенерируйте клиент:
npx prisma generate. Он не входит вmigrate deploy, и приложение, запущенное без него, падает наimport { PrismaClient }. - Примените миграции:
npx prisma migrate deploy. - Запустите приложение.
Держите prisma в dependencies, а не только в devDependencies, если миграции выполняются на той же машине, что и приложение, с NODE_ENV=production; иначе npm ci --omit=dev удалит CLI, который вы собираетесь вызвать.
{ "scripts": { "build": "prisma generate && tsc", "migrate": "prisma migrate deploy", "start": "node dist/server.js", "start:prod": "npm run migrate && npm run start" }, "dependencies": { "@prisma/client": "^6.0.0", "prisma": "^6.0.0" }}Строка подключения берётся из окружения, а не из закоммиченного .env. Назовите её DATABASE_URL и укажите sslmode, если база требует TLS - какое значение вам нужно, разобрано в sslmode в PostgreSQL. Общий подход к секретам описан в статье переменные окружения и секреты.
Запуск миграций при старте
"start:prod": "npm run migrate && npm run start" - вариант, который выбирает большинство небольших приложений: каждый старт применяет всё ожидающее, а затем начинает обслуживание. У него есть реальные плюсы: не нужно помнить о лишнем шаге, а схема всегда соответствует запускаемому коду. Минусы те же, что у любой схемы «миграции при старте»; статья про миграции EF Core разбирает их на примере .NET:
- Упавшая миграция - это упавший старт. Процесс завершается с ненулевым кодом, платформа его перезапускает, и он падает снова. Видимый симптом - цикл перезапусков, а причина в нескольких строках выше по журналу.
- Несколько экземпляров гоняются друг с другом. Во время миграции Prisma берёт advisory lock в PostgreSQL и MySQL, поэтому два одновременно стартующих экземпляра не выполнят одну миграцию дважды: второй ждёт и затем ничего не находит. Если ждать слишком долго, блокировка истекает по таймауту (по умолчанию около десяти секунд), и этот экземпляр не запускается. Это страховка, а не план.
- Учётные данные приложения могут менять схему. Всё, с чем приложение подключается, может заодно удалять таблицы.
- Долгие миграции задерживают готовность. Миграция, перестраивающая индекс большой таблицы, выполняется, пока ваш сайт недоступен, ведь приложение ещё не стартовало.
&& важен. С ; приложение запустится, даже если миграция упала, и будет работать со схемой, которой не соответствует. С && сбой останавливает старт и виден сразу.
Для одного экземпляра на одном сервере запуск при старте - разумный выбор. Как только появляются два экземпляра, большая таблица или команда, вынесите миграцию в отдельный шаг: задачу в CI, release-команду или разовый запуск до переключения на новую версию. Последовательность та же, что в деплое без простоя на небольшом сервере, с одной дополнительной строкой перед переключением.
На RE:NODE
На тарифе приложений Node.js миграция входит в последовательность запуска, как и везде: ваша команда запуска выполняет npm run migrate && npm run start, а DATABASE_URL задан среди переменных окружения сервера на вкладке Startup. Тариф PostgreSQL из раздела хостинг баз данных выдаёт хост, порт, пользователя и пароль, сгенерированные для каждого сервера; соберите из них URL и не храните его в репозитории. Если вы деплоите из GitHub, pull при каждом старте приносит новые файлы миграций до выполнения команды - см. деплой Node-приложения из GitHub. Запланируйте backup на вкладке Schedules перед релизом с разрушающей миграцией или сделайте его вручную на вкладке Backups в тарифе сервера приложения.
Baseline существующей базы#
Если в базе уже есть таблицы, но нет таблицы _prisma_migrations, migrate deploy отказывается её трогать:
Error: P3005The database schema is not empty. Read more about how to baseline an existingproduction database: https://pris.ly/d/migrate-baselineBaseline означает сказать Prisma: «в этой базе уже есть всё до миграции X, не запускай её снова». Вы создаёте одну миграцию, описывающую текущее состояние, помечаете её применённой без выполнения, и дальше запускаются только новые.
- Приведите
schema.prismaв соответствие с живой базой. Если вы подключили Prisma к существующей базе,npx prisma db pullзапишет схему из неё. Закоммитьте результат. - Создайте папку baseline. Её имя должно сортироваться раньше всех последующих миграций, подойдёт префикс
0_:
$ 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- Сообщите Prisma, что эта миграция уже применена в целевой базе:
$ npx prisma migrate resolve --applied 0_initMigration 0_init marked as applied.- Выполните
npx prisma migrate deploy. Он сообщит, что ожидающих миграций нет. Все добавляемые дальше миграции применяются как обычно.
Флаг --to-schema-datamodel в новых выпусках переименован (замена - --to-schema), поэтому, если ваша версия его отвергает, выполните npx prisma migrate diff --help. Сначала проделайте всё на копии. Восстановите вчерашний дамп в пробную базу, выполните шаги на ней и убедитесь, что npx prisma migrate status пишет «Database schema is up to date!».
Новые окружения - CI, свежая staging-база, новый разработчик - baseline не делают. У них пустая база, поэтому migrate deploy выполняет 0_init по-настоящему, а затем остальные. Поэтому SQL baseline обязан точно описывать схему: если diff пропустил индекс или значение по умолчанию, свежая база и продакшен тихо разойдутся. После baseline сравните их через prisma migrate diff --from-url для боевой базы и --to-schema-datamodel для схемы и ожидайте пустой результат.
Когда миграция падает#
Миграция выполняется как последовательность SQL-операторов, и последствия сбоя зависят от базы. PostgreSQL по возможности оборачивает миграцию в транзакцию, поэтому упавшая миграция обычно не оставляет наполовину применённых изменений. MySQL фиксирует DDL-операторы по ходу, и сбой на середине оставляет применённой первую половину. В любом случае Prisma записывает сбой в _prisma_migrations со временем начала и без времени завершения и отказывается продолжать:
Error: P3009migrate found failed migrations in the target database, new migrations willnot be applied. Read more about how to resolve migration issues in aproduction database: https://pris.ly/d/migrate-resolveThe `20261012093011_add_orders` migration started at 2026-10-12 09:30:11 UTC failedКаждый следующий migrate deploy упирается в это препятствие, пока вы не разберётесь. Так задумано. Порядок действий:
- Прочитайте настоящую ошибку.
P3009лишь сообщает, что раньше что-то упало. Исходное сообщение -P3018в журнале упавшего запуска, а под ним ошибка SQL. Типичные причины: столбецNOT NULL, добавленный в таблицу с данными, уникальный индекс по дублирующимся данным, внешний ключ на несуществующие строки, таймаут оператора. - Осмотрите базу. Соответствует ли схема замыслу миграции полностью, частично или вообще не тронута? В PostgreSQL обычно не тронута; в MySQL проверьте вручную.
- Приведите базу в известное состояние. Либо отмените частичное изменение с помощью SQL, либо доведите его до конца с помощью SQL.
- Сообщите Prisma, что вы сделали:
# the migration did not apply (or you undid it): it will run again$ npx prisma migrate resolve --rolled-back 20261012093011_add_orders# you completed its effect by hand: never run it$ npx prisma migrate resolve --applied 20261012093011_add_orders- Исправьте файл миграции, если использовали `--rolled-back`. Править миграцию, которая нигде не применилась успешно, можно: её нигде не применяли. Если на staging она прошла, а на продакшене упала, не правьте её: добавьте новую миграцию с исправляющими действиями и выполните
--appliedдля сломанной после ручного ремонта боевой базы. - Снова выполните
npx prisma migrate deploy.
migrate resolve меняет только учёт в _prisma_migrations. Он никогда не выполняет ваш SQL. Поэтому он безопасен и поэтому не может починить схему за вас. Проверить состояние можно командой npx prisma migrate status: она перечисляет применённые, ожидающие и упавшие миграции и завершается с ненулевым кодом, если база не актуальна, - удобный барьер для CI.
Родственное сообщение P3008 говорит, что миграция уже записана как применённая; вы выполнили --applied дважды, это безвредно. P3006 означает, что миграция упала на shadow-базе, то есть историю нельзя воспроизвести с нуля, чаще всего потому, что ранняя миграция зависит от того, что существовало только в исходной базе. Чините это в dev, а не на продакшене.
Откаты: down-миграций нет#
В Prisma Migrate нет down. Миграции идут только вперёд, а migrate resolve --rolled-back - это учёт, а не отмена. Остаются три реальных варианта, в порядке предпочтения:
- Откат вперёд. Напишите новую миграцию, отменяющую изменение: верните столбец, пересоздайте индекс. Для аддитивных изменений это быстро и честно сохраняет историю.
- Откатите приложение, а не схему. Если предыдущая версия кода работает и с новой схемой, разверните её заново и не трогайте базу. Это возможно, только если миграция была аддитивной, а ради этого их так и проектируют - см. миграции без простоя о схеме expand и contract.
- Восстановите backup. Единственный ответ на миграцию, уничтожившую данные. Всё записанное после backup теряется, поэтому это крайняя мера и причина, по которой backup делают до деплоя.
Prisma может сама написать SQL для обратного изменения:
$ npx prisma migrate diff \ --from-schema-datamodel prisma/schema.prisma \ --to-migrations prisma/migrations \ --shadow-database-url "$SHADOW_DATABASE_URL" \ --scriptКоманда сравнивает два состояния и печатает SQL для перехода из одного в другое. Прочитайте его перед запуском; это отправная точка для ручного отката, а не кнопка. Удалить столбец и вернуть его данные - не одна и та же операция.
Чек-лист деплоя#
Релиз с миграцией должен ответить на эти вопросы до выкатки:
| Вопрос | Зачем |
|---|---|
| Миграция аддитивная? | Старый код работает во время переключения |
| Блокирует ли она большую таблицу? | Сборка индексов и смена типов блокируют запись |
| Backup есть и восстанавливается? | Это единственная отмена |
| Запускалась ли на копии боевых данных? | Ограничения падают на реальных строках, а не на пустых |
migrate status чист заранее? | Упавшая миграция блокирует деплой |
Перед каждым релизом, затрагивающим большие таблицы или добавляющим ограничения, запускайте миграцию на восстановленной копии вчерашнего дампа. Столбец NOT NULL, проходящий на пустой dev-базе, падает на 200 000 строк боевой, и гораздо дешевле выяснить это на пробной базе, чем в цикле перезапусков.
Предпочитайте миграции, безопасные в два шага: добавьте столбец, допускающий NULL, и выкатите, заполните его, затем добавьте ограничение в следующей миграции. SQL, который Prisma генерирует для столбца NOT NULL без значения по умолчанию, - это как раз оператор, падающий на заполненной таблице.
FAQ#
Можно ли запускать prisma migrate dev в продакшене?
Нет. Ему нужна shadow-база, он интерактивный, может предложить сбросить ваши данные и создаёт миграции, которые должны были пройти ревью в Git. Используйте prisma migrate deploy.
Запускает ли prisma migrate deploy команду prisma generate?
Нет. Запускайте npx prisma generate сами, на этапе сборки. Без неё клиент в node_modules отсутствует или устарел, и приложение падает при импорте PrismaClient.
Почему я получаю P3005 на базе, которая уже работает?
Потому что в ней есть таблицы, но нет таблицы _prisma_migrations. Сделайте baseline: создайте миграцию 0_init через prisma migrate diff и пометьте её применённой командой prisma migrate resolve --applied 0_init.
Как отменить последнюю миграцию Prisma?
Никак. Напишите новую миграцию, которая её отменяет, или восстановите backup. migrate resolve --rolled-back меняет только записи Prisma об упавшей миграции, а не ваши таблицы.
Безопасно ли запускать migrate deploy сразу из двух экземпляров?
В основном да. Prisma берёт advisory lock в PostgreSQL и MySQL, поэтому второй экземпляр ждёт, а не повторяет работу, но ожидание может истечь по таймауту. Запускайте миграции одним шагом до старта экземпляров.
Где хранится история миграций?
В двух местах, которые обязаны совпадать: в папке prisma/migrations в Git и в таблице _prisma_migrations каждой базы. migrate status показывает, где они расходятся.


