Deploy Runbook¶
Vendure / Dashboard / Storefront を staging / production へ deploy する際の事前準備、想定エラー、復旧コマンドを 1 ページに集約する。背景の incident は #848 などを参照。
TL;DR (定常運用)¶
| 目的 | コマンド |
|---|---|
| 全面 (Vendure + Dashboard + Storefront) を local から deploy | just deploy-all <env> |
| Vendure backend + Dashboard だけ deploy | just deploy-vendure-with-migration <env> |
| production へ 5 分以内に反映 (build 済み image 昇格のみ) | just fast-deploy-production |
| Storefront だけ deploy | just cloudflare-deploy-storefront <env> |
| Storefront + Dashboard を prod-preview へ deploy | Deploy Prod Preview workflow |
| 全面 (staging) を CI から起動 | just manual-deploy-staging |
| 全面 (production) を CI から起動 (main 再配信) | just manual-deploy-production |
| WordPress CMS plugin を VPS へ配信 | just wp-deploy-vps <env> |
manual-deploy-staging などの just recipe 引数は positional で渡す。just manual-deploy-staging ref=develop ...
のように recipe 名の後ろへ key=value を置くと、その文字列が ref として渡され GitHub Actions dispatch が
No ref found for: ref=develop で失敗する。明示指定する場合は次の形を使う:
just manual-deploy-staging develop true true true true
deploy-vendure-with-migration は drift audit (--allow-pending-migrations) → image build → Fly deploy → Dashboard deploy → post-deploy verify を一括する。新 migration / 新 entity を含む deploy では必ずこちらを使う (通常の manual-deploy-vendure は drift で abort する)。
deploy-all は (1) Vendure backend + Dashboard → (2) Storefront の順で deploy し、Vendure migration 完了を待ってから Storefront を Cloudflare へ反映する。deploy_storefront=false / deploy_dashboard=false で個別 skip 可能。
Production 5 分 fast lane¶
production へ手動で 5 分以内に反映したい場合は、CI / staging で build 済みの Vendure image を昇格する fast-deploy-production を使う:
just fast-deploy-production
この入口は既定で origin/main を対象にし、main 未到達 ref は FAST_DEPLOY_PRODUCTION_ALLOW_NON_MAIN=true を明示しない限り拒否する。さらに MANUAL_DEPLOY_SLA_SECONDS=300 と DEPLOY_REQUIRE_PREBUILT_IMAGE=true を既定で付ける。production 用 image が既にある場合はそれを使い、無い場合は同一 SHA の staging image を production tag へ registry copy してから deploy する。どちらも見つからない場合は ローカル Docker build へ fallback せず abort する。5 分以内反映の契約を守るため、build が必要な変更は先に staging / CI で image を作る。
明示 image を昇格する場合:
just fast-deploy-production "" registry.fly.io/ritsubi-ecommerce:prod-<sha>
Dashboard 変更を含まない backend-only hotfix では Dashboard deploy を外せる:
just fast-deploy-production "" "" false
新 migration / 新 entity を含む場合も fast lane は allow_pending_migration=true で drift audit を通す。ただし release phase で実 migration が走り、schema 変更があると production backup が必要になるため、5 分を超える可能性がある。その場合は SLA 警告を本物の signal と扱い、次回以降は事前 backup または通常の deploy-vendure-with-migration production に戻す。
Deploy 面 inventory¶
| Surface | Where deployed | Local recipe | deploy-all 同梱 |
|---|---|---|---|
| Vendure app + worker | Fly (ritsubi-ecommerce / -staging) |
just fly-deploy-vendure |
✅ |
| React Dashboard | Cloudflare Workers (vendure-dashboard*) |
nx run ritsubi-vendure-server:dashboard:deploy:<env> |
✅ |
| Storefront | Cloudflare Workers (ec-storefront*) |
just cloudflare-deploy-storefront <env> |
✅ |
| Storefront Prod Preview | Cloudflare Worker (ec-storefront-prod-preview) |
just cloudflare-deploy-storefront prod-preview |
❌ (release 前 lane) |
| Dashboard Prod Preview | Cloudflare Worker (vendure-dashboard-prod-preview) |
nx run ritsubi-vendure-server:dashboard:deploy -- prod-preview |
❌ (release 前 lane) |
| Storefront Preview | Cloudflare Worker (ec-storefront-preview) |
nx run ritsubi-storefront:cloudflare:deploy:preview |
❌ (PR 用) |
| WordPress CMS plugin | VPS | just wp-deploy-vps <env> |
❌ (別 cadence) |
| Fly secrets sync | Fly (bundled in deploy-fly) |
just sync-fly-secrets <env> |
✅ (内側で実行) |
| Fly Redis password sync | Fly Redis app (ritsubi-redis-*) |
just sync-fly-redis-password <env> |
✅ (内側で実行) |
| CF maintenance KV namespace | Cloudflare (one-time) | just cloudflare-maintenance-kv-create <env> |
– (初回 setup) |
| CF R2 buckets | Cloudflare (one-time) | just cloudflare-r2-cache-bucket-create <env> |
– (初回 setup) |
WordPress は別 git ref / 別 release cadence で配信するため deploy-all には含めない。WP の plugin / theme 変更時は明示的に just wp-deploy-vps を叩く (docs/03-implementation/infrastructure/wordpress-drift-runbook.md 参照)。
Pre-flight (recipe が自動で行う)¶
scripts/ops/manual-deploy-vendure.sh の Phase 0 で:
- Advisory lock:
/tmp/ritsubi-deploy-locks/<env>.lockで同一環境への並列 deploy を直列化。既に他 process が deploy 中なら早期 abort して Fly registry / machine update 競合を防ぐ。PRE_DEPLOY_SKIP_LOCK=trueで skip 可 - Auth pre-flight: Fly (
flyctl auth whoami) / AWS Secrets Manager (aws sts get-caller-identity) / Cloudflare (with-env.sh経由でCLOUDFLARE_API_TOKEN取得) を 1 秒以内で probe。遅い phase (image build / Dashboard deploy) で auth 切れに当たって巻き戻すリスクを排除。PRE_DEPLOY_SKIP_AUTH_CHECK=trueで skip 可 - 非互換 migration 検知: production + bluegreen 戦略時に直近 24h の migration を scan し
DROP COLUMN/ALTER COLUMN ... NOT NULL等の pattern を検出したら warning。DEPLOY_FLY_STRATEGY=canaryへの切替を促す。PRE_DEPLOY_SKIP_MIGRATION_SCAN=trueで skip 可、PRE_DEPLOY_MIGRATION_SCAN_STRICT=trueで abort 化 - plugins dist precheck:
packages/plugins/dist/index.jsを確認し、欠落していればnx reset+pnpm -C packages/plugins buildを実行 - Fly machine settle 待ち:
flyctl secrets set等で transitional な machine が残っていないか polling (最大 90 秒)。PRE_DEPLOY_SKIP_MACHINE_SETTLE=trueで抑止可 - production backup:
BACKUP_POSTGRES_PRODUCTION_SKIP=trueを deploy-fly へ transit して二重 backup を避ける - drift audit:
--fail-on-drift。allow_pending_migration=true(with-migration recipe) では reconcilable drift を warn 化
これらは過去 incident で実際にハマったポイントなので、手動で実行する手順を runbook 化していない。skip するには各々の env を export する。
Vendure staging が deploy_dashboard=false でも長い場合は、Dashboard ではなく backend image
build/push を疑う。build-vendure-image.sh は CI で Fly registry cache と GitHub Actions cache
を使い、local では registry cache read-only + filesystem cache mode=min を既定にする。詳細な
切り分けは deployment-guide.md の「staging manual deploy が image build/push で待つ場合」を参照し、
local log は ${TMPDIR:-/tmp}/ritsubi-vendure-image-build/、CI log は
vendure-image-build-<env>-<sha> artifact の .summary.json を見る。
Deploy strategy 全体像¶
各 surface の deploy 戦略と zero-downtime 保証を以下にまとめる。「blue-green」は Fly Machine の strategy = "bluegreen" を指し、Cloudflare Workers は仕組み上の atomic switch で同等の zero-downtime を実現する。
| Surface | 戦略 | zero-downtime | 並走期間 | 設定の正本 |
|---|---|---|---|---|
| Vendure production (Fly) | bluegreen |
✅ | あり (全 machine 並走 → health 後 swap) | apps/vendure-server/fly.toml [deploy] |
| Vendure staging (Fly) | rolling (max_unavailable=1) |
△ (短時間瞬断) | なし (1 台運用) | apps/vendure-server/fly.staging.toml [deploy] |
| React Dashboard (CF Workers) | atomic switch (CF default) | ✅ | なし (新 version 即時 100%) | apps/vendure-server/wrangler.jsonc |
| Storefront (CF Workers) | atomic switch (CF default) | ✅ | なし (新 version 即時 100%) | apps/storefront/wrangler.toml |
| Prod Preview Workers | atomic switch + Cloudflare Access | ✅ | なし | deploy-targets.sh + Access application |
| Storefront Preview | atomic switch | ✅ | なし | 同上 (--env preview) |
| WordPress plugin (VPS) | rsync + WP-CLI | △ | なし (in-place 上書き) | just wp-deploy-vps |
ポイント:
- 純粋な blue-green (旧新並走 → swap) は Vendure production の Fly Machine だけ
- Vendure production は Fly の
bluegreenに加え、manual-deploy-vendureの deploy safety gate で新 release の Machine / process group / Fly checks / public/health/ready/ public/versionを二重検証する。さらに切替前に target image tag / build metadata / 現行 production health を確認し、 provenance mismatch や degraded current production はflyctl deploy前に止める。 自動 rollback は追加の production switch を発生させるため既定 off - この保証は deploy wrapper 経由の production deploy に対する fail-closed contract。Fly が traffic swap を担当するため「一瞬も traffic が向かない」ことを repo 側だけで保証するものではないが、 health / version が target SHA に収束しない deploy は成功扱いにしない
- Cloudflare Workers は新 version upload → routing を一括差し替えする atomic switch。並走期間は無いが、deploy の瞬間に traffic が切り替わるため実用上は zero-downtime
- CF Workers にも
gradual deployments(10% → 50% → 100% の段階配信) とversionsAPI が存在するが、本プロジェクトでは未採用。必要になったらdeploy-runbookの改修と一緒に検討する - prod-preview は段階配信ではなく hidden lane です。production traffic は流さず、 Cloudflare Access 保護 host で未リリース Storefront / Dashboard build を production backend に接続して検証します。
- prod-preview は release candidate の短時間確認用です。Cloudflare Worker / route / Access / KV は維持してよいが、追加の Vendure Fly app / machine / DB は作らない。 Fly cost を増やさないため、backend は常に現行 production Vendure を使う。
- prod-preview の deploy target は
prod-previewだが、AWS Secrets Manager は production secrets を読む。GitHub Actions の OIDC subject も production trust に 揃えるため、deploy/smoke job の GitHub Environment はproductionを使い、 Worker target / public URL / smoke environment はprod-previewのまま維持する。 - release 後に prod-preview を閉じる場合は Cloudflare 側だけを止める。厳密に閉じたい時は
order-prod-preview.ritsubi-platform.com/*/dashboard-prod-preview.ritsubi-platform.com/*の route を削除し、次回 release 前に再作成する。production Vendure / WordPress / DB は触らない。 - WordPress plugin は in-place 上書きで完全な zero-downtime にはならない。dev/staging で動作確認した上で短時間メンテ枠を取る運用
blue-green を一時的に canary へ切り替える¶
DROP COLUMN NOT NULL や非互換 enum 変更など、deploy 中に新旧 machine の混在で fail する migration を含むときは Vendure production の blue-green を canary に倒す:
DEPLOY_FLY_STRATEGY=canary just deploy-vendure-with-migration production
DEPLOY_FLY_STRATEGY は canary|bluegreen|rolling|immediate を受け付ける。fly.toml の [deploy] strategy は触らず、env override で 1 回限りの切替を実現する。
CF Workers gradual deployments を有効化したい場合 (未採用)¶
Storefront / Dashboard も blue-green 相当に並走させたい場合は Cloudflare gradual deployments を導入する。具体的には:
wrangler deploy --versionで version だけ作成 (traffic は割り当てない)wrangler versions deploy <version_id>@10% <prev_version_id>@90%で段階配信- 各段で
/health/ready等を polling、問題なければ 100% に進める
gradual deployments を採用する判断は (a) Storefront の SSR が複雑な runtime invariant を持つ、(b) Dashboard 側で破壊的 UI 変更を含む、いずれかが発生したタイミングで再評価する。
想定エラーと復旧手順¶
1. Cannot find module '@ritsubi/plugins/dist/*.js'(index.js / rule-engine/shipping.js 等)¶
症状: ローカルの Dashboard build フェーズ、または CI の pre-deploy drift audit(nx run ritsubi-vendure-server:drift:audit → node dist/scripts/audit-runtime-drift.js)で失敗。@ritsubi/plugins/dist/index.js や @ritsubi/plugins/dist/rule-engine/shipping.js のような plugins の dist サブモジュールが解決できない。
原因: nx ビルドキャッシュが、plugins の source リファクタ(ファイル移動 / 分割 / 拡張子変更)より前の stale な packages/plugins/dist を "build fresh" 扱いで復元し、tsc を再実行せず古い dist を供給する。CI では @ritsubi/plugins:build [remote cache] + Nx read the output from the cache instead of running the command for N out of N tasks がログに出る。packages/plugins/dist は git untracked なので必ず CI / local の nx cache 側の問題(commit 由来ではない)。
復旧(ローカル):
pnpm exec nx reset
pnpm -C packages/plugins build
ls packages/plugins/dist/index.js # 存在することを確認
復旧(CI / deploy): ローカルの nx reset は効かない。develop scope の poisoned nx GitHub Actions cache を bust する。deploy ログの Cache restored from key: Linux-X64-nx-ref-... のキーを確認して削除する:
gh api -X DELETE "repos/{owner}/{repo}/actions/caches?key=<その nx-ref key>"
# もしくは develop scope の nx-ref cache を id 指定で一掃
gh api --paginate "repos/{owner}/{repo}/actions/caches?ref=refs/heads/develop&per_page=100" \
| jq -r '.actions_caches[]|select(.key|test("nx-ref"))|.id' \
| xargs -I{} gh api -X DELETE "repos/{owner}/{repo}/actions/caches/{}"
削除後に deploy を再実行すると cache miss → tsc -p の full build で正しい dist が再生成される。codeql 等の巨大 cache に押されて GitHub LRU が nx cache を自然 evict すると、削除なしでも次回 deploy で解消することがある。
manual-deploy-vendure.sh の pre-flight は local の packages/plugins/dist を自動復旧するが、CI 経由 deploy の drift audit はこの cache poisoning で再発し得る(2026-06 の rule-engine リファクタで発生)。恒久的な再発防止が必要なら drift audit を --skip-nx-cache で常に fresh build にする選択肢がある(deploy ごとに plugins 再 compile 数秒のコスト)。
2. failed to push registry.fly.io/...: unknown: app repository not found¶
症状: image push 段階で transient 失敗。
原因: Fly registry token の一時的な auth glitch。
復旧: BUILD_PUSH_RETRIES (default 2) で自動 retry されるため、operator は何もしなくてよい。3 回目以降も失敗するときは flyctl auth whoami で token の app access を確認:
flyctl apps list | grep ritsubi-ecommerce
3. aborted: machine is replacing: concurrent update in progress¶
症状: deploy-fly フェーズで全 machine update が失敗。
原因: 直前の flyctl secrets set が machine restart を trigger し、deploy がそれと衝突した。
復旧: pre-flight Fly machine settle 待ち が 90 秒で次状態確定を待つように改修済 (2025-06)。それでも発生する場合は手動で:
flyctl status -a ritsubi-ecommerce # transitional machine が無いか確認
flyctl machine wait <machine_id> -a ritsubi-ecommerce
just deploy-vendure-with-migration production
4. release_command failed: VENDURE_HARDEN_PLUGIN_ENABLED must be 'true'¶
症状: production deploy の release_command で HardenPlugin guard が abort。
原因: Fly secret が未設定。
復旧:
# 注意: bare `flyctl secrets set` は bluegreen deploy を暗黙発動するため禁止。wrapper を使う。
bash scripts/ops/fly-secrets-set.sh -a ritsubi-ecommerce VENDURE_HARDEN_PLUGIN_ENABLED=true
# --deploy なしなので stage のみ。次の deploy で反映される:
just deploy-vendure-with-migration production
5. release_command failed: Runtime drift detected¶
症状: release_command の startup で SchemaDrift detector が abort。
原因: コード側で entity / custom field を追加したが対応する migration が抜けている。
復旧: 不足 migration を作って commit → push → deploy 再実行。詳細は schema-drift-runbook.md。
6. post-deploy-check.sh HTTP 000¶
症状: /health/ready が 000 で false alarm。
原因: deploy 直後の dashboardApiCanary warmup や machine 切替で probe が瞬間的に timeout。
復旧: probe は 5s x 6 attempts で retry されるよう改修済 (2025-06)。それでも残るときは:
curl -sS https://commerce.ritsubi-platform.com/health/ready | jq .
を手動で確認し、status: ok ならば deploy 自体は成功。verify だけ手動で再実行:
bash apps/vendure-server/scripts/post-deploy-check.sh production
7. Vendure 成功 / Dashboard 失敗¶
症状: [manual-deploy-vendure] ABORT: fly_fail=0 dashboard_fail=N。
原因: Vendure backend は OK だが、Dashboard 側の Cloudflare deploy や build が失敗。
復旧: backend は既に新 image で稼働しているため、Dashboard を単独再実行する:
SECRETS_CONFIG=production_vendure ./scripts/ops/with-env.sh -- \
./scripts/ops/nx.sh run ritsubi-vendure-server:dashboard:deploy:production
operator が DEPLOY_DASHBOARD_BLOCKING=false を export して deploy した場合は exit 0 で続行され、recipe 末尾に WARN ログが残る (Vendure は live、Dashboard は前 version のまま)。後追いで Dashboard を deploy すれば良い。
8. worker process が停止して queue が詰まる¶
詳細は job-queue-runbook.md を参照。/health/ready の jobQueueLag で fail を観測する。fly.toml の [[restart]] policy = "always" で自動復旧されるが、max_retries 超過したら machine を手動で start する:
flyctl machine start <worker_machine_id> -a ritsubi-ecommerce
CI 経由 deploy (workflow_dispatch)¶
# staging に全面 deploy (vendure / storefront / dashboard)
just manual-deploy-staging develop true true true true
# staging smoke 成功後、Storefront / Dashboard を prod-preview へ配信
gh workflow run .github/workflows/deploy-prod-preview.yml --ref develop
# prod-preview smoke を手元から再実行
just env-status prod-preview
just storefront-shadow-probe prod-preview
just storefront-business-canary prod-preview
# release 後に hidden lane を明示的に閉じる場合は Cloudflare route だけを外す。
# Cloudflare 側は低コストなので、通常は Access 保護のまま idle にして次回 release で再利用してよい。
# 新 migration を含む CI deploy (allow_pending_migration=true)
gh workflow run .github/workflows/deploy-staging.yml --ref develop \
-f vendure=true -f storefront=true -f dashboard=true \
-f allow_pending_migration=true
CI deploy は GITHUB_ACTIONS=true を見て AWS secrets injection 経路を skip し (prepare-aws-deploy-secrets action が既に inject している)、local 専用 fallback と二重実行しない。
CI image build / buildcache 方針¶
deploy パイプラインの build 時間に効く原則。詳細根拠は各ファイルの inline コメントが SSOT。
- Fly registry は deploy 成果物(image push)専用にし、buildcache の経路から外す。buildcache は最適化に過ぎず deploy の critical path を塞いではならない。正本は
scripts/ops/build-vendure-image.sh。 - CI の buildcache は gha backend に一本化(Docker 公式が GitHub Actions 内で推奨)。
cache-toはmode=min+ignore-error=trueで非ブロッカー。registry への buildcache 書き出しは既定 OFF(exporting cache to registryが deploy を ~111s 塞いでいた実測を撤去)。 - registry
mode=maxへの深い cache が要る時のみBUILDX_CI_REGISTRY_CACHE_TO=true。 - Dockerfile (
apps/vendure-server/Dockerfile.fly): workspace packages の build を app COPY より前に置き、COPY apps/vendure-server/は build 入力 subdir(src/scripts/static/types/public/tsconfig/project/package)に分割して cache 無効化面を最小化する。scripts/ops/copy-vendure-static.mjsはcopy:staticが参照するため image へ同梱必須。 - deploy / build job の
pnpm installは app スコープに絞る:setup-pnpm-workspaceのpnpm-filter入力にritsubi-storefront.../ritsubi-vendure-server...を渡し、monorepo 全体 install(~48s)を避ける。nx/wrangler/tscは root devDep(catalog:)で filter install でも常に入るため app スコープで足りる。Vendure Fly Dockerfile base stage と同じ実証済みパターン。 - flyctl は
mise.tomlで version 固定 し、各 job はmise-toolsにflyctlを入れて mise tool cache(hashFiles('mise.toml')キー)から配布する。install-flyctl(curl latest, 非 cache, ~19s)は段階的に廃止中。
関連リンク¶
- 観測 surface 全体像:
monitoring-operations.md - schema drift runbook:
schema-drift-runbook.md - job queue runbook:
job-queue-runbook.md - WordPress drift runbook:
wordpress-drift-runbook.md - recipe 実装:
scripts/ops/manual-deploy-vendure.sh - Fly deploy 包み込み:
apps/vendure-server/scripts/deploy-with-metadata.sh - post-deploy verify:
apps/vendure-server/scripts/post-deploy-check.sh