コンテンツにスキップ

バックアップとリストア手順

この文書は、production で state を持つデータが現在どう保護されているか を整理した正本です。単なる理想形ではなく、実際に repo にある workflow / script / just recipe / infra role と、2026-05-26 時点の確認結果を基準にまとめています。

まずここだけ読めばよい

現在のバックアップ体制は、まず次の 3 分類で理解すると全体像を掴みやすくなります。

  1. 自動で守れているもの
    Vendure PostgreSQL と WordPress MariaDB は、どちらも 毎時 backup / 30 日保持 まで実運用へ反映済みです。失敗時は Slack alert を出し、WordPress は VPS local、 Postgres は GitHub Actions artifact に短期 fallback を残します。
  2. 意図的に backup 対象外としているもの
    Redis と Storefront maintenance KV は、再構築・再投入前提の運用 state なので、formal backup の対象にしていません。
  3. 将来検討として残しているもの
    Vendure asset と WordPress media は、primary storage のみで運用しており、 secondary backup / versioning / replication はまだ入れていません。

この文書の使い分け

目的 まず参照する節 補足
現在の保護状況を把握したい production バックアップ状況サマリ 何が自動 backup 対象で、何が対象外かを確認する
定期演習として restore drill を実施したい 図で見る restore drill と各系統の Restore drill staging を上書きする actual 実行かどうかを先に確認する
実際に DB を復元したい 各系統の リストア 復元先 DB は上書きされるため、対象環境を取り違えない
契約上の窓口や応答条件を確認したい 保守・サポート計画書 本書は契約条件の正本ではない

実行前チェックリスト

restore drill / 本番復元のどちらでも、実行前に次を確認します。

  • 対象環境(staging / production)
  • 使用する backup file 名
  • 復元先 DB が 上書き対象 であること
  • 実行時点のコードと DB schema の整合性
  • 実行開始/終了時刻、所要時間、結果を記録する担当者
  • 本番 dump を扱う場合の機密情報取り扱い

Production DB 操作ガード

production の DB state を変更し得る標準入口は、直前 backup または明示 confirm を要求します。 repo の wrapper を迂回した raw ssh / flyctl / psql / docker compose 直叩きは、このガードの対象外です。

対象 標準入口 production での挙動
WordPress WP-CLI just wp-cli-vps "<command>" production read-only allowlist 外の command は ritsubi-wordpress-backup.service 成功後に実行
WordPress PHP snippet just wp-php-vps-file <file> production PHP の書き込み有無は判定せず、常に ritsubi-wordpress-backup.service 成功後に実行
WordPress SSH shell just wp-ssh production production-shell confirm 必須。DB 操作が必要なら wp-cli-vps / wp-php-vps-file を優先
Vendure migration / drift repair just migrate-fly production / just vendure-drift-* production just backup-postgres-production 成功後に実行
Vendure drift sync (data 書き込み含む) production では廃止。staging のみ just vendure-drift-sync staging / just vendure-drift-sync-baseline staging production は fixture repair を経由し得る一括 sync を使わない。just vendure-drift-audit production の結果を見て、backup 成功後に対象を絞った audited repair を実行
Vendure deploy release command just deploy-fly production ... / just manual-deploy-vendure production / GitHub Actions production deploy Fly release command 前に just backup-postgres-production を実行 (manual-deploy 経由は image build と並列に backup を起動し、flyctl deploy の手前で wait。手動 backup 済みの再実行では precompleted_backup=<dump名またはR2 URI> を渡す)
Vendure production DB proxy just proxy-postgres production auto production-db-proxy confirm 必須、proxy 開始前に just backup-postgres-production を実行
Vendure Fly console just fly-console-vendure production production-console confirm 必須。DB 操作は監査済み recipe を優先
production fixture sync sync-vendure-test-customers* / sync-vendure-reservation-products* production では fail-closed

production バックアップ状況サマリ

対象 正本 自動バックアップ 現在の状態 復元導線
Vendure PostgreSQL Fly app ritsubi-postgres-db GitHub Actions backup-postgres-prd.yml -> R2 postgres/ 正常: 毎時 backup / 720 時間保持。2026-05-26 に latest dump の pg_restore -l と manual backup success を確認 pg_restore + just proxy-postgres <env>
WordPress MariaDB production VPS 上の MariaDB ritsubi-wordpress-backup.timer -> R2 wordpress/production/db/ 正常: 毎時 backup / 720 時間保持。2026-05-26 に DB container dump へ修正し、timer / R2 / local fallback / alert を確認 gunzip -c ... から wp-cli db import - へ流し込む
Vendure asset R2 ritsubi-ec-assets-prod なし 現行方針: primary storage のみ(独立バックアップは将来検討) 同一バケット上のオブジェクト再取得のみ
WordPress media R2 ritsubi-wp-media-prod なし 現行方針: primary storage のみ(独立バックアップは将来検討) 同一バケット上のオブジェクト再取得のみ
Storefront maintenance state Cloudflare KV ec-storefront-configstorefront:maintenance なし 対象外: 手動再作成前提 Cloudflare KV へ JSON 再投入
Redis session / shared cache Fly Redis ritsubi-redis-prod formal backup は未定義 対象外: セッション/共有キャッシュのみ、再構築前提 再ログイン / cache warm-up

[!IMPORTANT] production の DB backup は 毎時 cadence + 720 時間保持(30 日)へ反映済みです。Vendure PostgreSQL は GitHub Actions success + manual backup successWordPress MariaDB は production VPS 上の timer active + manual upload success まで確認しました。backup 失敗時の Slack alert は BACKUP_NOTIFY_WEBHOOK_URL を正本にします。

図で見る production バックアップ体制

flowchart LR
  subgraph prod["production で state を持つもの"]
    pg["Vendure PostgreSQL\nFly app: ritsubi-postgres-db"]
    wpdb["WordPress MariaDB\nproduction VPS"]
    asset["Vendure asset\nR2: ritsubi-ec-assets-prod"]
    media["WordPress media\nR2: ritsubi-wp-media-prod"]
    redis["Redis session / shared cache"]
    kv["Storefront maintenance KV"]
  end

  gha["GitHub Actions\n毎時 :05"]
  timer["systemd timer\n毎時 :15"]
  backup_bucket["R2 backup bucket\nritsubi-ecommerce-backup\n30 日保持"]
  alert["Slack alert\n:warning:"]
  fallback["短期 fallback\nWP: VPS local 72h\nPG: Actions artifact 3d"]
  rebuild["再構築 / 再投入前提"]
  future["将来検討\nsecondary backup なし"]

  pg --> gha --> backup_bucket
  wpdb --> timer --> backup_bucket
  gha -. failure .-> alert
  timer -. failure .-> alert
  gha -. R2 upload failure .-> fallback
  timer -. R2 upload failure .-> fallback
  asset --> future
  media --> future
  redis --> rebuild
  kv --> rebuild

この図の読み方は次のとおりです。

  • 実際に backup されているのは DB 2 系統だけです。
  • DB backup の失敗は通知対象です。通知 webhook は AWS Secrets Manager b2b-ecommerce/prod/infraBACKUP_NOTIFY_WEBHOOK_URL を使います。
  • asset / media は失われてよいのではなく、現時点では primary storage のみで受容している領域です。
  • Redis / KV は重要度が低いのではなく、正本データではないため再構築方針を採っています。

要件目標と現在の到達状況

要件上の目標は docs/01-requirements/consolidated/requirements-specification.mdRPO 1 時間 / RTO 30 分です。現在の到達状況は次のとおりです。

項目 目標 現在の到達状況
Vendure PostgreSQL RPO 1 時間相当 達成。毎時 HH:05 UTC の custom dump と 720 時間保持で、復元点は最大 1 時間以内。RTO 30 分は 初回 actual restore drill 成功(24 秒) まで確認済み
WordPress MariaDB RPO 1 時間相当 達成。毎時 HH:15 UTC の gzip SQL dump と 720 時間保持で、復元点は最大 1 時間以内。RTO 30 分は 初回 actual restore drill 成功(31 秒) まで確認済み

[!NOTE] Vendure asset / WordPress media は 2026-05-26 時点では primary storage のみを許容し、独立バックアップは将来検討とします。Redis と Storefront maintenance KV は再構築前提の運用 state であり、restore 前提の backup target には含めません。

Alert と障害時 fallback

DB backup の alert は、backup 処理そのものが失敗した事実を operator に出すためのものです。 復元可能性の判定は、alert だけでなく R2 object の存在、dump の読込検証、必要に応じた restore drill で確認します。

対象 Alert 条件 fallback 保持期間
Vendure PostgreSQL dump 生成失敗、R2 upload 失敗、R2 retention cleanup 失敗 R2 upload 失敗時に GitHub Actions artifact へ dump を保存 3 日
WordPress MariaDB dump 生成失敗、R2 upload 失敗、R2 retention cleanup 失敗 VPS local /var/backups/ritsubi-wordpress/ に gzip dump を保存 72 時間

通知 payload は :warning: を使い、Ritsubi Backup Alert として投稿します。 webhook URL は repo に書かず、AWS Secrets Manager b2b-ecommerce/prod/infraBACKUP_NOTIFY_WEBHOOK_URL を正本にします。GitHub Actions の汎用 failure finalizer が使う SLACK_WEBHOOK_URL は fallback として扱います。

障害対応の初動は次の順序で確認します。

  1. Slack alert の対象(Postgres / WordPress)と state(failed / degraded)を読む。
  2. R2 に同時刻の backup object があるか確認する。
  3. R2 upload failure の場合は fallback を確認する。Postgres は Actions artifact、 WordPress は VPS local backup を見る。
  4. dump が存在しても、Postgres は pg_restore -l、WordPress は gunzipwp_options の存在確認で、壊れた復元点ではないことを確認する。

Postgres Actions artifact の確認:

gh run list --workflow backup-postgres-prd.yml --limit 5
gh run view <run-id> --json conclusion,artifacts
gh run download <run-id> -n postgres-backup-local-fallback -D tmp/postgres-backup-local-fallback

WordPress local fallback の確認:

just wp-ssh production production-shell
sudo ls -lh /var/backups/ritsubi-wordpress/

図で見る restore drill

flowchart LR
  dry_run["事前 rehearsal\nRESTORE_DRILL_DRY_RUN=true\njust restore-drill all staging latest"]
  backups["R2 backup bucket\npostgres/ と wordpress/production/db/"]
  actual["actual 実行\njust restore-drill all staging latest restore-drill-staging"]
  stg_pg["staging Vendure DB\nschema recreate + restore + 件数確認"]
  stg_wp["staging WordPress DB\nimport + URL rewrite + verify"]
  record["記録\nbackup file / 所要時間 / 結果"]

  dry_run --> actual
  backups --> actual
  actual --> stg_pg
  actual --> stg_wp
  stg_pg --> record
  stg_wp --> record

初回の actual 実行では、次を確認済みです。

  • Postgres: ritsubi_vendure_20260410143247.dump を staging へ復元し、24 秒
  • WordPress: wordpress-db-20260410T141501Z.sql.gz を staging へ復元し、31 秒
  • ただし 月次の継続実績はまだ 1 回分なので、以後はこの記録を積み上げます

1. Vendure PostgreSQL(Fly.io Docker Postgres)

1.1 現行バックアップ導線

  1. GitHub Actions backup-postgres-prd.yml が毎時 5 * * * *(UTC)で起動する。
  2. workflow は AWS Secrets Manager から production_vendureproduction_infra を読み込む。
  3. workflow は production DB と同じ major の postgresql-client-17 を PGDG から入れる。pg_dump version mismatch 時だけ scripts/ops/backup-postgres-fly.sh が Docker fallback (postgres:<major>-alpine) を使う。
  4. scripts/ops/backup-postgres-fly.sh が以下を実行する。
  5. fly proxy で production DB app ritsubi-postgres-db に接続
  6. pg_dump -Fc で custom format dump (*.dump) を作成
  7. Cloudflare R2 バケット ritsubi-ecommerce-backuppostgres/ プレフィックスへ upload
  8. scripts/ops/backup-postgres-fly.sh は upload 後に postgres/ 配下を走査し、 720 時間(30 日)より古い dump を削除する。
  9. 手動実行の入口は just backup-postgres-production。workflow と同じ scripts/ops/backup-postgres-fly.sh を使う。snapshot-staging-vendure-db は staging 専用の R2 snapshot 入口であり、production 用 recipe は存在しない。
  10. R2 upload / retention cleanup / dump 生成が失敗した場合は BACKUP_NOTIFY_WEBHOOK_URL(または SLACK_WEBHOOK_URL)へ :warning: 付きで通知する。GitHub Actions 上の R2 upload 失敗では local dump を削除せず、 postgres-backup-local-fallback artifact として 3 日だけ保存する。

1.2 現在の確認結果

  • 2026-05-26 時点で直近の backup-postgres-prd.yml は success 継続。
  • R2 postgres/ の latest dump として ritsubi_vendure_20260526024412.dump の生成を確認した。
  • postgres:17-alpinepg_restore -lritsubi_vendure_20260526011213.dump を読み、custom format dump と TOC を確認した。
  • just backup-postgres-production を手動実行し、R2 upload と期限切れ object cleanup が成功した。
  • R2 upload / retention cleanup / dump 生成失敗時は BACKUP_NOTIFY_WEBHOOK_URL(または SLACK_WEBHOOK_URL)へ :warning: 付きで通知する。
  • R2 upload 失敗時は local dump を削除せず、GitHub Actions 上では postgres-backup-local-fallback artifact として 3 日保持する。

1.3 確認コマンド

直近 run の成否:

gh run list --workflow backup-postgres-prd.yml --limit 5

R2 上の最新 dump:

SECRETS_CONFIG=production_infra SECRETS_INCLUDE_SHARED=0 ./scripts/ops/with-env.sh -- bash -lc '
  set -euo pipefail
  endpoint="${BACKUP_R2_ENDPOINT:-https://${BACKUP_R2_ACCOUNT_ID}.r2.cloudflarestorage.com}"
  env -u AWS_PROFILE -u AWS_REGION -u AWS_DEFAULT_REGION \
    AWS_ACCESS_KEY_ID="$BACKUP_R2_ACCESS_KEY_ID" \
    AWS_SECRET_ACCESS_KEY="$BACKUP_R2_SECRET_ACCESS_KEY" \
    AWS_REGION=auto AWS_DEFAULT_REGION=auto \
    aws --region auto s3 ls "s3://${BACKUP_R2_BUCKET_NAME:-ritsubi-ecommerce-backup}/postgres/" \
      --endpoint-url "$endpoint" | tail -n 5
'

1.3.1 一回限りの production migration / data 補正で守る順序

release image を再 deploy せず、手元から production DB に一回限りの migration / data 補正を当てる場合でも、次の順序を崩さない。

  1. just backup-postgres-production
  2. just vendure-drift-migrate production
  3. 必要なら just vendure-drift-audit production
  4. 対象行の確認 SQL またはスモークテスト

[!IMPORTANT] just vendure-migrate-run production_vendure は手元から shared env の DB に直接つなぐ導線ではない。 Fly private hostname (*.internal) を解決できず失敗しうるため、production の手動 migration は vendure-drift-migrate の proxy 導線を使う。

1.4 リストア

backup-postgres-fly.sh が生成するのは plain SQL ではなく custom format dump (*.dump) なので、復元は psql ではなく pg_restore を使う。

  1. R2 から対象 dump をローカルへ取得する。
SECRETS_CONFIG=production_infra SECRETS_INCLUDE_SHARED=0 ./scripts/ops/with-env.sh -- bash -lc '
  set -euo pipefail
  endpoint="${BACKUP_R2_ENDPOINT:-https://${BACKUP_R2_ACCOUNT_ID}.r2.cloudflarestorage.com}"
  env -u AWS_PROFILE -u AWS_REGION -u AWS_DEFAULT_REGION \
    AWS_ACCESS_KEY_ID="$BACKUP_R2_ACCESS_KEY_ID" \
    AWS_SECRET_ACCESS_KEY="$BACKUP_R2_SECRET_ACCESS_KEY" \
    AWS_REGION=auto AWS_DEFAULT_REGION=auto \
    aws --region auto s3 cp \
      "s3://${BACKUP_R2_BUCKET_NAME:-ritsubi-ecommerce-backup}/postgres/<file>.dump" \
      "./<file>.dump" \
      --endpoint-url "$endpoint"
'
  1. 復元先に応じて proxy を張る。
# staging
just proxy-postgres staging

# production は confirm 必須。recipe が直前 backup を作ってから proxy を開く
just proxy-postgres production auto production-db-proxy
  1. DATABASE_URL から対象 DB の user / dbname を確認し、pg_restore を実行する。
 # ローカル
 pg_restore -h localhost -p 5432 -U postgres -d vendure \
   --clean --if-exists --no-owner --no-privileges ./<file>.dump

 # staging (proxy: 15433)
 pg_restore -h localhost -p 15433 -U <staging-user> -d <staging-dbname> \
   --clean --if-exists --no-owner --no-privileges ./<file>.dump

復元後は、少なくとも次を確認します。

  • 必要なテーブルが復元されていること
  • channel 件数など最低限の件数確認が取れること
  • 対象環境でアプリケーション接続エラーが出ていないこと

1.5 Restore drill

Postgres の restore drill は、production backup を staging DB へ実際に戻す演習として just restore-drill に統一しました。

事前 rehearsal(non-destructive):

RESTORE_DRILL_DRY_RUN=true just restore-drill postgres staging latest

actual 実行(staging DB を上書き):

just restore-drill postgres staging latest restore-drill-staging

recipe は次を自動実行します。

  1. R2 postgres/ から対象 dump(既定: 最新)を取得する
  2. staging Fly Postgres へ proxy を張る
  3. public schema を再作成して pg_restore を流し込む
  4. information_schema.tableschannel 件数で最低限の復元確認を行う

演習記録には、実行開始/終了時刻、使用した backup file、所要時間、詰まった点 を残してください。

[!NOTE] 2026-04-10 に just restore-drill all staging latest restore-drill-staging を actual 実行し、 ritsubi_vendure_20260410143247.dump で staging restore success を確認しました。 public table 125 / channel 1、所要 24 秒で完了しています。

1.6 現時点の漏れ

  1. medium: restore drill の 初回 actual 証跡 は得られたが、月次の定例実行履歴は まだ 1 回分のみ。RTO 30 分の継続達成は、今後の月次実績で追跡する。

2. WordPress / MariaDB on VPS

[!NOTE] 2026-05-26 に production restore と backup 修復を実施しました。 障害前に有効な production DB backup として確認できた最新は wordpress-db-20260517T221501Z.sql.gz で、その後の 2026-05-18 08:15 JST 以降の backup は mysqldump 不在により 20 byte 程度の空 gzip でした。復旧後は DB container の mariadb-dump / mysqldump を使う実装へ変更し、R2 / local fallback / Slack alert まで確認済みです。

2.1 現行バックアップ導線

  1. production VPS に ritsubi-wordpress-backup.timer を配備する。
  2. timer は毎時 *:15:00 UTCapps/wordpress-cms/scripts/backup-wordpress-vps.sh を起動する。
  3. script は WordPress container の wp-cli db export ではなく、DB container の mariadb-dump / mysqldump で dump を作る。
  4. dump を先に VPS ローカルの /var/backups/ritsubi-wordpress/wordpress-db-*.sql.gz へ保存し、その後 Cloudflare R2 ritsubi-ecommerce-backup/wordpress/production/db/ へ upload する。 export 結果が空、または wp_options を含む WordPress SQL dump と判定できない場合は、 upload 前に失敗させて壊れた復元点を R2 へ追加しない。
  5. R2 upload / retention cleanup に失敗した場合は、ローカル backup を残したうえで BACKUP_NOTIFY_WEBHOOK_URL(または SLACK_WEBHOOK_URL)へ通知し、service を fail させる。通知 webhook が未設定の場合でも systemd journal には失敗を残す。
  6. dump の R2 保持期間は BACKUP_RETENTION_HOURS=720。script 自体が 720 時間(30 日)より古い wordpress-db-*.sql.gz を削除する。 ローカル backup の既定保持期間は BACKUP_LOCAL_RETENTION_HOURS=72(3 日)で、 R2 障害時の短期復旧点として扱う。

2.2 現在の確認結果

  • 2026-05-26 の復旧では wordpress-db-20260517T221501Z.sql.gz を production へ import し、 home / siteurlpage=12product_detail=3campaign=3announcement=1 を確認した。
  • 復旧直前の安全 backup は production VPS の /home/ubuntu/restore-safety/pre-restore-wordpress-20260526T015610Z.sql.gz に残した。
  • 復旧後の R2 wordpress/production/db/ では wordpress-db-20260526T022112Z.sql.gz などの非空 dump を確認した。
  • wordpress-db-20260526T022112Z.sql.gzgunzip で展開でき、 CREATE TABLE 13 件と wp_options を確認した。
  • ritsubi-wordpress-backup.timer は active。manual run と timer run の両方で R2 upload success を確認した。
  • R2 upload failure の疑似試験では service が失敗し、VPS local backup を残し、 Slack alert が投稿されることを確認した。
  • staging には production と同等の ritsubi-wordpress-backup.timer / /etc/ritsubi/wordpress-backup.env / R2 wordpress/staging/db/ は存在しない。 staging は restore drill の復元先であり、production backup の正本ではない。

2.2.1 2026-05-26 WordPress backup 不全からの教訓

障害発生前に利用可能だった production backup は、R2 上では wordpress-db-20260517T221501Z.sql.gz まででした。2026-05-18 08:15 JST 以降の R2 object は 20 byte 程度の空 gzip で、復元点として使えませんでした。

根本原因は、WordPress container の wp-cli db export が内部で mysqldump を必要とする一方、 runtime container に mysqldump が存在しなかったことです。復旧後の script は DB container の mariadb-dump / mysqldump を使い、次の条件を満たさない dump は R2 upload へ進めません。

  • gzip 前の SQL が空ではないこと
  • wp_options を含む WordPress DB dump であること
  • R2 upload 前に VPS local backup として保存できていること

同種の不全を疑う場合は、R2 object の size だけではなく、実際に gunzip して wp_optionsCREATE TABLE を確認してください。

2.3 確認コマンド

timer と直近ログ:

just wp-backup-status-vps production
just wp-backup-run-vps production

R2 上の最新 dump:

SECRETS_CONFIG=production_infra SECRETS_INCLUDE_SHARED=0 ./scripts/ops/with-env.sh -- bash -lc '
  set -euo pipefail
  endpoint="${BACKUP_R2_ENDPOINT:-https://${BACKUP_R2_ACCOUNT_ID}.r2.cloudflarestorage.com}"
  env -u AWS_PROFILE -u AWS_REGION -u AWS_DEFAULT_REGION \
    AWS_ACCESS_KEY_ID="$BACKUP_R2_ACCESS_KEY_ID" \
    AWS_SECRET_ACCESS_KEY="$BACKUP_R2_SECRET_ACCESS_KEY" \
    AWS_REGION=auto AWS_DEFAULT_REGION=auto \
    aws --region auto s3 ls "s3://${BACKUP_R2_BUCKET_NAME:-ritsubi-ecommerce-backup}/wordpress/production/db/" \
      --endpoint-url "$endpoint" | tail -n 5
'

AccessDenied が出る場合:

  1. AWS Secrets Manager b2b-ecommerce/prod/infraBACKUP_R2_ACCESS_KEY_ID / BACKUP_R2_SECRET_ACCESS_KEY を更新する。
  2. just wp-deploy-vps production backup verify=false/etc/ritsubi/wordpress-backup.env を再配布する。
  3. just wp-backup-run-vps production を再実行し、journal に Uploaded WordPress backup to ... が出ることを確認する。

2.4 リストア

  1. R2 から対象 dump (wordpress-db-*.sql.gz) をローカルへ取得する。
  2. 復元先ホストで docker composewp-cli が正常であることを確認する。
  3. 注意: 復元先の既存 WordPress DB は上書きされる。

production へ復元:

gunzip -c wordpress-db-20260402T190000Z.sql.gz \
  | just wp-cli-vps "db import -" production

just wp-cli-vps "db import -" production は import の前に ritsubi-wordpress-backup.service を実行し、直前 backup が失敗した場合は import へ進みません。

staging へ復元:

gunzip -c wordpress-db-20260402T190000Z.sql.gz \
  | ssh ubuntu@116.80.84.4 "cd /opt/wordpress && sudo docker compose -f docker-compose.yml run --rm -T wp-cli --quiet db import -"

復元後は、少なくとも次を確認します。

  • home / siteurl が対象環境の URL になっていること
  • 商品件数など最低限の目視確認が取れること
  • just wp-verify-vps <env> 相当の検証で公開 URL と compose 状態を確認できること

[!IMPORTANT] WordPress の定期バックアップは DB only です。media 自体は cms-assets.ritsubi-platform.com / ritsubi-wp-media-prod を正本とするため、DB restore は attachment 情報と URL の復元に限定されます。

2.5 Restore drill

WordPress の restore drill も、production backup を staging VPS へ戻す演習として just restore-drill に統一しました。

事前 rehearsal(non-destructive):

RESTORE_DRILL_DRY_RUN=true just restore-drill wordpress staging latest

actual 実行(staging WordPress DB を上書き):

just restore-drill wordpress staging latest restore-drill-staging

recipe は次を自動実行します。

  1. R2 wordpress/production/db/ から対象 dump(既定: 最新)を取得する
  2. staging VPS の WordPress DB へ wp-cli db import - で投入する
  3. cms.ritsubi-platform.com / cms-assets.ritsubi-platform.com を staging domain へ search-replace する
  4. home / siteurl を staging URL へ戻す
  5. just wp-verify-vps staging で compose / cloudflared / 公開 URL を検証する

演習記録には、実行開始/終了時刻、使用した backup file、所要時間、URL 置換結果 を残してください。

[!NOTE] 2026-04-10 に同じ just restore-drill all staging latest restore-drill-staging の actual 実行で wordpress-db-20260410T141501Z.sql.gz を staging VPS へ復元しました。home / siteurl はともに https://cms-staging.ritsubi-platform.com、商品件数は 3、 所要 31 秒でした。

2.6 現時点の漏れ

  1. medium: restore drill の 初回 actual 証跡 は得られたが、月次の定例実行履歴は まだ 1 回分のみ。RTO 30 分の継続達成は、今後の月次実績で追跡する。

3. Vendure asset(Cloudflare R2)

production の商品画像・PDF 等は ritsubi-ec-assets-prod(配信ドメイン: ec-assets.ritsubi-platform.com)を正本とする。

現在 repo 上で確認できるのは以下までです。

  • R2 バケットが primary storage であること
  • Storefront / API 側が VENDURE_ASSET_URL としてそのバケットを参照すること
  • DB backup 用の R2 bucket (ritsubi-ecommerce-backup) とは分離されていること

一方で、次は repo 管理下に存在しません。

  • secondary backup bucket
  • bucket versioning / object lock / replication
  • 定期 export / cross-account copy
  • asset restore drill

2026-05-26 時点の運用判断では、上記は 将来検討 とし、現時点の必須バックアップ対象には含めません。したがって Vendure asset は、primary storage のみを前提に運用する受容リスク として扱います。

4. WordPress media(Cloudflare R2 offload)

production の WordPress media は ritsubi-wp-media-prod(配信ドメイン: cms-assets.ritsubi-platform.com)を正本とする。

wordpress-media-offload.mdwordpress-vps-deployment.md は、WordPress DB backup が DB only であり、media は R2 offload を正本とする前提を明示しています。

したがって、現在の位置づけは次のとおりです。

  • WordPress DB backup は attachment metadata の保護
  • media 実体は ritsubi-wp-media-prod に依存
  • media 実体の secondary backup / versioning / replication は repo 上で未定義
  • VPS deploy は /opt/wordpress/uploads を code sync / --delete の対象から外す。 uploadswp-content/uploads の bind mount であり、repo 側から上書きしない。

2026-05-26 時点の運用判断では、上記は 将来検討 とし、現時点の必須バックアップ対象には含めません。したがって WordPress media も、primary storage のみを前提に運用する受容リスク として扱います。

5. 再構築前提の運用 state

次は production で state を持つが、現行方針では formal backup target ではないものです。

対象 現行方針 備考
Redis (ritsubi-redis-prod) session / shared cache は再構築前提 セッション失効や cache miss は起こり得るが、正本データではない
Storefront maintenance KV storefront:maintenance を手動再投入 単一キーの運用 state
AWS Secrets Manager secret の正本 アプリデータ backup ではないが、DR には必須
  • Vendure 実装では Redis は sessionCacheStrategycacheStrategy にのみ使う。
  • job queue は DefaultJobQueuePlugin の in-memory 運用であり、Redis 永続キューは使っていない。

[!NOTE] 一部文書には Redis AOF / Fly Volume snapshot をバックアップ戦略として書いている箇所がありますが、現在の実装と運用正本では Redis backup は不要 と判断します。Redis 消失時の影響はセッション失効と cache warm-up に留まり、restore 前提のバックアップ対象ではありません。

6. Staging 環境の snapshot 管理

Staging は「dogfood 可能な既知の良好状態」に手動でリセットできるよう、 staging 専用スナップショット 機能を用意しています。Production backup とは別の R2 prefix (postgres/staging-snapshot/) に保存し、最新 5 件を自動ローテーションします。

スナップショット取得

# 現在の staging DB を R2 に保存(保持件数: 5 件)
just snapshot-staging-vendure-db

# オプション: ラベル付きで保存(ファイル名に付与される)
just snapshot-staging-vendure-db label=before-dogfood-2026-05-28

一覧確認

just list-staging-snapshots

復元(デストラクティブ)

# dry-run で確認
RESTORE_STAGING_DRY_RUN=true just restore-staging-snapshot

# 実際に復元(latest スナップショット)
RESTORE_STAGING_CONFIRM=restore-staging-snapshot just restore-staging-snapshot

# 特定のスナップショットを指定して復元
RESTORE_STAGING_CONFIRM=restore-staging-snapshot \
  just restore-staging-snapshot backup=staging-20260528T032435Z.dump

いつ snapshot を取るか

タイミング 操作
dogfood や大きな staging 変更作業の前 just snapshot-staging-vendure-db label=before-<purpose>
SMILE 商品データをインポートした直後 just snapshot-staging-vendure-db label=after-smile-import
staging reset → migrate → seed 後 just snapshot-staging-vendure-db label=clean-baseline

制約・注意事項

  • このスナップショットは staging 専用 です。production の RPO/RTO 保証には影響しません。
  • スナップショット取得には staging_vendure(DATABASEURL)と production_infra(BACKUP_R2*) の secrets が必要です(AWS Secrets Manager)。
  • 復元後は検索インデックスが古くなる場合があります。 必要に応じて just deploy-storefront-staging で Worker を再デプロイしてください。

7. 現時点で残るギャップ

優先度 ギャップ 影響
medium restore drill の初回 actual は成功したが、月次実績はまだ 1 回分のみ RTO 30 分を継続的に満たせるという運用 evidence がまだ薄い
medium Vendure asset / WordPress media の独立バックアップは未整備 primary R2 storage の削除・破損に対する復元点がない

[!NOTE] Vendure asset / WordPress media の独立バックアップは未整備ですが、2026-05-26 時点では必須対応から外し、将来検討事項として扱います。

8. 優先して塞ぐ順序

  1. 月次点検へ just restore-drill all staging latest restore-drill-staging を正式に組み込み、2 回目以降の所要時間を継続記録する。
  2. Postgres / WordPress それぞれの月次実績を蓄積し、RTO 30 分の継続達成を実績値として更新する。
  3. Vendure asset / WordPress media の secondary backup を入れる場合は、DB backup とは別設計で R2 versioning / replication / cross-account copy のどれを正本にするか決める。

注意事項

  • バージョン整合性: DB のスキーマ構造はコードベースと一致している必要がある。本番 dump を古いコードの staging に戻すと migration 差分で失敗し得る。
  • 機密情報: 本番 dump には実顧客情報が含まれる。ローカル・staging へ持ち出す場合は、個人情報保護方針に従い、必要に応じて masking を行う。