本番のデータベースに対するスキーマ変更は、アプリのデプロイとは別格の緊張を伴います。コードのバグは切り戻せますが、データを壊すマイグレーションは切り戻せないことがあるからです。「列を消したらデータが消えた」「migrate dev を本番で打ってDBがリセットされた」——こうした事故は、コマンドの役割を理解していれば100%防げます。
この記事は、Prisma Migrate(v7)を本番で安全に運用するための実装ガイドです。スキーマ設計そのものはPrisma スキーマ設計&リレーション完全ガイドに、Prisma 全体像はPrisma ORM 本番運用ガイド(v7)にあります。本記事は「設計したスキーマを、どうやってデータを壊さず本番へ適用し続けるか」に集中します。
この記事のルール:コマンド・挙動は Prisma 公式ドキュメント(2026年6月時点、v7系) に基づきます。
prisma.config.tsによるシャドウDB/シード設定、driver adapter 必須化、自動シードの廃止はいずれも v7 の挙動です。破壊的操作(reset等)は本番で絶対に実行せず、本番投入前に必ず公式ドキュメントで最新仕様を確認してください。
0. メンタルモデル:マイグレーションは「レビュー対象のSQL」
私が決済基盤の運用で徹底したのは、「自動生成されたものを、人間がレビューしてから本番へ通す」という規律です。Prisma Migrate は便利ですが、migrate dev が生成するSQLは万能ではありません。列のリネームを「削除+追加」と解釈してデータを捨てる、といった意図しない破壊が起こり得ます。
だから本番フローの中心はこうです。
開発で
migrate devしてSQLを生成 → 生成SQLを人間がレビュー(破壊的変更がないか)→ コミット → CI がmigrate deployで本番に適用。
マイグレーションファイルは「コードと同じ第一級の成果物」です。prisma/migrations/ をコミットし、PRでレビューし、CIで適用する。この一本道を外さないことが、本番DBを守る最大の防御線になります。
1. コマンドの役割分離(ここを混同すると事故る)
# 開発専用:差分からマイグレーションを生成して即適用(+クライアント再生成)
npx prisma migrate dev --name add_user_role
# 開発専用:SQLだけ生成して適用しない(手編集したいとき)
npx prisma migrate dev --create-only
# 本番/CI:保留中のマイグレーションだけを順に適用
npx prisma migrate deploy
# 開発専用:DBを破棄→再作成→全マイグレーション再適用(データ消滅)
npx prisma migrate reset
# どの環境でも:適用状況のレポート(CIゲートに使える)
npx prisma migrate status
# 2つのスキーマ差分をSQL/サマリで出力
npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script
# _prisma_migrations に「適用済み/ロールバック済み」を記録
npx prisma migrate resolve --applied 20260626120000_init
役割を表で固定します。ここを覚えれば事故は激減します。
| コマンド | 環境 | 重要な性質 |
|---|---|---|
migrate dev | 開発専用 | シャドウDBが必要。ドリフト検出時にDBをリセット(データ消滅)し得る。本番で打ってはいけない |
migrate deploy | 本番/CI | 保留中のマイグレーションのみ適用。ドリフト検査もリセットもシャドウDBも使わない。適用済みが改変されていれば警告 |
migrate reset | 開発専用 | DBを破棄して作り直す(データ消滅)。本番厳禁 |
migrate status | 任意 | 適用状況を返す。未適用/乖離/失敗で非ゼロ終了するためCIゲートに使える |
migrate diff | 任意 | 2ソースの差分。--script で実行可能SQL、--exit-code で差分時に2を返す |
migrate resolve | 任意(本番復旧の要) | マイグレーションを「適用済み/ロールバック済み」として手動記録 |
公式は migrate deploy について「ドリフトもスキーマ変更も検査しない/DBをリセットも生成もしない」と明言しています。だからこそ本番で安全です。逆に migrate dev は「開発コマンドであり、本番環境で使ってはならない」と明記されています。
2. シャドウDB:開発で「壊れる前に気づく」仕組み
migrate dev は、マイグレーションを本物のDBに当てる前に、一時的なシャドウDBでドリフトや潜在的なデータ損失を検出します。これは migrate dev 実行のたびに自動で作成・削除されます(本番には不要で、migrate deploy / migrate resolve では使われません)。
v7 では、必要に応じてシャドウDBの接続先を prisma.config.ts で指定します。
// prisma.config.ts
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
datasource: {
url: env("DATABASE_URL"),
shadowDatabaseUrl: env("SHADOW_DATABASE_URL"), // クラウドDB等で手動指定
},
});
重大な注意(公式の警告):
urlとshadowDatabaseUrlに同じ値を絶対に指定しないこと。シャドウDBはリセットされるため、本番DBを指してしまうとデータが消えます。シャドウDBは「作って壊してよい捨てDB」専用です。
3. 既存DBへ後付け導入する:ベースライン
「すでに本番で動いているDB」に Prisma Migrate を導入するとき、いきなり migrate dev すると既存テーブルを作り直そうとして衝突します。これを防ぐのがベースライン——「現状をマイグレーション履歴の出発点として記録する」手順です。
# 1. 既存DBをイントロスペクトして schema.prisma を生成(既存schemaは上書きされる)
npx prisma db pull
# 2. 初期マイグレーション用ディレクトリを用意し、現状を再現するSQLを生成
mkdir -p prisma/migrations/0_init
npx prisma migrate diff \
--from-empty \
--to-schema prisma/schema.prisma \
--script > prisma/migrations/0_init/migration.sql
# 3. その初期マイグレーションを「適用済み」として記録(実行はしない)
npx prisma migrate resolve --applied 0_init
ポイントは、手順3で実際にはSQLを流さず、_prisma_migrations テーブルに「適用済み」と記録するだけという点です。これにより、既存DBを一切変更せずにマイグレーション履歴を開始でき、以後の変更は通常どおり migrate dev → migrate deploy で積み上げられます。生成された schema.prisma とマイグレーションは必ずバージョン管理にコミットします。
注意:
migrate diffのフラグ名はCLIの版で表記が動くことがあります(--to-schema系)。実行前にnpx prisma migrate diff --helpで自環境の正確なフラグを確認してください。MongoDB はこの手順の対象外(db pushを使います)。
4. 無停止スキーマ変更:expand-and-contract
本番でアクセスが流れ続ける中で、既存列を「いきなり」変更すると、デプロイの一瞬で新旧コードとスキーマの不整合が起き、ダウンタイムやエラーになります。Prisma の公式ドキュメントは、これを避ける**expand-and-contract(拡張と収縮)**パターンを明示的に推奨しています。
考え方は3段階です。
- Expand(拡張):新しい列を追加する(旧列はそのまま)。アプリは新旧両方に書き込む。
- データ移行(バックフィル):旧列の値を新列へコピーする(カスタムSQL)。
- Contract(収縮):アプリを新列だけ読むように切替え、安定後に旧列を削除する。
4.1 列リネームを「データを壊さず」行う
migrate dev は列リネームをしばしば「DROP COLUMN + ADD COLUMN」として生成します——これはデータを捨てます。--create-only で生成して手編集し、RENAME に直します。
-- ❌ 自動生成されがちな破壊的SQL(データ消滅)
ALTER TABLE "Profile" DROP COLUMN "biograpy",
ADD COLUMN "biography" TEXT NOT NULL;
-- ✅ 手編集してデータを保持する
ALTER TABLE "Profile" RENAME COLUMN "biograpy" TO "biography";
4.2 バックフィルSQLを差し込む
新列を追加し、旧列からコピーする一連を、カスタムSQLとして空のマイグレーションに書きます。
-- Expand:新列を追加(まずは NULL 許容で)
ALTER TABLE "User" ADD COLUMN "profileId" INTEGER;
-- バックフィル:既存データを新列へ
UPDATE "User"
SET "profileId" = "Profile".id
FROM "Profile"
WHERE "User".id = "Profile"."userId";
-- 安定後(別マイグレーション)に NOT NULL 化/旧列削除(Contract)
ALTER TABLE "User" ALTER COLUMN "profileId" SET NOT NULL;
公式は「このアプローチにより、既存フィールドの変更が招きがちなダウンタイムを避けられる」と述べています。段階を分けてデプロイする(拡張→移行→収縮を別リリースにする)ことが要点です。
5. --create-only:危険なDDLは「生成→レビュー→適用」
リスクのある変更(リネーム、バックフィル、巨大テーブルへのNOT NULL列追加、拡張機能の有効化など)は、必ず生成と適用を分離します。
# 1. 適用せずにSQLだけ生成
npx prisma migrate dev --create-only
# 2. prisma/migrations/<timestamp>_*/migration.sql を手で編集・レビュー
# 3. 編集済みSQLを適用
npx prisma migrate dev
この3ステップが、私が本番で守る「自動生成を人間が点検してから通す」を Prisma で実現する具体手段です。巨大テーブルへの NOT NULL 列追加は書き込みロックを長く握りやすいので、PostgreSQL なら「NULL許容で追加→バックフィル→SET NOT NULL」と段階化し、必要なら lock_timeout や CONCURRENTLY を併用する——といった無停止DDLの知見を、このレビュー段で生成SQLに反映します。
6. CI/CD:deploy を自動化し、dev/reset を締め出す
公式の指針は明確です。「migrate deploy はCI/CDパイプラインの一部であるべきで、本番DBへの変更をローカルから流すことは推奨しない」。そして「migrate dev は開発コマンドで、本番で使ってはならない」。
CI のデプロイ前ステップは概念的に次の形になります(YAMLは各CIに合わせて記述)。
# CI/CD(本番/ステージングDBに対して)
npx prisma migrate deploy
# アプリのビルド前に型付きクライアントも生成
npx prisma generate
運用の鉄則を3つ。
- 本番のDB認証情報は環境変数(CIシークレット)から。ハードコード・ログ出力は厳禁。
migrate statusをゲートにする:未適用/乖離があれば非ゼロ終了するので、デプロイ前チェックに使えます。- マイグレーションの適用はアプリのデプロイと順序を設計する:expand-and-contract では「スキーマ拡張 → 新コードのデプロイ → 収縮」を別ステップに分けます。
7. ドリフトと失敗からの復旧
ドリフトとは「DBの実態がマイグレーション履歴(真実源)から乖離した状態」です。手動の本番ホットフィックスや、失敗したマイグレーションが原因になります。migrate dev はシャドウDBでこれを検出し、開発環境ではリセットを促すことがありますが、本番ではリセットしてはいけません。本番の復旧は migrate resolve で行います。
# 失敗したマイグレーションを「ロールバック済み」と記録し、修正版を deploy し直す
npx prisma migrate resolve --rolled-back 20260626130000_failed_migration
# 手動で適用を完了させた変更を「適用済み」と記録する
npx prisma migrate resolve --applied 20260626140000_manually_completed
マイグレーション失敗の典型は、構文エラー、データが入った表への必須列追加、プロセスの異常終了、適用中のDB停止などです。各失敗は _prisma_migrations の logs 列に記録されます。migrate deploy はドリフトを検出しないため、本番のドリフトは「起こさない運用(直接変更を禁じる)」と「起きたら resolve で履歴を整える」の両輪で守ります。
8. シード:v7 の変更点
v7 では、シードの設定が prisma.config.ts の migrations.seed に移り、さらに自動シードが廃止されました。
// prisma.config.ts
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "tsx prisma/seed.ts", // db seed が実行するコマンド
},
});
// prisma/seed.ts(v7:driver adapter 必須)
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/prisma/client";
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
async function main() {
await prisma.user.upsert({
where: { email: "admin@example.com" },
update: {},
create: { email: "admin@example.com", name: "Admin" },
});
}
main().finally(() => prisma.$disconnect());
npx prisma db seed
公式の破壊的変更(要約):「v7 では、シードは npx prisma db seed の明示実行でのみ起動する。migrate dev / migrate reset 時の自動シードは廃止された」。シードは upsert で冪等に書くのが鉄則です(何度流しても結果が同じ)。
9. 本番マイグレーション・チェックリスト
- 開発は
migrate dev、本番/CIはmigrate deploy。本番でdev/resetを絶対に実行しない - 生成されたマイグレーションSQLをPRでレビューしてからマージ(破壊的変更の有無を確認)
- リスクのあるDDLは
--create-onlyで生成→手編集→適用 - 既存列の変更は expand-and-contract で段階デプロイ(リネームは
RENAMEに手編集) - 既存DB導入は
db pull→migrate diff --from-empty→migrate resolve --appliedでベースライン - シャドウDBの接続先は本番と別物(同一URL厳禁)
- CIで
migrate deploy→generate、migrate statusをゲートに - 本番のドリフト/失敗は
migrate resolve(--rolled-back/--applied)で復旧。直接変更は禁止 - シードは
prisma.config.tsのmigrations.seedに置き、upsertで冪等に。v7は明示db seedのみ
まとめ
Prisma Migrate の安全運用は、突き詰めれば「コマンドの役割を絶対に混同しない」と「自動生成SQLを人間がレビューしてから本番へ通す」の2点に集約されます。開発は migrate dev、本番は migrate deploy。既存DBはベースライン、無停止変更は expand-and-contract、危険なDDLは --create-only でレビュー、復旧は resolve——これだけ守れば、本番DBにスキーマ変更を当て続けても壊れません。
「スキーマ変更を、ダウンタイムなく・データを壊さず本番へ流し続ける仕組みを作りたい」——マイグレーション運用の設計からCI/CDの組み込みまで、本番で破綻しない形に落とし込むお手伝いができます。決済基盤で二重課金0件・無停止デプロイを支えた知見を、あなたのDB運用にも適用します。