# Prisma Migrate 本番運用ガイド：dev/deployの正しい分離、シャドウDB、既存DBのベースライン、expand-and-contractの無停止移行、CI/CD

> Prisma Migrate（v7）を本番で安全に運用する実装ガイド。migrate dev/deploy/reset/diff/resolveの役割分離、シャドウDBの仕組み、既存DBへの後付け導入（db pull＋ベースライン）、--create-onlyでSQLを編集するexpand-and-contractの無停止スキーマ変更、ドリフト検知と復旧、CI/CDでのmigrate deploy、v7のシード変更までを、公式ドキュメントに忠実な実コマンドで解説します。

- 公開日: 2026-06-26
- 著者: 友田 陽大
- タグ: Prisma, TypeScript, PostgreSQL, CI/CD, 信頼性
- URL: https://tomodahinata.com/blog/prisma-migrate-production-zero-downtime-cicd-guide

## 要点

- コマンドの役割を絶対に混同しない。開発はmigrate dev（シャドウDB必須・リセットあり）、本番CIはmigrate deploy（保留分のみ適用・ドリフト検査もリセットもしない）
- 既存DBへの後付けはベースライン。db pullで取り込み、migrate diff --from-emptyで初期SQLを生成し、migrate resolve --appliedで適用済みとして記録する
- 無停止移行はexpand-and-contractの3段階。列追加→両書き込み→バックフィル→新列に切替→旧列削除。renameは自動生成のDROP+ADDをRENAMEに手編集する
- リスクのあるDDLはmigrate dev --create-onlyでSQLを生成→人間がレビュー・編集→適用。生成SQLは必ずコミットしレビュー対象にする
- 本番のドリフト/失敗はmigrate resolveで解消（--rolled-back/--applied）。本番でmigrate dev/resetは厳禁。v7は自動シードが廃止されdb seedの明示実行のみ

---

本番のデータベースに対するスキーマ変更は、アプリのデプロイとは別格の緊張を伴います。コードのバグは切り戻せますが、**データを壊すマイグレーションは切り戻せない**ことがあるからです。「列を消したらデータが消えた」「`migrate dev` を本番で打ってDBがリセットされた」——こうした事故は、コマンドの役割を理解していれば100%防げます。

この記事は、**Prisma Migrate（v7）を本番で安全に運用する**ための実装ガイドです。スキーマ設計そのものは[Prisma スキーマ設計＆リレーション完全ガイド](/blog/prisma-schema-data-modeling-relations-design-guide)に、Prisma 全体像は[Prisma ORM 本番運用ガイド（v7）](/blog/prisma-orm-production-guide-type-safe-database-v7-driver-adapters)にあります。本記事は「**設計したスキーマを、どうやってデータを壊さず本番へ適用し続けるか**」に集中します。

> **この記事のルール**：コマンド・挙動は **Prisma 公式ドキュメント（2026年6月時点、v7系）** に基づきます。`prisma.config.ts` によるシャドウDB/シード設定、driver adapter 必須化、自動シードの廃止はいずれも v7 の挙動です。破壊的操作（`reset` 等）は本番で絶対に実行せず、本番投入前に必ず[公式ドキュメント](https://www.prisma.io/docs/orm/prisma-migrate/workflows/development-and-production)で最新仕様を確認してください。

---

## 0. メンタルモデル：マイグレーションは「レビュー対象のSQL」

私が決済基盤の運用で徹底したのは、「**自動生成されたものを、人間がレビューしてから本番へ通す**」という規律です。Prisma Migrate は便利ですが、`migrate dev` が生成するSQLは万能ではありません。列のリネームを「削除＋追加」と解釈してデータを捨てる、といった**意図しない破壊**が起こり得ます。

だから本番フローの中心はこうです。

> **開発で `migrate dev` してSQLを生成 → 生成SQLを人間がレビュー（破壊的変更がないか）→ コミット → CI が `migrate deploy` で本番に適用。**

マイグレーションファイルは「コードと同じ第一級の成果物」です。`prisma/migrations/` をコミットし、PRでレビューし、CIで適用する。この一本道を外さないことが、本番DBを守る最大の防御線になります。

---

## 1. コマンドの役割分離（ここを混同すると事故る）

```bash
# 開発専用：差分からマイグレーションを生成して即適用（＋クライアント再生成）
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` で指定します。

```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` すると**既存テーブルを作り直そうとして衝突**します。これを防ぐのが**ベースライン**——「現状をマイグレーション履歴の出発点として記録する」手順です。

```bash
# 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段階です。

1. **Expand（拡張）**：新しい列を**追加**する（旧列はそのまま）。アプリは新旧**両方に書き込む**。
2. **データ移行（バックフィル）**：旧列の値を新列へコピーする（カスタムSQL）。
3. **Contract（収縮）**：アプリを**新列だけ読む**ように切替え、安定後に**旧列を削除**する。

### 4.1 列リネームを「データを壊さず」行う

`migrate dev` は列リネームをしばしば「**DROP COLUMN ＋ ADD COLUMN**」として生成します——これは**データを捨てます**。`--create-only` で生成して**手編集**し、`RENAME` に直します。

```sql
-- ❌ 自動生成されがちな破壊的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として空のマイグレーションに書きます。

```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列追加、拡張機能の有効化など）は、必ず**生成と適用を分離**します。

```bash
# 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に合わせて記述）。

```bash
# 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` で行います。

```bash
# 失敗したマイグレーションを「ロールバック済み」と記録し、修正版を 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` に移り、さらに**自動シードが廃止**されました。

```ts
// prisma.config.ts
export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts", // db seed が実行するコマンド
  },
});
```

```ts
// 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());
```

```bash
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運用にも適用します。
