RE:NODE

Prisma migrate deploy v produkci: baseline a obnova po chybě

Text: Tým RE:NODE
13 min čtení


V produkci spouštíš prisma migrate deploy a nic jiného. Příkaz aplikuje soubory migrací, které už leží v prisma/migrations, popořadě, každou zapíše do tabulky _prisma_migrations a skončí. prisma migrate dev je vývojový nástroj: vytváří nové migrace, potřebuje druhou odhazovací databázi (shadow database) a když najde rozdíl, nabídne ti smazání dat. Namířit ho na produkční databázi je nejčastější způsob, jak o ni přijít.

Zbytek jsou detaily a právě v nich deploye selhávají: kde se příkaz spouští, co dělat, když spadne v půlce, jak převzít databázi, která už tabulky má, a proč Prisma nemá „zpět“. Příklady používají PostgreSQL; MySQL a SQL Server se chovají stejně, kromě míst, kde je to řečeno.

Co dělají migrate dev a migrate deploy#

Migrace Prisma jsou obyčejné SQL soubory. Upravíš schema.prisma, Prisma spočítá rozdíl oproti poslední migraci a zapíše prisma/migrations/<timestamp>_<name>/migration.sql. Tyto soubory se commitují do Gitu a jsou zdrojem pravdy. Oba příkazy jsou pro různé situace:

PříkazUrčeno proDotýká se
prisma migrate devTvůj notebookVývojové a shadow databáze
prisma migrate deployCI, staging, produkceJen cílové databáze
prisma migrate statusKdekoli, jen čteníČte _prisma_migrations
prisma db pushPrototypySchéma, bez historie migrací

migrate dev dělá pět věcí najednou: přehraje historii na shadow databázi, aby odhalil rozdíly, aplikuje čekající migrace, vytvoří novou migraci, pokud se změnilo schema.prisma, spustí prisma generate a spustí seed skript, pokud je nastaven. Je interaktivní a ptá se na věci jako „musíme databázi resetovat, všechna data budou ztracena“, když vývojová databáze přestala odpovídat historii.

migrate deploy dělá jedinou věc. Přečte složku, porovná ji s _prisma_migrations a spustí, co chybí. Nevytváří migrace, nic neresetuje, nepoužívá shadow databázi, nekontroluje rozdíly a nespouští generate ani seed. V té úzkosti je smysl: dá se bezpečně spouštět bez dozoru i dvakrát, protože druhé spuštění nenajde nic k práci.

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

Dvě věci je třeba zdůraznit. Zaprvé, prisma db push není lehčí verze téhož. Synchronizuje schéma přímo, bez historie, takže pozdější migrate deploy nemá na čem stavět. Používej ho pro zahazované prototypy a nikdy na databázi, kterou chceš zachovat. Zadruhé, migrate deploy věří složce. Pokud někdo upravil už aplikovaný migration.sql, Prisma si nesouladu kontrolního součtu všimne ve vývoji, ale deploy prostě pokračuje s tím, co je zaznamenáno. Nikdy neupravuj migraci, která byla někde aplikována; přidej novou.

Shadow database#

Shadow database je dočasná databáze, kterou migrate dev vytvoří, naplní přehráním všech migrací od začátku, porovná s tvým aktuálním schématem a zase smaže. Takhle pozná, že někdo ručně změnil vývojovou databázi, a vytvoří správný rozdíl pro další migraci.

Pro hosting na tom záleží, protože uživatel databáze musí mít právo vytvářet databáze. Na spravované nebo sdílené databázi ho obvykle nemá a uvidíš:

code
Error: P3014Prisma Migrate could not create the shadow database. Please make sure thedatabase user has permission to create databases.

Řešení je dát migrate dev druhou databázi, uvedenou v schema.prisma:

prisma/schema.prisma
datasource db {  provider          = "postgresql"  url               = env("DATABASE_URL")  shadowDatabaseUrl = env("SHADOW_DATABASE_URL")}

Jednou pro ni vytvoř prázdnou databázi, nasměruj na ni SHADOW_DATABASE_URL a používej ji jen pro vývoj. Prisma ji při každém běhu vyprázdní. Plynou z toho dvě pravidla:

  • Produkce shadow databázi nikdy nepotřebuje. migrate deploy shadowDatabaseUrl nečte. Pokud si tvůj produkční deploy na jednu stěžuje, spouštíš tam migrate dev.
  • Shadow databáze nesmí být vývojová databáze. Pokud obě URL míří na stejnou databázi, první běh smaže tvá vývojová data.

Prisma 7 přesunula připojovací URL ze schema.prisma do souboru prisma.config.ts, takže to, kde přesně deklaruješ url a shadowDatabaseUrl, závisí na nainstalované hlavní verzi. Chování je stejné; pokud schema.prisma odmítne řádek url, podívej se do reference konfigurace pro svou verzi.

Nastavení produkčního běhu#

Postup je krátký. V CI nebo na serveru, po instalaci závislostí a před startem aplikace:

  1. Nainstaluj přesně to, co říká package-lock.json: npm ci.
  2. Vygeneruj klienta: npx prisma generate. Není součástí migrate deploy a aplikace spuštěná bez něj spadne na import { PrismaClient }.
  3. Aplikuj migrace: npx prisma migrate deploy.
  4. Spusť aplikaci.

Měj prisma v dependencies, ne jen v devDependencies, pokud se migrace spouštějí na stejném stroji jako aplikace s NODE_ENV=production; jinak npm ci --omit=dev odstraní CLI, které se chystáš zavolat.

package.json
{  "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"  }}

Připojovací řetězec pochází z prostředí, nikdy z commitnutého .env. Pojmenuj ho DATABASE_URL a uveď sslmode, pokud databáze vyžaduje TLS - kterou hodnotu chceš, vysvětluje sslmode v PostgreSQL. Obecné nakládání s tajemstvími je v článku proměnné prostředí a tajemství.

Spouštění migrací při startu

"start:prod": "npm run migrate && npm run start" je varianta, kterou používá většina malých aplikací: každý start aplikuje, co čeká, a pak začne obsluhovat. Má skutečné výhody - není žádný krok, na který bys zapomněl, a schéma vždy odpovídá kódu, který se spouští. Nevýhody jsou stejné jako u každého nastavení „migrace při startu“; článek o migracích EF Core je rozebírá v řeči .NET:

  • Selhaná migrace je selhaný start. Proces skončí nenulovým kódem, platforma ho restartuje a on selže znovu. Viditelný příznak je smyčka restartů; příčina je o pár řádků výš v logu.
  • Více instancí se předbíhá. Prisma při migraci bere advisory lock v PostgreSQL a MySQL, takže dvě instance startující současně nespustí tutéž migraci dvakrát: druhá čeká a pak nenajde nic k práci. Pokud čeká příliš dlouho, zámek vyprší (výchozí hodnota je asi deset sekund) a ta instance se nespustí. Je to záchranná síť, ne plán.
  • Přihlašovací údaje aplikace mohou měnit schéma. Cokoli, čím se aplikace připojuje, může také mazat tabulky.
  • Pomalé migrace zdržují připravenost. Migrace, která přestavuje index na velké tabulce, běží, zatímco je web nedostupný, protože aplikace ještě nenastartovala.

Na && záleží. Se ; aplikace nastartuje, i když migrace selhala, a pak běží proti schématu, kterému neodpovídá. S && selhání start zastaví a je vidět.

Pro jednu instanci na jednom serveru je spouštění při startu rozumná volba. Jakmile máš dvě instance, velkou tabulku nebo tým, přesuň migraci do samostatného kroku: úlohy v CI, release příkazu nebo jednorázového spuštění před přepnutím na novou verzi. Postup je stejný jako v deployích bez výpadku na malém serveru, s jedním řádkem navíc před přepnutím.

Na RE:NODE

Na tarifu aplikací Node.js je migrace součástí startovací sekvence jako všude jinde: tvůj startovací příkaz spouští npm run migrate && npm run start a DATABASE_URL je nastavena mezi proměnnými prostředí serveru na záložce Startup. Tarif PostgreSQL z hostingu databází dává hostitele, port, uživatele a heslo generované pro každý server; z nich sestav URL a nedávej ji do repozitáře. Pokud nasazuješ z GitHubu, pull při každém startu přinese nové soubory migrací dřív, než příkaz poběží - viz nasazení Node aplikace z GitHubu. Před vydáním s destruktivní migrací naplánuj zálohu na záložce Schedules nebo ji udělej ručně na záložce Backups u tarifu aplikačního serveru.

Baseline existující databáze#

Pokud databáze už má tabulky, ale nemá tabulku _prisma_migrations, migrate deploy se jí odmítne dotknout:

code
Error: P3005The database schema is not empty. Read more about how to baseline an existingproduction database: https://pris.ly/d/migrate-baseline

Baseline znamená říct Prisma: „tato databáze už obsahuje všechno až po migraci X, nespouštěj ji znovu“. Vytvoříš jednu migraci popisující současný stav, označíš ji za aplikovanou, aniž by se spustila, a od té chvíle běží jen nové migrace.

  1. Uveď schema.prisma do souladu s živou databází. Pokud jsi Prisma nasadil na existující databázi, npx prisma db pull z ní schéma zapíše. Commitni ho.
  2. Vytvoř složku baseline. Její název se musí řadit před všechny pozdější migrace, takže stačí předpona 0_:
bash
$ 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
  1. Řekni Prisma, že tato migrace je v cílové databázi už aplikována:
bash
$ npx prisma migrate resolve --applied 0_initMigration 0_init marked as applied.
  1. Spusť npx prisma migrate deploy. Ohlásí, že nic nečeká. Každá migrace, kterou přidáš potom, se aplikuje normálně.

Příznak --to-schema-datamodel byl v novějších vydáních přejmenován (náhrada je --to-schema), takže pokud ho tvá verze odmítne, spusť npx prisma migrate diff --help. Nejdřív to zkus na kopii. Obnov včerejší dump do zkušební databáze, projdi kroky na ní a ověř, že npx prisma migrate status hlásí „Database schema is up to date!“.

Nová prostředí - CI, čerstvá staging databáze, nový vývojář - baseline nedělají. Mají prázdnou databázi, takže migrate deploy spustí 0_init doopravdy a pak zbytek. Proto musí být SQL baseline věrným popisem schématu: pokud diff vynechal index nebo výchozí hodnotu, čerstvá databáze a produkce se potichu liší. Po baseline je porovnej pomocí prisma migrate diff --from-url na produkci a --to-schema-datamodel na schéma a očekávej prázdný výsledek.

Když migrace selže#

Migrace běží jako řada SQL příkazů a co se stane při selhání, závisí na databázi. PostgreSQL obalí migraci transakcí, kde to jde, takže selhaná migrace obvykle nezanechá napůl aplikované změny. MySQL potvrzuje DDL příkazy průběžně; selhání uprostřed nechá aplikovanou první polovinu. V každém případě Prisma zaznamená selhání do _prisma_migrations s časem začátku a bez času dokončení a odmítne pokračovat:

code
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

Každý další migrate deploy narazí na tuto zeď, dokud se s tím nevypořádáš. Je to záměr. Postup:

  1. Přečti si skutečnou chybu. P3009 říká jen to, že něco dříve selhalo. Původní zpráva je P3018 v logu selhaného běhu a pod ní chyba SQL. Typické příčiny: sloupec NOT NULL přidaný do tabulky, která už má řádky, unikátní index nad duplicitními daty, cizí klíč na neexistující řádky, vypršení časového limitu příkazu.
  2. Prohlédni databázi. Odpovídá schéma záměru migrace, částečně, nebo je nedotčené? V PostgreSQL obvykle nedotčené; v MySQL zkontroluj ručně.
  3. Dostaň databázi do známého stavu. Buď částečnou změnu vrať pomocí SQL, nebo ji pomocí SQL dokonči.
  4. Řekni Prisma, co jsi udělal:
bash
# 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
  1. Oprav soubor migrace, pokud jsi použil `--rolled-back`. Upravit migraci, která nikde úspěšně neproběhla, je v pořádku, protože nikde nebyla úspěšně aplikována. Pokud prošla na stagingu a selhala v produkci, neupravuj ji: přidej novou migraci, která provede opravnou práci, a po ruční opravě produkce spusť --applied na rozbitou.
  2. Znovu spusť npx prisma migrate deploy.

migrate resolve mění jen evidenci v _prisma_migrations. Nikdy nespouští tvé SQL. Proto je bezpečný a proto za tebe nemůže opravit schéma. Stav zkontroluješ příkazem npx prisma migrate status, který vypíše aplikované, čekající a selhané migrace a skončí nenulovým kódem, pokud databáze není aktuální - užitečná brána v CI.

Příbuzná zpráva P3008 říká, že migrace je už zaznamenána jako aplikovaná; spustil jsi --applied dvakrát, což je neškodné. P3006 říká, že migrace selhala na shadow databázi, tedy že historii nelze přehrát od prázdné databáze, nejčastěji proto, že raná migrace závisí na něčem, co existovalo jen v původní databázi. Oprav to ve vývoji, ne v produkci.

Rollbacky: žádné down migrace#

Prisma Migrate nemá down. Migrace jdou jen dopředu a migrate resolve --rolled-back je účetnictví, ne vrácení zpět. Máš tři skutečné možnosti, v pořadí podle preference:

  • Rollforward. Napiš novou migraci, která změnu vrátí: přidej sloupec zpět, znovu vytvoř index. U aditivních změn je to rychlé a historie zůstane poctivá.
  • Vrať aplikaci, ne schéma. Pokud předchozí verze kódu funguje i s novým schématem, nasaď ji znovu a databázi nech být. Jde to jen tehdy, když byla migrace aditivní, což je smysl takového návrhu - viz migrace bez výpadku o vzoru expand a contract.
  • Obnov zálohu. Jediná odpověď na migraci, která zničila data. Vše zapsané po záloze je ztraceno, takže je to poslední možnost a důvod, proč záloha přichází před deployem.

Můžeš Prisma nechat napsat zpětné SQL:

bash
$ npx prisma migrate diff \    --from-schema-datamodel prisma/schema.prisma \    --to-migrations prisma/migrations \    --shadow-database-url "$SHADOW_DATABASE_URL" \    --script

Příkaz porovná dva stavy a vypíše SQL, které vede z jednoho do druhého. Přečti si ho, než ho spustíš; je to výchozí bod pro ručně psaný rollback, ne tlačítko. Zahodit sloupec a dostat zpět jeho data není stejná operace.

Kontrolní seznam deploye#

Vydání, které obsahuje migraci, by mělo před nasazením odpovědět na tyto otázky:

OtázkaProč
Je migrace aditivní?Starý kód běží i během přepnutí
Zamyká velkou tabulku?Stavba indexů a změny typů blokují zápis
Záloha existuje a obnoví se?Je to jediné vrácení zpět
Běžela na kopii produkčních dat?Omezení selhávají na skutečných řádcích, ne na prázdných
Je migrate status předem čistý?Selhaná migrace blokuje deploy

Před každým vydáním, které se dotýká velkých tabulek nebo přidává omezení, spusť migraci na obnovené kopii včerejšího dumpu. Sloupec NOT NULL, který projde na prázdné vývojové databázi, selže na 200 000 řádcích v produkci a je mnohem levnější zjistit to na zkušební databázi než ve smyčce restartů.

Preferuj migrace bezpečné ve dvou krocích: přidej sloupec povolující NULL a nasaď, doplň data, potom přidej omezení v pozdější migraci. SQL, které Prisma vygeneruje pro sloupec NOT NULL bez výchozí hodnoty, je přesně příkaz, který na zaplněné tabulce selže.

FAQ#

Můžu v produkci spustit prisma migrate dev?

Ne. Potřebuje shadow databázi, je interaktivní, může navrhnout smazání tvých dat a generuje migrace, které měly projít kontrolou v Gitu. Použij prisma migrate deploy.

Spouští prisma migrate deploy příkaz prisma generate?

Ne. Spusť npx prisma generate sám, v kroku sestavení. Bez něj klient v node_modules chybí nebo je zastaralý a aplikace při importu PrismaClient spadne.

Proč dostávám P3005 na databázi, která už funguje?

Protože má tabulky, ale nemá tabulku _prisma_migrations. Udělej baseline: vytvoř migraci 0_init pomocí prisma migrate diff a označ ji jako aplikovanou příkazem prisma migrate resolve --applied 0_init.

Jak vrátit poslední migraci Prisma?

Nevrátíš. Napiš novou migraci, která ji obrátí, nebo obnov zálohu. migrate resolve --rolled-back mění jen záznamy Prisma o selhané migraci, ne tvé tabulky.

Je bezpečné spustit migrate deploy ze dvou instancí najednou?

Většinou ano. Prisma bere advisory lock v PostgreSQL a MySQL, takže druhá instance čeká, místo aby práci opakovala, ale čekání může vypršet. Spouštěj migrace jako jediný krok před startem instancí.

Kde žije historie migrací?

Na dvou místech, která se musí shodovat: ve složce prisma/migrations v Gitu a v tabulce _prisma_migrations každé databáze. migrate status ukáže, kde se liší.

Pomohlo vám to?