
「supabase db push したら謎のエラーで失敗した」——Supabase
でデータベース開発をしていると、一度は遭遇するトラブルです。このエラーの原因を追いかけることで、マイグレーションの本質が見えてきます。
本記事では、よくある3つの失敗パターンと、その背後にある「マイグレーション履歴の仕組み」を解説します。トラブル対処だけでなく、なぜそのような設計になっているのか、どう付き合っていくべきかを理解することで、安全な運用への道筋が見えてくるはずです。
Supabaseを使った開発を始めたばかりの方、AIツール(Cursor やSupabase MCP など)でスキーマ変更を行っている方に特におすすめの内容です。
突然のエラー: 何が起きたのか
開発中、ローカルで作成したマイグレーションを本番に適用しようとした時のことです。
$ supabase db push
Error: cannot apply migration 20240115120000_create_users_table.sql:
relation "users" already exists
あるいは、こんなパターンも。
$ supabase db push
Error: migration 20240115120000 has already been applied
さらに厄介なのがこれ。
$ supabase db push
Error: migration history is not in sync with local migrations
Remote has migrations not found locally: 20240114090000
一体何が起きているのでしょうか?
まず理解すべきこと: db push が成功する条件
エラーを理解する前に、supabase db push が正常に動作する条件を明確にしましょう。
db push の正常な動作
$ supabase db push
Applying migration 20240115120000_create_users_table.sql...
Migration applied successfully.
このシンプルな成功メッセージの裏で、何が起きているのでしょうか?
成功のための3つの条件
flowchart TD
A[supabase db push] --> B{条件1:\nローカルに新しい\nマイグレーションがある?}
B -->|Yes| C{条件2:\nリモート履歴に\n記録がない?}
B -->|No| Z[何もしない\nすでに同期済み]
C -->|Yes| D{条件3:\n実際のスキーマに\n影響しない?}
C -->|No| E[エラー:\nalready applied]
D -->|Yes| F[マイグレーション実行]
D -->|No| G[エラー:\nrelation exists]
F --> H[履歴テーブルに記録]
H --> I[成功!]
具体的には:
条件1: ローカルに新規マイグレーションファイルがある
# ローカルにこのファイルがある
supabase/migrations/20240115120000_create_users_table.sql
条件2: リモートの履歴テーブルに未記録
-- リモートで確認すると、このversionが存在しない
SELECT version FROM supabase_migrations.schema_migrations
WHERE version = '20240115120000';
-- (0 rows)
条件3: SQLの実行が成功する
マイグレーションファイルのSQL文が、現在のスキーマに対して問題なく実行できる状態。
db push の実行フロー
sequenceDiagram
participant CLI as supabase CLI
participant Local as ローカルファイル
participant Remote as リモートDB
participant History as schema_migrations
CLI->>Local: マイグレーションファイルを読み込み
CLI->>History: 適用済み履歴を取得
CLI->>CLI: 差分を計算
(ローカル - リモート履歴)
alt 未適用のマイグレーションあり
loop 各マイグレーション
CLI->>Remote: BEGIN TRANSACTION
CLI->>Remote: SQLを実行
CLI->>History: version, name, statementsを記録
CLI->>Remote: COMMIT
end
CLI-->>CLI: 成功
else すべて適用済み
CLI-->>CLI: 何もしない(Already up to date)
end
実際の成功例
ステップ1: 新しいマイグレーションを作成
$ supabase migration new create_users_table
Created new migration at supabase/migrations/20240115120000_create_users_table.sql
ステップ2: SQLを記述
CREATE TABLE public.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ステップ3: db push を実行
$ supabase db push
Applying migration 20240115120000_create_users_table.sql...
Migration applied successfully.
ステップ4: 裏で何が起きたか
-- リモートで実行された処理
BEGIN;
-- 1. マイグレーションのSQLを実行
CREATE TABLE public.users (...);
-- 2. 履歴テーブルに記録
INSERT INTO supabase_migrations.schema_migrations (version, name, statements)
VALUES ('20240115120000', 'create_users_table', '{"CREATE TABLE ..."}');
COMMIT;
重要な仕組み: トランザクション保証
db push は、各マイグレーションをトランザクション内で実行します。
BEGIN;
-- マイグレーションのSQL実行
-- 履歴への記録
COMMIT; -- すべて成功した場合のみコミット
これにより:
- 全成功 または 全失敗 が保証される
- 途中でエラーが起きても、スキーマは変更されない
- 履歴とスキーマの整合性が保たれる
成功時の状態変化
実行前:
ローカル: 20240115120000_create_users_table.sql ✓
リモート履歴: (なし)
リモートスキーマ: usersテーブルなし
実行後:
ローカル: 20240115120000_create_users_table.sql ✓
リモート履歴: 20240115120000 ✓
リモートスキーマ: usersテーブルあり ✓
この3つの同期状態が、正常な状態です。
エラーの正体: 「状態」の不整合
では、冒頭のエラーはなぜ起きたのでしょうか?
答えは、この3つの同期状態が崩れたときです。
Supabaseのマイグレーションシステムは、2つの情報を照合して動作します。
flowchart LR
subgraph ローカル
A[supabase/migrations/\nディレクトリ]
end
subgraph リモート
B[schema_migrations\nテーブル]
C[実際のスキーマ]
end
A -->|比較| B
B -->|追跡| C
- ローカル:
supabase/migrations/ディレクトリ内のSQLファイル - リモート:
supabase_migrations.schema_migrationsテーブル(実行履歴)
supabase db push は、この2つを比較して「ローカルにあるがリモートにない」マイグレーションだけを実行します。問題は、この比較がうまくいかない時に起きます。
3つの失敗パターンと原因
パターン1: 「relation already exists」
Error: relation "users" already exists
原因: マイグレーションファイルの内容がすでに適用されているが、履歴テーブルに記録されていない。
よくあるシナリオ:
- Supabase Studioで直接テーブルを作成した
- 別の手段(psqlなど)でSQLを実行した
db pushが途中で失敗し、一部だけ適用された
パターン2: 「already been applied」
Error: migration 20240115120000 has already been applied
原因: 履歴テーブルには記録があるが、同じタイムスタンプのマイグレーションを再度適用しようとしている。
よくあるシナリオ:
- 適用済みマイグレーションと同じタイムスタンプで新しいファイルを作成した
db pushを誤って複数回実行した- リモートに既に適用済みのマイグレーションをローカルで再作成した
パターン3: 「not in sync」
Error: migration history is not in sync
Remote has migrations not found locally: 20240114090000
原因: リモートには適用済みの履歴があるが、対応するローカルファイルが存在しない。
よくあるシナリオ:
- チームメンバーが作成したマイグレーションを
git pullしていない - ローカルでマイグレーションファイルを削除してしまった
- 別ブランチで作成されたマイグレーションがリモートに適用された
状態を確認する方法
トラブルシューティングの第一歩は、現在の状態を正確に把握することです。
リモートの履歴を確認
supabase db remote status
または、直接SQLで確認:
SELECT version, name, statements
FROM supabase_migrations.schema_migrations
ORDER BY version;
ローカルのファイルを確認
ls -la supabase/migrations/
差分を可視化
supabase db diff
このコマンドは、現在のリモートスキーマとローカルのマイグレーションファイルを適用した結果の差分を表示します。
各パターンの修正方法
パターン1の修正: スキーマは存在するが履歴がない
履歴テーブルに手動でレコードを追加し、「このマイグレーションは適用済み」とマークします。
INSERT INTO supabase_migrations.schema_migrations (version, name, statements)
VALUES ('20240115120000', 'create_users_table', NULL);
注意: この方法は、スキーマが本当にマイグレーションファイルの内容と一致している場合のみ使用してください。
パターン2の修正: 重複したタイムスタンプ
選択肢A: 新しいタイムスタンプでマイグレーションを作り直す
# 重複したマイグレーションファイルを削除
rm supabase/migrations/20240115120000_duplicate_migration.sql
# 新しいタイムスタンプで作成し直す
supabase migration new create_new_feature
選択肢B: 履歴から該当バージョンを削除する(慎重に)
-- リモートDBに接続して実行(注意: データ整合性に影響する可能性)
DELETE FROM supabase_migrations.schema_migrations
WHERE version = '20240115120000';
その後、改めて db push を実行。
注意: Supabaseはマイグレーションファイルの内容をチェックサム検証していません。履歴テーブルの version(タイムスタンプ)のみで適用済みかを判断します。そのため、適用済みマイグレーションファイルの内容を変更しても検出されませんが、絶対に変更してはいけません。変更すると、環境間でスキーマの不整合が発生します。
パターン3の修正: ローカルにファイルがない
# チームメンバーの変更を取得
git pull origin main
# または、リモートから差分を取り込む
supabase db pull
db pull は、リモートのスキーマ変更をマイグレーションファイルとして生成します。
実践トラブル事例: Supabase MCPとAIコーディング
事例: AIがマイグレーションを直接実行してしまう
Supabase MCP を使ってCursor やClaude Desktop でAIコーディングしていると、思わぬトラブルに遭遇することがあります。
よくある状況:
AIに「usersテーブルを作成して」と指示すると、AIはSupabase MCP経由でリモートデータベースに直接SQLを実行します。
# AIが実行するSQL(MCP経由)
CREATE TABLE public.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
テーブルは無事作成されますが、その後 supabase db push しようとすると:
$ supabase db push
Error: migration history is not in sync with local migrations
Local migrations not found on remote:
20240115120000_create_users_table.sql
あるいは、マイグレーションファイルを後から作成して push すると:
Error: cannot apply migration 20240115120000_create_users_table.sql:
relation "users" already exists
なぜこの問題が起きるのか
flowchart TD
A[AIに指示:\nusersテーブル作成] --> B[AI判断:\nMCP経由で直接実行]
B --> C[リモートDBに\nCREATE TABLE実行]
C --> D{履歴テーブルに記録?}
D -->|No| E[スキーマだけ変更\n履歴なし]
E --> F[後でdb push]
F --> G[エラー:\nテーブルが既に存在]
style D fill:#ff9999
style E fill:#ff9999
style G fill:#ff9999
問題の本質:
AIがMCP経由でSQLを直接実行
CREATE TABLEなどをリモートDBに即座に実行- 履歴テーブル(
schema_migrations)には記録されない
ローカルのマイグレーションファイルが存在しない
supabase/migrations/ディレクトリには何も作成されていない- Gitで管理される変更履歴がない
状態の不整合が発生
ローカル: マイグレーションファイルなし リモート履歴: 記録なし リモートスキーマ: usersテーブル存在 ✓
対処方法1: リモートの変更をマイグレーションファイルに取り込む
最もシンプルな解決策は、db pull でリモートの変更を取り込むことです。
# リモートのスキーマ変更を検出してマイグレーションファイル化
supabase db pull
実行結果:
Pulling schema from remote database...
Created new migration supabase/migrations/20240115135500_remote_schema.sql
生成されたファイル(supabase/migrations/20240115135500_remote_schema.sql)には、リモートとローカルの差分が含まれています:
-- このファイルには、リモートで直接作成された users テーブルの定義が含まれる
CREATE TABLE public.users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
メリット:
- リモートの実際の状態を確実にキャプチャできる
- 他のチームメンバーも同じマイグレーションファイルを共有できる
注意点:
- AIが作成したスキーマの詳細(インデックス、制約など)が期待通りか確認が必要
- 生成されたファイル名は自動的なタイムスタンプなので、わかりやすい名前に変更することを推奨
対処方法2: リモートの変更を履歴として記録する
すでにマイグレーションファイルを作成済みで、リモートのスキーマと一致している場合は、履歴テーブルに手動で記録します。
-- リモートDBに接続して実行
INSERT INTO supabase_migrations.schema_migrations (version, name, statements)
VALUES ('20240115120000', 'create_users_table', NULL);
その後、db push を実行:
$ supabase db push
Already up to date.
この方法を使うべき条件:
- ローカルのマイグレーションファイルが既に存在する
- リモートのスキーマとマイグレーションファイルの内容が完全に一致している
- AIが実行したSQLの内容を正確に把握している
対処方法3: リモートをクリーンアップして再適用(開発環境のみ)
開発初期段階で、リモートのスキーマをリセットできる場合:
# 1. リモートDBのスキーマを削除(注意: データも失われる)
supabase db reset --linked
# 2. ローカルのマイグレーションを適用
supabase db push
警告:
- この方法は本番環境では絶対に使用しないこと
- 既存のデータがすべて失われる
- 開発の初期段階でのみ有効
予防策: AIに正しい指示を出す
Supabase MCPを使う際は、AIに明示的に「マイグレーションファイルを作成する」よう指示します。
悪い指示例:
usersテーブルを作成して
→ AIがMCP経由で直接SQLを実行する可能性が高い
良い指示例:
usersテーブルのマイグレーションファイルを作成して。
supabase migration new を使って、ファイルにCREATE TABLE文を記述してください。
リモートには push しないで。
さらに良い指示例:
以下の手順でusersテーブルを追加:
1. supabase migration new create_users_table を実行
2. 生成されたファイルにCREATE TABLE文を記述
3. ローカルで supabase db reset してテスト
4. 確認後、supabase db push で本番適用
MCPでリモートDBに直接SQLを実行しないでください。
ベストプラクティス: AIとの協調作業
flowchart LR
A[AIに指示] --> B{指示内容}
B -->|推奨| C[マイグレーションファイル作成]
B -->|避ける| D[直接SQL実行]
C --> E[supabase migration new]
E --> F[ファイル編集]
F --> G[ローカルテスト]
G --> H[db push]
D --> I[履歴と不整合]
style C fill:#99ff99
style D fill:#ff9999
style I fill:#ff9999
推奨ワークフロー:
AIにマイグレーション生成を依頼
「create_posts_table というマイグレーションファイルを作成して、 postsテーブルの定義を含めてください」生成されたファイルをレビュー
- SQLの内容が意図通りか確認
- セキュリティ(RLS)設定が含まれているか確認
ローカルでテスト
supabase db reset問題なければコミット
git add supabase/migrations/ git commit -m "Add posts table migration"リモートに適用
supabase db push

トラブル事例4: ローカルのマイグレーションがリモート履歴の途中に挿入される
チーム開発やブランチ作業で発生しやすいパターンです。
$ supabase db push
Found local migration files to be inserted before the last migration on remote database.
Rerun the command with --include-all flag to apply these migrations:
supabase/migrations/20251223100000_add_spec_version_to_mock_applications.sql
なぜこれが起きるのか:
flowchart LR
subgraph リモート履歴
R1[20251220_feature_a] --> R2[20251225_feature_b]
end
subgraph ローカル
L1[20251220_feature_a] --> L2[20251223_new_column] --> L3[20251225_feature_b]
end
L2 -->|挿入位置| R1
style L2 fill:#ffcc00
リモートでは 20251220 → 20251225 の順で適用済みですが、ローカルには 20251223 のマイグレーションが存在します。これは以下のような状況で発生します:
- ブランチ作業: 別ブランチで作成したマイグレーションをマージした
- チーム開発: 他のメンバーが先に
db pushして、自分のマイグレーションが「間に挟まる」形になった - 作業順序の逆転: 古いタイムスタンプのマイグレーションを後から追加した
対処方法1: --include-all フラグを使用(推奨)
supabase db push --include-all
このフラグを付けると、タイムスタンプの順序に関わらず、未適用のマイグレーションをすべて適用します。
注意点:
- マイグレーション間に依存関係がある場合は問題が発生する可能性がある
- 実行前に
supabase db diffで差分を確認することを推奨
対処方法2: タイムスタンプを更新
マイグレーションがまだ他の環境に適用されていない場合は、タイムスタンプを最新に更新できます:
# 現在時刻でタイムスタンプを生成
TIMESTAMP=$(date +%Y%m%d%H%M%S)
# ファイル名を変更
mv supabase/migrations/20251223100000_add_spec_version.sql \
supabase/migrations/${TIMESTAMP}_add_spec_version.sql
予防策:
- チームで作業する場合は、
git pullとsupabase db pullを頻繁に実行 - マイグレーション作成時は必ず
supabase migration newコマンドを使用(現在時刻が自動設定される) - ブランチをマージする前に、マイグレーションのタイムスタンプを確認
トラブル事例5: AIが古いタイムスタンプを生成してしまう
AIコーディング中、稀にAIがマイグレーションファイルを手動作成する際、極端に古いタイムスタンプを付けてしまうことがあります。
問題のあるファイル例:
supabase/migrations/20200101000000_add_new_feature.sql
このファイルは supabase db push 時に以下の問題を引き起こします:
$ supabase db push
Error: migration 20200101000000 comes before the latest applied migration 20240115120000
なぜこれが問題なのか:
flowchart LR
A[既存マイグレーション
20240115120000] --> B[AIが作成
20200101000000]
B --> C[タイムスタンプ順では
Aより前]
C --> D[実行順序が逆転]
D --> E[依存関係エラー]
style B fill:#ff9999
style D fill:#ff9999
style E fill:#ff9999
マイグレーションはタイムスタンプ順に実行されるため、新しい機能を追加するマイグレーションが既存のマイグレーションより前に実行されようとします。これは:
- 依存するテーブルがまだ存在しない
- 実行順序が意図と逆になる
- 履歴の整合性が崩れる
原因:
AIが supabase migration new コマンドを使わずに、ファイルを直接作成してしまった場合に発生します。
# AIが誤って実行する例
touch supabase/migrations/20200101000000_add_new_feature.sql
対処方法1: ファイル名を修正
# 1. 現在時刻でタイムスタンプを生成
TIMESTAMP=$(date +%Y%m%d%H%M%S)
# 2. ファイル名を変更
mv supabase/migrations/20200101000000_add_new_feature.sql \
supabase/migrations/${TIMESTAMP}_add_new_feature.sql
# 例: 20200101000000_add_new_feature.sql
# → 20240116153045_add_new_feature.sql
対処方法2: 正しいコマンドで作り直す
# 1. 誤ったファイルの内容を一時保存
cp supabase/migrations/20200101000000_add_new_feature.sql /tmp/migration_content.sql
# 2. 誤ったファイルを削除
rm supabase/migrations/20200101000000_add_new_feature.sql
# 3. 正しいコマンドで作成(自動的に正しいタイムスタンプが付く)
supabase migration new add_new_feature
# 4. 内容を復元
cat /tmp/migration_content.sql > supabase/migrations/*_add_new_feature.sql
対処方法3: 既存履歴をリセット(開発初期のみ)
開発の初期段階で、まだリモートに適用していない場合:
# ローカルDBをリセット
supabase db reset
# マイグレーションファイルを全て削除
rm -rf supabase/migrations/*
# 現在のスキーマから新しいマイグレーションを生成
supabase db diff -f initial_schema
予防策: AIに正しいコマンドを使わせる
マイグレーションファイルを作成する際は、必ず以下のコマンドを使ってください:
supabase migration new <マイグレーション名>
ファイルを手動で作成したり、touchコマンドを使ったりしないでください。
タイムスタンプは自動生成されます。
タイムスタンプの重要性:
正しい順序:
20240115120000_create_users.sql ← 先に実行
20240115130000_create_posts.sql ← 後で実行(users に依存)
20240116153045_add_new_feature.sql ← 最後に実行
間違った順序:
20200101000000_add_new_feature.sql ← 最初に実行されようとする(エラー)
20240115120000_create_users.sql
20240115130000_create_posts.sql
まとめ: MCP使用時の注意点
| 項目 | 推奨 | 避けるべき |
|---|---|---|
| スキーマ変更方法 | マイグレーションファイル経由 | MCP経由で直接SQL実行 |
| AIへの指示 | 「migration new で作成」と明示 | 曖昧な指示 |
| ファイル作成方法 | supabase migration new コマンド | 手動ファイル作成・touch コマンド |
| タイムスタンプ | 自動生成されたものを使用 | 手動で設定・古い日付 |
| 順序の問題 | --include-all で対処 | 依存関係を無視して適用 |
| テスト方法 | ローカルで db reset | リモートで直接確認 |
| 適用タイミング | レビュー後に db push | AIの実行直後 |
Supabase MCPは強力なツールですが、マイグレーション管理の原則を理解した上で使用することが重要です。
なぜこの設計なのか: マイグレーションの本質
ここまで見てきたトラブルは、マイグレーションシステムの設計思想を理解すると腑に落ちます。
「宣言的」ではなく「命令的」
Supabaseのマイグレーションは、最終状態を宣言するのではなく、変更の手順を記録する方式です。
宣言的: 「usersテーブルにはemail列があるべき」
命令的: 「ALTER TABLE users ADD COLUMN email TEXT を実行せよ」
命令的アプローチのメリット:
- 実行順序が明確
- 複雑な変更(データ移行など)を表現できる
- 何が起きたか履歴として残る
デメリット:
- 一度適用したマイグレーションは変更できない
- 履歴とファイルの整合性を保つ必要がある
タイムスタンプによる順序保証
20240115120000_create_users_table.sql ← 先に実行
20240115130000_add_email_column.sql ← 後で実行
ファイル名のタイムスタンプは、単なる命名規則ではありません。実行順序を決定する重要な要素です。これにより:
- 依存関係のある変更が正しい順序で適用される
- チームメンバーが同時に作成しても衝突しにくい
- 履歴が時系列で追跡できる
冪等性の欠如
多くのマイグレーションは冪等(何度実行しても同じ結果)ではありません。
CREATE TABLE users (...); -- 2回実行するとエラー
だからこそ、履歴テーブルで「何を実行済みか」を厳密に管理する必要があるのです。
トラブルを防ぐワークフロー
理解が深まったところで、トラブルを未然に防ぐワークフローを紹介します。
原則: 「履歴を壊さない」
flowchart TD
A[スキーマ変更が必要] --> B{変更方法}
B -->|正しい| C[migration newで\nファイル作成]
B -->|危険| D[Studioで直接変更]
B -->|危険| E[psqlで直接変更]
C --> F[ローカルでテスト]
F --> G[db pushで適用]
D --> H[履歴と不整合]
E --> H
推奨ワークフロー
1. マイグレーションファイルを作成
supabase migration new create_posts_table
2. SQLを記述
-- supabase/migrations/20240116090000_create_posts_table.sql
CREATE TABLE public.posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES public.users(id),
title TEXT NOT NULL,
content TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
3. ローカルでテスト
supabase db reset # ローカルDBを初期化して全マイグレーションを再実行
4. コードレビューとマージ
git add supabase/migrations/
git commit -m "Add posts table migration"
git push
5. リモートに適用
supabase db push
やってはいけないこと
| 行為 | 理由 |
|---|---|
| 適用済みマイグレーションを編集 | 環境間でスキーマが不整合になる(検出されない) |
| Studioで直接スキーマ変更 | 履歴に記録されない |
| マイグレーションファイルを削除 | リモートと不整合になる |
| タイムスタンプを手動で変更 | 実行順序が狂う可能性 |
CI/CDでの自動化
マイグレーションの適用をCI/CDパイプラインに組み込むことで、ヒューマンエラーを防げます。
# .github/workflows/deploy.yml
name: Deploy Migrations
on:
push:
branches: [main]
paths:
- 'supabase/migrations/**'
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: supabase/setup-cli@v1
with:
version: latest
- name: Link project
run: supabase link --project-ref ${{ secrets.SUPABASE_PROJECT_ID }}
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
- name: Push migrations
run: supabase db push
まとめ: エラーは理解のチャンス
supabase db push の失敗は、マイグレーションシステムが「状態の不整合を検知した」というシグナルです。
重要なポイント:
- マイグレーションは「変更の履歴」: ファイルと履歴テーブルの整合性が命
- 一度適用したら変更しない: 追加の変更は新しいマイグレーションで
- 直接変更は避ける: 必ずマイグレーションファイル経由で変更
- 状態確認が第一歩:
db remote statusとdb diffで現状把握
エラーに遭遇したら、まず「ローカルとリモートの状態がどう違うのか」を確認してください。原因がわかれば、適切な対処法が見えてきます。