WordPress VPS デプロイメントガイド (Ansible + Cloudflare Tunnel)¶
概要¶
この文書では、WebArena Indigo の VPS(Ubuntu)へ WordPress 環境をデプロイし、Cloudflare Tunnel 経由で公開するための手順を説明します。
構成¶
- ホスト: WebArena Indigo VPS (Ubuntu 24.04 推奨)
- 公開方式: Cloudflare Tunnel (cloudflared)
- 実行基盤: Docker Compose (WordPress, MariaDB, Socat)
- プロキシ: Vendure Server (Fly.io) が WordPress の GraphQL エンドポイントをプロキシ
- 自動化: Ansible + Justfile
環境情報¶
| 環境 | IP アドレス | 公開ドメイン |
|---|---|---|
| Staging | 116.80.84.4 |
https://cms-staging.ritsubi-platform.com |
| Production | 116.80.84.2 |
https://cms.ritsubi-platform.com |
前提条件¶
必要なツール¶
- Ansible:
mise exec ansible -- ...経由で使用可能 - Just: タスクランナー
- SSH 接続: 手元で通常の
ssh ubuntu@<ip>が通る OpenSSH 設定があること。just wp-*-vps/ Ansible はその設定を正として利用し、必要時は~/.ssh/agent-environmentを自動読込する
VPS SSH 接続情報¶
| 環境 | IP アドレス | SSH ユーザー | デプロイパス |
|---|---|---|---|
| Staging | 116.80.84.4 |
ubuntu |
/opt/wordpress |
| Production | 116.80.84.2 |
ubuntu |
/opt/wordpress |
Ansible インベントリ: infra/ansible/inventory/{staging,production}.yml
ステップ 1: インフラ構成の定義 (Terraform)¶
Cloudflare Tunnel および DNS レコードは Terraform で管理されています。
- 設定ファイル:
infra/terraform/cloudflare-apps/ritsubi-platform.com/tunnels.tf
[!NOTE] 本リポジトリの Ansible は
/etc/systemd/system/cloudflared.serviceを直接管理し、WordPress VPS ではcloudflared tunnel --protocol http2 run --token ...を既定にしています。Production で QUIC のtimeout: no recent network activityが断続的な 502 を起こしたため、CMS 用 Tunnel は HTTP/2 を正とします。
新しいトンネルを作成した場合は、生成された tunnel_token を AWS Secrets
Manager に登録する必要があります。
ステップ 2: 環境変数の設定 (AWS Secrets Manager)¶
WordPress 運用に必要な変数を AWS Secrets Manager に設定します。
| 環境 | SSM パス | SECRETS_CONFIG 値 |
|---|---|---|
| Staging | b2b-ecommerce/staging/wp |
staging_wp |
| Production | b2b-ecommerce/prod/wp |
production_wp |
主要な変数:
WORDPRESS_DB_PASSWORD: データベースパスワードWP_ADMIN_USER/WP_ADMIN_PASSWORD: 初期管理者情報CLOUDFLARE_TUNNEL_TOKEN: Cloudflare Tunnel の接続トークン(後述)STOREFRONT_URL: 連携先フロントエンドの URL(server-to-server で叩く canonical origin。production はhttps://order.ritsubi-platform.com)STOREFRONT_PUBLIC_URL: WP 管理画面の preview / パーマリンクで エンドユーザーに露出させる公開 alias。未設定ならSTOREFRONT_URLを流用。 production はhttps://medical.ritsubi.co.jpを設定するVENDURE_BASE_URL: Vendure API の URLSENTRY_DSN: WordPress PHP runtime から送る Sentry DSNSENTRY_ENVIRONMENT:staging/productionの environment 名SENTRY_RELEASE: 任意。例:wordpress@<git-sha>SENTRY_SEND_DEFAULT_PII: 任意。trueの場合のみユーザー/IP を送信WORDPRESS_BROWSER_SENTRY_DSN: 任意。WordPress browser runtime を別 DSN に分けたい場合に使用WORDPRESS_BROWSER_SENTRY_ENVIRONMENT: 任意。未設定時はSENTRY_ENVIRONMENTを流用WORDPRESS_BROWSER_SENTRY_RELEASE: 任意。未設定時はSENTRY_RELEASEを流用WORDPRESS_BROWSER_SENTRY_SEND_DEFAULT_PII: 任意。未設定時はSENTRY_SEND_DEFAULT_PIIを流用
just deploy-wp-staging / just deploy-wp-production は、AWS Secrets
Manager の staging_wp / production_wp から取り出した SENTRY_* を VPS 上の
/opt/wordpress/.env へそのまま反映します。WordPress 側ではこの .env
を正として runtime の Sentry 設定を読み込みます。
ステップ 3: デプロイの実行¶
プロジェクトルートの just レシピを使用してデプロイを行います。
フルデプロイ (OS 設定・パッケージ込)¶
初回デプロイや、Docker/cloudflared 自体の設定変更時に使用します。
# Staging
just deploy-wp-staging
# Production
just deploy-wp-production
just wp-deploy-vps / just deploy-wp-* は、完了後に just wp-verify-vps
を自動実行します。さらに verify=true のままなら just wp-drift-audit-vps <env> remote-deploy
も続けて実行し、plugin / ACF / option / URL など deploy で壊してはいけない構成を read-only
監査します。remote-deploy は通常 deploy で seed しない固定ページ不足を deploy 失敗扱いにしません。
固定ページを含む live CMS state の厳格監査は、別途 just wp-drift-audit-vps <env>(既定 remote)で行います。一時的に外形確認を省略したい場合のみ
verify=false を明示してください。
通常の VPS deploy では WordPress の bootstrap / seed-data を実行しません。
bootstrap は固定ページ、バナー、ストア設定、ランキングなどの CMS content / option
を seed 値へ更新するため、production で通常 deploy と同時に走らせると運用中の CMS
内容を上書きします。一度 remote WordPress が稼働した後の CMS state は backup /
restore を正本にし、staging / production の deploy 導線には bootstrap task を置きません。
bootstrap.sh 側も remote CMS での実行を拒否します。
apps/wordpress-cms/ の code sync では /opt/wordpress/.env と
/opt/wordpress/uploads を配備物として扱いません。.env は Secrets
Manager 正本から env task が生成する専用ファイルであり、uploads は
production の wp-content/uploads に bind mount される media local cache です。
deploy の code フェーズではどちらも明示的に除外されます。repo 側の local 用
.env や uploads が VPS に混入した場合は role が fail するため、手動 rsync
を行う場合も同じ前提を崩さないでください。
手動 rsync が必要な場合は、apps/wordpress-cms/deploy-rsync-filter.txt を
除外ルールの正本として再利用してください。Ansible も同じ filter を読むため、
このファイルを迂回して独自の rsync 引数を書くと、.env 混入や media local cache
上書きの再発経路になります。/opt/wordpress/ への 2 段目の rsync でも
.env / db-data / wordpress-data / uploads を exclude し、--delete
対象に runtime state を混ぜません。
フルデプロイ(infra を含む実行)では、次の安定化設定も同時に反映されます。
- VPS に 1GiB の swapfile を作成し、
vm.swappiness=10を永続化する。 - WordPress コンテナに healthcheck を設定し、
vendure-proxyはunless-stoppedで自動復帰させる。 - Cloudflare Tunnel 配下のため、VPS の
8181/tcpは外部公開せず UFW で閉じる。 - Production では
ritsubi-wordpress-backup.timerを配備し、DB backup を R2 へ毎時保存する。
差分更新 (コード・環境変数のみ)¶
日々のソースコード更新や .env の変更を高速に反映したい場合に使用します。
# Staging
just update-wp-staging
# Production
just update-wp-production
運用・メンテナンス¶
状態確認¶
VPS 上のコンテナ稼働状態を確認します。
just wp-status-vps staging
just wp-verify-vps <env> は外形確認だけでなく、次もまとめて確認します。
cloudflaredが active で--protocol http2になっていること。/swapfileが有効でvm.swappiness=10になっていること。8181/tcpが UFW で遮断され、db/wordpress/vendure-proxyが期待状態で稼働していること。- 公開 CMS の
wp-login.phpが200、post-new.php?post_type=product_detailが未ログイン時302/303を返すこと。
ログ確認¶
特定のサービスのログをリアルタイムで表示します。
# wordpress サービスのログを表示
just wp-logs-vps wordpress staging
# live WordPress drift を監査
just wp-drift-audit-vps staging
[!TIP]
just wp-drift-audit-vps <env>は SSH で wp-cli を叩くコマンドではありません。
WORDPRESS_ENDPOINTとCMS_API_TOKENを使って、live WordPress の/wp-json/ritsubi/v1/drift-auditを read-only で呼び出します。
そのため、drift 監査だけなら GitHub Actions や手元の shell に SSH 鍵は不要です。
SSH ログイン¶
トラブルシューティングのために VPS へ直接ログインします。
just wp-ssh staging
[!NOTE] リポジトリ標準の
just wp-*-vps/ Ansible は、手元で通常のssh ubuntu@<ip>が通る OpenSSH 設定を正として利用します。現在は-iの固定指定を避け、必要時のみ~/.ssh/agent-environmentを自動読込するため、普段使っている agent / RSA fallback / Host 設定と一致した経路で接続できます。
[!IMPORTANT] ただし drift 監査だけは例外です。
just wp-drift-audit-vpsは SSH ではなく認証付き HTTP endpoint を使います。SSH が必要なのはwp-ssh/wp-cli-vps/wp-logs-vpsなどの server-side 操作です。
WP-CLI の実行¶
VPS 上の WordPress に対して wp-cli コマンドを直接実行できます。
production では wp-cli-vps が read-only と判定できない WP-CLI command の前に
ritsubi-wordpress-backup.service を実行します。backup が失敗した場合、本操作には進みません。
# プラグイン一覧を表示
just wp-cli-vps "plugin list" staging
# ユーザー一覧を表示
just wp-cli-vps "user list" production
# production DB を変更するコマンドは直前 backup 後に実行される
just wp-cli-vps "option update blogdescription 'updated by maintenance'" production
複数行の PHP や => を含む配列を ad-hoc で実行したい場合は、inline の php -r
/ wp eval を ssh 経由で直書きせず、file-based helper を使います。
production で wp-php-vps-file を使う場合も、PHP の DB 書き込み有無を静的判定しないため
常に直前 backup を取ります。
# wp-load.php を自動読込した状態で PHP ファイルを実行
just wp-php-vps-file path/to/script.php production
[!WARNING]
=>を含む PHP 断片を shell の多重 quoting に載せると、>がリダイレクトとして解釈されてリポジトリ root にゼロバイトファイルを生成することがあります。一時調査やデータ補正でも、inline 実行ではなくwp-php-vps-fileを使ってください。
DB バックアップ (production)¶
production VPS では ritsubi-wordpress-backup.timer が 毎時 :15 UTC
で WordPress DB backup を実行し、Cloudflare R2 バケット
ritsubi-ecommerce-backup の wordpress/production/db/ へ保存します。
保持期間は 720 時間(30 日)です。
前提:
production_infraのBACKUP_R2_*を解決できること- media は
cms-assets.ritsubi-platform.com側 R2 offload を正本とし、定期 backup は DB only であること
確認・手動実行:
# timer の状態確認
just wp-backup-status-vps production
# 手動で 1 回実行
just wp-backup-run-vps production
# 直近ログを直接確認
ssh ubuntu@116.80.84.2 "sudo journalctl -u ritsubi-wordpress-backup.service -n 120 --no-pager"
期待する状態:
ritsubi-wordpress-backup.timerがactive (waiting)。ritsubi-wordpress-backup.serviceが直近成功している。- journal に
Uploaded WordPress backup to s3://.../wordpress/production/db/...が出ている。
AccessDenied が出る場合の復旧手順:
- AWS Secrets Manager
b2b-ecommerce/prod/infraのBACKUP_R2_ACCESS_KEY_ID/BACKUP_R2_SECRET_ACCESS_KEYを更新する。 just wp-deploy-vps production backup verify=falseで VPS 上の/etc/ritsubi/wordpress-backup.envを再配布する。just wp-backup-run-vps productionを再実行し、journal にUploaded WordPress backup to ...が出ることを確認する。
Cloudflare Tunnel 502 の切り分け¶
cms.ritsubi-platform.com / cms-staging.ritsubi-platform.com で
502 Bad Gateway が断続的に出る場合、まず WordPress 本体ではなく cloudflared
の transport を確認します。今回の実障害では WordPress/Apache は 200
を返しており、cloudflared の QUIC timeout: no recent network activity
が直接原因でした。
確認観点:
- WordPress コンテナ自体は稼働しているか(
just wp-status-vps production)。 journalctl -u cloudflaredに QUIC timeout / reconnect が出ていないか。systemctl cat cloudflaredのExecStartが--protocol http2を含むか。- 変更が repo に入っていても既存ホストへ未反映の可能性があるため、staging /
production それぞれへ
cloudflaredrole を再適用したか。
確認例:
# Production の tunnel ログ
ssh ubuntu@116.80.84.2 "sudo journalctl -u cloudflared --since '-30 minutes' --no-pager | tail -n 120"
# systemd unit の実際の起動コマンド
ssh ubuntu@116.80.84.2 "sudo systemctl cat cloudflared"
# cloudflared role の再適用
just wp-deploy-vps production cloudflared
just wp-deploy-vps staging cloudflared
# cloudflared と公開導線の簡易検証
just wp-verify-vps production
just wp-verify-vps staging
期待する状態:
cloudflaredのSettings/Initial protocol/Registered tunnel connectionがhttp2を示す。wp-admin/post-new.php?post_type=product_detailなどの CMS 管理 URL が502ではなく、未ログイン時は302、ログイン済みでは200を返す。8181/tcpは VPS へ直接公開されず、Cloudflare Tunnel 経由だけで到達できる。WordPress側の access log に5xxが無いのにブラウザで502が出る場合は、引き続き tunnel 層を疑う。
定期監視¶
.github/workflows/scheduled-safe-probes.yml は 30 分ごとに staging /
production の Vendure・Storefront に加えて、WordPress 公開 CMS 導線も確認します。
確認対象:
https://cms-*.ritsubi-platform.com/wp-login.phpが200。https://cms-*.ritsubi-platform.com/wp-admin/post-new.php?post_type=product_detailが未ログイン時302/303。- 失敗時は既存の Slack 通知ジョブが起動する。
Media Offload の確認¶
Cloudflare R2 への media offload は、Secrets
Manager に値があるだけでは成立しません。 staging_wp / production_wp の
WORDPRESS_R2_* が VPS 上の /opt/wordpress/.env
とコンテナ環境へ反映されていること が前提です。
media offload の詳細手順、既存メディアの一括移行、R2 実体確認、トラブルシュートは wordpress-media-offload.md を正本とします。
確認観点:
- AWS Secrets Manager に
WORDPRESS_R2_ACCESS_KEY_ID,WORDPRESS_R2_SECRET_ACCESS_KEY,WORDPRESS_R2_BUCKET_NAME,WORDPRESS_R2_ENDPOINT,WORDPRESS_R2_REGIONが存在すること。 - 反映後の VPS 上で
/opt/wordpress/.envにWORDPRESS_R2_*が書き出されていること。 - WordPress コンテナ内で
WORDPRESS_R2_*が見えていること。 wp-cliでAS3CF_SETTINGSが定義されていること。- WP Offload Media の
delivery-providerがstorageのままではなく、cloudflareなど custom domain を許可する provider になっていること。 - 添付ファイルの URL が期待する offload 配信先を向いていること。
確認例:
# VPS 上の env ファイルに R2 設定が反映されているか
ssh ubuntu@116.80.84.4 "sudo grep '^WORDPRESS_R2' /opt/wordpress/.env"
# WordPress コンテナに R2 環境変数が入っているか
ssh ubuntu@116.80.84.4 "sudo docker compose -f /opt/wordpress/docker-compose.yml exec -T wordpress printenv | grep '^WORDPRESS_R2'"
# AS3CF_SETTINGS が定義されているか
just wp-cli-vps "eval 'echo defined(\"AS3CF_SETTINGS\") ? AS3CF_SETTINGS : \"AS3CF_SETTINGS_NOT_DEFINED\";'" staging
# 配信 provider / delivery-domain を確認
just wp-cli-vps "option get tantan_wordpress_s3" staging
# 有効プラグインの確認
just wp-cli-vps "plugin list --status=active" staging
[!IMPORTANT] WordPress の media offload では、
provider=awsのままでも R2 への保存自体はできますが、配信用のdelivery-providerが既定のstorageのままだと custom domain を使えません。実運用ではdelivery-provider=cloudflareとし、delivery-domainをcms-assets.ritsubi-platform.com/cms-assets-staging.ritsubi-platform.comに設定してください。 [!IMPORTANT]staging_wp/production_wpにWORDPRESS_R2_*が存在していても、VPS 上の/opt/wordpress/.envに出てこない場合は反映漏れです。just update-wp-staging/just update-wp-productionを実行して env を再同期し、その後に再確認してください。 [!IMPORTANT]stagingとproductionの WordPress env 反映は同時に走らせず、原則 1 環境ずつ順番に実行してください。環境ごとの一時 env ファイルが混線すると、誤った環境変数が別環境へ反映される事故につながります。 [!CAUTION]docker compose実行時にThe "<name>" variable is not set. Defaulting to a blank string.という警告が出る場合、VPS 上の.env内で$を含む値が Compose の変数展開対象になっている可能性があります。Secrets の値が欠落したように見える場合は、/opt/wordpress/.envの生成内容も併せて確認してください。
既存メディアの一括 Offload¶
新規アップロードだけでなく、既存の attachment をまとめて R2 へ移したい場合は、
offload-existing-media.php を wp-cli eval-file で実行します。
# Staging
just wp-cli-vps "eval-file /var/scripts/offload-existing-media.php" staging
# Production は wp-cli-vps の guard が直前 DB backup を取ってから実行する
just wp-cli-vps "eval-file /var/scripts/offload-existing-media.php" production
期待する結果:
OK <attachment_id>が並ぶ- 最後に
{"processed":...,"skipped":...,"failed":0}が出る media_counts()のnot_offloadedが0になる- REST / GraphQL の
source_urlがcms-assets.*を返す
[!NOTE] このスクリプトは既に offload 済みの attachment を skip し、未 offload 分だけ
wp_update_attachment_metadata()を明示実行して R2 へ送ります。
セキュリティ設計¶
- ポートの閉鎖: Cloudflare Tunnel を使用しているため、外部からポート
8181を開放する必要はありません。Ansible が UFW を自動設定し、外部からの直接アクセスを遮断します。 - 環境変数の最小化: デプロイ時には、WordPress に必要な変数のみが抽出され、VPS 上の
.envに書き出されます。AWS 認証情報などは VPS 上には保存されません。 - 初期化の分離:
bootstrapは local/dev の再現性を作るための導線です。remote CMS の state は backup / restore を正本にし、deploy では初期化や seed を実行しません。