コンテンツにスキップ

Vendure ステージング/本番マイグレーション実行手順(公式ドキュメント準拠)

目的と適用範囲

  • Vendure をステージング環境で検証し、同一アーティファクトを本番へ昇格させるための実行手順をまとめる。
  • データベースマイグレーションと初回データ投入、リリース時のチェックポイントを標準化する。
  • 本書は開発・運用チームの内部向け。顧客向け資料には転記しない。
  • 障害時の drift 初動・復旧は Vendure Drift 運用 Runbook を正とする。

公式リファレンス

環境原則

  • 同一 Docker イメージをステージング→本番へプロモートし、環境差分は環境変数とシークレットで管理する(Using Docker ガイド)。
  • VENDURE_SYNCHRONIZE=false を基本とし、本番では常にマイグレーションで管理する。 空の DB を初期化するときだけ migration 実行時に ALLOW_SCHEMA_SYNC=true を明示し、通常運用では残さない(Getting Data into Production)。
  • Fly の release_command は 2026-04-15 以降、migration の直後に additive-only structural repair も実行し、missing relation / join table をアプリ起動前に補修する。
  • ID 戦略と HardenPlugin を本番で有効化(UUID 推奨、GraphQL/REST の公開範囲を最小化)(Production Configuration)。
  • 複数インスタンス時はセッション共有または stateless JWT を利用し、共通 COOKIE_SECRET/SESSION_SECRET を配布(Horizontal Scaling)。

データベースマイグレーション運用

  1. 生成(開発環境)
  2. スキーマ変更後、ローカルで pnpm exec nx run ritsubi-vendure-server:migrate:generate -- --name=<name> を実行し、差分を確認する。
  3. 親 shell に古い DATABASE_URL が残っていると、scripts/run-local-ts-source.sh のローカル既定値 (postgresql://vendure:vendure_dev_password@127.0.0.1:5433/vendure_dev) が使われず、password authentication failed for user "vendure" で失敗することがある。 その場合は env | grep DATABASE_URL で接続先を確認し、ローカル生成では DATABASE_URL=postgresql://vendure:vendure_dev_password@127.0.0.1:5433/vendure_dev pnpm exec nx run ritsubi-vendure-server:migrate:generate -- --name=<name> のように明示して再実行する。
  4. migrate:generate は生成後に 13 桁 epoch-ms timestamp へ正規化し、review / data guard まで実行する。 生の Vendure CLI を直接叩いて 14 桁 YYYYMMDDHHmmss の migration file を作らない。
  5. timestamp だけを確認したい場合は pnpm run lint:vendure-migration-timestamps を使う。pre-commit / pre-push でも migration 変更時に同じ軽量検査が走る。
  6. 必要に応じて pnpm exec nx run ritsubi-vendure-server:migrate:review:latest で最新の未コミット migration を再 review する。
  7. 生成結果をレビューし、不要な SQL を手動で修正する(公式も手動レビュー推奨)。
  8. Vendure core entity の custom field 追加は自動生成だけに依存しない。 特に Order など既存テーブルでは、期待する customFields... カラムが migration に載らないことがあるため、実際の差分を確認し、必要なら ALTER TABLE ... ADD COLUMN IF NOT EXISTS を明示した手書き migration を追加する。
  9. VENDURE_SYNCHRONIZE=true は development/test 用とし、共有環境では使わない。新規環境の空 DB 初期化が必要な場合だけ ALLOW_SCHEMA_SYNC=true を明示する。
  10. 直近の実例として、Order.customFields.billingCustomerCode / shippingMode / deliveryDate / deliveryTimeSlot / specialInstructions は不足カラムを補う migration を別途追加している。 Order.customFields.directShippingSurcharge は per-line finalUnitPrice 内包設計へ 統一したため、専用の drop migration (*_drop_order_direct_shipping_surcharge.ts) で列を削除している。
  11. migration を追加・編集した PR では、少なくとも pnpm exec nx run ritsubi-vendure-server:db:reset:dry-runpnpm exec nx run ritsubi-vendure-server:db:upgrade:dry-run を通し、 空 DB bootstrap と既存 migration state からの upgrade path の両方を確認する。
  12. ステージング適用
  13. デプロイ前に DB バックアップ取得。
  14. 標準導線では just deploy-fly staging または GitHub Actions deploy を使い、 Fly release_command が migration と additive-only structural repair まで自動実行する。
  15. 手元から shared env の DB に migration だけを当てる場合は just vendure-drift-migrate staging を正本にし、 just vendure-migrate-run staging_vendure は使わない。 後者は DATABASE_URL の Fly private hostname (*.internal) をそのまま解決しようとするため、手元ネットワークからは getaddrinfo ENOTFOUND ...internal で失敗する。
  16. just vendure-drift-migrate <env> / just vendure-drift-sync <env>scripts/ops/vendure-environment-drift.sh の中で flyctl proxy を張り、 prepare-fly-cli-env.sh で既存の Fly 認証状態を再利用してから 127.0.0.1:<port> 経由で DB に接続する。shared env への手動 migration / drift repair はこの proxy 導線に統一する。
  17. just migrate-fly stagingdeploy-fly-app staging / --skip-release-command を使った復旧時に、release contract(migration + structural repair)を手動再実行する補助導線。skip-release は明示確認付きの 例外導線とし、通常の復旧は just recover-fly-promoted ... env=staging を正本にする。
  18. 影響範囲のスモークテストを実施(カタログ参照・カート追加・チェックアウト)。
  19. 本番適用
  20. メンテナンスウィンドウを設定し、直前にバックアップ。
  21. ステージングと同じビルドのイメージをデプロイし、標準導線では just deploy-fly production または GitHub Actions deploy を使って Fly release_command に migration と additive-only structural repair を自動適用させる。
  22. 手元から shared env の DB に migration だけを当てる場合は just backup-postgres-production を先に実行し、その後 just vendure-drift-migrate production を使う。release image を再 deploy しない一回限りの data/schema 補正でも、この順序を崩さない。
  23. just migrate-fly productiondeploy-fly-app production / --skip-release-command を使った復旧時に、release contract(migration + structural repair)を手動再実行する補助導線。skip-release は明示確認付きの 例外導線とし、通常の復旧は just recover-fly-promoted ... env=production を正本にする。
  24. エラー時はマイグレーションをロールバック(PostgreSQL: 同一トランザクション内のため自動巻き戻し。必要に応じて前バージョンのイメージ+バックアップ復元)。

No pending migrations でも終わりではないケース

2026-04-14 に production で確認した実例:

  • 当時の pnpm run migrate:production(migration-only 運用)は No pending migrations
  • しかし React Dashboard の商品一覧は空表示
  • 実原因は featuredAsset 解決時の relation "asset_translation" does not exist

この知見から、migration history が最新でも core schema drift は起こりうる ことを前提に、次を短く確認します。

  1. just vendure-diagnose-dashboard-products <env> を実行する
  2. just vendure-drift-audit <env> を実行する
  3. React Dashboard / Admin API で products query を見る
  4. featuredAsset なしで成功し、ありで失敗するなら translation table 欠落を疑う
  5. information_schema.tables / information_schema.columns で対象 table の有無を確認する
  6. 欠落が限定的なら full sync より repair migration を優先する

2026-04-15 に確認した追加知見:

  • relation "product_option_groups_product_option_group" does not exist は Vendure 3.6 の shared option group 用 core join table 欠落で起こりうる
  • この系統は migration history が最新でも発生する
  • repo 側では repair migration に加えて runtime drift の structural 監査を metadata 駆動にし、同じ join table 群を GraphQL 実行前に fail-fast させる

推奨コマンドの粒度

  • migration だけ: just vendure-drift-migrate <env>
  • structural repair だけ: just vendure-drift-repair-structural <env>
  • baseline / policy / commercial rule sync だけ: just vendure-drift-sync-baseline staging
  • 全部まとめて: just vendure-drift-sync staging

shared env の vendure-drift-sync / vendure-drift-sync-baseline は visibility policy・commercial rule・test customer の fixture seed repair を流しません。 drift:repair:policy / drift:repair:commercial は shared env 向け導線から外し、 policy / commercial の drift は対象を絞った audited repair か seed 定義の見直しで対応します。

shared env から踏みやすい罠

  • just vendure-migrate-run staging_vendure / production_vendure は local から shared DB へ直接つなぐ導線ではない。Fly private hostname を解決できず getaddrinfo ENOTFOUND ritsubi-postgres-db-*.internal で失敗する。
  • just vendure-drift-migrate <env> は Fly proxy 前提の導線であり、shared env の手動 migration はこれを使う。
  • flyctl proxy did not become ready ... no access token available が出たときは、prepare-fly-cli-env.sh が既存ログイン状態を拾えていない。 現在の vendure-environment-drift.sh はこの準備を内包しているため、同スクリプト経由で再実行する。

CI / local の dry-run 期待値

  • db:reset:dry-run は empty DB から current head だけで bootstrap したときに schema drift が残らないことを確認する。
  • db:upgrade:dry-runNX_BASE(または VENDURE_MIGRATION_BASE_REF)時点の migration state から current head へ進め、schema issue / structural drift / migration history set drift が 0 件で終わることを確認する。
  • upgrade-path dry-run は runtime baseline seed の有無ではなく、 schema / migration contract の整合性に絞って判定する。
  • staging / production の machine 起動時は Dashboard Admin API canary も走るため、 relation "...\" does not exist 系の GraphQL 失敗は /health が 503 のまま traffic へ乗らないことを前提にする。
  • drift:repair:structural-schema は schema builder の差分から additive-only query だけを抽出して実行するため、drop / rename / type change は自動適用しない。

局所的な core schema 欠落では、vendure-drift-sync を最初に選ばず、上から順に必要最小限の粒度を選びます。

最短 SQL

SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
  AND table_name IN (
    'asset_translation',
    'product_translation',
    'collection_translation',
    'facet_translation',
    'facet_value_translation'
  )
ORDER BY table_name;

今回の repair 指針

  • asset_translation は Vendure 3.6 系 core schema
  • table 作成後、channel.code='__default_channel__'defaultLanguageCode を使って asset.name から backfill する
  • repo には repair migration を追加し、runtime drift audit 側でも metadata ベースの structural drift として検知する
  • shared option group 系では product_option_groups_product_option_group, product_option_group_channels_channel, product_option_channels_channel をまとめて repair 対象にし、旧 product_option_group.productId が残る環境では Vendure 公式 utility による backfill を優先する

[!NOTE] React Dashboard の GraphQL は HTTP 200 でも errors を返しうるため、Sentry が静かでも network response を直接見る方が早いことがあります。

migration class 名の timestamp 規約

  • migration file 名・class 名・name プロパティ末尾の timestamp は 13 桁 Unix milliseconds に統一する。 例: name = "AddFooBar1779184860000";
  • TypeORM の MigrationExecutor.getMigrations は class 名末尾の 13 文字parseInt(name.substr(-13)) で抽出し、その数値で sort する。 14 桁 (YYYYMMDDHHmmss) を使うと先頭の 2 が落ちて極端に小さい値になり、 新しい migration が古い migration より早く走って依存切れする。 実例: column "searchNormalizedKeywords" does not exist で migrate が失敗した経験あり。
  • filename も新規では必ず 13 桁 Unix ms にする。既存の 14 桁 YYYYMMDDHHmmss_*.ts は legacy grandfather のみで、新規追加は禁止。 既存環境の vendure_migrations.name と一致しなくなるため、 途中で 14 桁→13 桁にリネームする場合は UPDATE vendure_migrations SET name = $1 WHERE name = $2 パターンの rename migration を別途追加する(前例: 1778257199000_rename_legacy_migration_registry_entries.ts1779900000000_rename_14digit_migration_names_to_unix_ms.ts)。
  • 14 桁 → 13 桁の変換は Date.UTC(YYYY, MM-1, DD, HH, mm, SS) の UTC 解釈で行う。 既存 13 桁との衝突は Set 検査して +1000ms シフトする。

初回データ投入フロー(新規環境)

  1. 環境変数を設定し、空の DB に対して初回のみ ALLOW_SCHEMA_SYNC=true で migration を実行し、Vendure のベース schema を生成する。完了後は設定を外す。
  2. populate() またはカスタムインポートスクリプトで初期データ(管理ユーザー、商品、顧客)を投入。 citeturn0search0
  3. 以降はすべてマイグレーション+インポートスクリプトで管理し、synchronize は再度使用しない。

デプロイとプロモーション手順(Fly.io 想定)

  1. ビルド: docker build で単一イメージを作成し、タグ ritsubi-vendure:<git-sha> を付与。
  2. ステージングデプロイ: イメージをプッシュし、ステージングでデプロイ。標準導線では just deploy-fly staging または GitHub Actions deploy を使い、 release_command が migration まで自動実行する。復旧で just deploy-fly-app staging を使う場合だけ、その後に just migrate-fly staging を実行。
  3. 検証: E2E スモーク(商品検索、ログイン、注文)と監視のメトリクスを確認。
  4. プロモーション: 同一イメージを本番へタグ付け/デプロイ。構成差分は AWS Secrets Manager(必要に応じてデプロイ先 Secret へ同期)/ 環境変数のみ。
  5. 本番リリース: release_command による migration 自動適用 → アプリ起動 → 簡易動作確認(管理画面ログイン、GraphQL Health)。

運用チェックリスト

  • マイグレーションファイルがリポジトリにコミット済みか
  • 既存 core entity の custom field 追加時、必要な customFields... カラムが migration に含まれているか
  • VENDURE_SYNCHRONIZE=false(本番・ステージング)を確認
  • ALLOW_SCHEMA_SYNC が空 DB 初期化の場面以外で有効になっていないことを確認
  • セッション鍵・JWT 秘密鍵が全インスタンスで一致
  • バックアップ完了(直近取得時刻を記録)
  • マイグレーション実行ログを保存し、失敗時の手順(リカバリ)を共有
  • スモークテスト結果を記録(ステージング/本番)

ロールバック方針

  • マイグレーション失敗時はトランザクションにより自動ロールバックされるが、 データ損失リスクを避けるためバックアップ復元手順を必ず用意 (MySQL/MariaDB はトランザクション非対応に注意)。
  • アプリケーションは直前の安定イメージに切り戻し、再度マイグレーションを適用しない(DB をバックアップから戻す)。

付録: 推奨環境変数(抜粋)

  • VENDURE_SYNCHRONIZE=false
  • ALLOW_SCHEMA_SYNC(空 DB 初期化時のみ)
  • VENDURE_AUTH_OPTIONS_TOKEN_METHOD=bearer
  • COOKIE_SECRET, SESSION_SECRET(全環境で共有鍵を設定)
  • VENDURE_API_OPTIONS_PLAYGROUND=false(本番では GraphQL Playground を無効化)
  • PORT, DOMAIN, SHOP_API_PATH, ADMIN_API_PATH(環境ごとのエンドポイント)

参照先

  • Vendure Guides: Production Configuration, Migrations, Getting Data into Production, Using Docker, Horizontal Scaling
  • 当プロジェクトのホスティング固有手順: docs/03-implementation/infrastructure/deployment-guide.md, docs/03-implementation/infrastructure/fly-io-postgres-docker.md