Cloudflareでセルフホストする
自分のCloudflareアカウントにTrayPageを立てる手順。Deploy to Cloudflareボタン、Postgres、カスタムドメイン、メールまで。
自分のCloudflareアカウントでTrayPageを動かします。サーバーの管理は不要です。構成の比較は セルフホスト を参照してください。
ブラウザ / AIエージェント
→ カスタムドメイン(アプリ用・閲覧用の2つ)
→ Worker(DBの近くで実行)
├─ Hyperdrive → Postgres(PlanetScale または Neon)
├─ R2(ページ本文)
└─ Email Service(ログインリンク・招待)前提
| 必要なもの | 理由 |
|---|---|
| Workers Paid プラン($5/月) | 無料プランのCPU時間の上限(1リクエストあたり10ms)では、ログインやページ表示の処理が収まりません。必須です |
| Postgres | PlanetScale(Cloudflareに請求。$5/月〜)またはNeon(無料枠あり) |
| GitHubまたはGitLabのアカウント | ボタンはリポジトリをあなたのアカウントにコピーし、そこへのpushで再デプロイします |
| Cloudflareで管理しているドメイン | アプリ用と閲覧用の2つのカスタムドメイン、およびメールの送信元に使います |
費用の目安は、Workers PaidとDBで月$5〜10程度からです。
1. Postgresを作る
PlanetScale
- Cloudflareのダッシュボードで Storage & databases → Postgres & MySQL (Hyperdrive) を開き、Create PlanetScale database を選びます。
- エンジンは Postgres を選びます。リージョンは利用者の多い地域に近い場所にします(例: 日本なら
aws-ap-northeast-1)。このリージョンを控えておいてください(手順5で使います)。 - 作成が終わるまで待ちます。
Neon
- Neon でprojectを作り、リージョンを控えます(例:
aws-ap-southeast-1)。 - 接続文字列(
postgresql://...)を控えます。Hyperdriveが接続プールを持つので、ホスト名に-poolerが付くものではなく、直接の接続文字列を使います。
2. Hyperdriveを作ってDBにつなぐ
DBを作っただけでは、Hyperdriveの設定は作られません。ボタンでは作成済みのHyperdriveの設定を選ぶので、先に作っておきます。
-
PlanetScale: 同じ画面で Create Hyperdrive Connection を選び、手順1のDBを選びます。
-
Neon: Storage & databases → Hyperdrive → Create configuration で、手順1の接続文字列を入力します。CLIでは次のとおりです。
npx wrangler hyperdrive create traypage --connection-string="postgresql://USER:PASSWORD@HOST/DB?sslmode=require"
3. ボタンでデプロイする
-
TrayPageのリポジトリのREADMEにある Deploy to Cloudflare ボタンを押します。
-
画面で次を設定します。
項目 設定 Gitアカウント・リポジトリ名 コピー先。以後の設定変更はこのリポジトリで行います Worker名 既定は traypage。workers.devのURLになりますHYPERDRIVE手順2で作ったHyperdriveの設定 PAGE_BUCKETページ本文のR2バケット。自動で作られます BETTER_AUTH_SECRETopenssl rand -hex 32の出力。あとで変えると全員がログアウトしますVIEWER_TICKET_SECRET別に openssl rand -hex 32を実行した出力。BETTER_AUTH_SECRETと同じ値にしないでくださいBuild command / Deploy command 既定のまま -
デプロイが終わったら、
https://<Worker名>.<アカウントのサブドメイン>.workers.dev/healthが{"ok":true,...}を返すことを確認します。 -
DBのマイグレーションは、初回のアクセスでアプリが自動で適用します。
/health以外のURL(例:/api/organizationsが401を返す)を1回開いてください。
この時点では、ページ本文がアプリと同じホストから配信され、メールも送れません。次の手順4から6を済ませてから使い始めてください。
4. カスタムドメインを2つ設定する
コピーしたリポジトリの wrangler.jsonc を自分のドメインに書き換えます。
"workers_dev": false,
"routes": [
{ "pattern": "traypage.example.com", "custom_domain": true },
{ "pattern": "view.traypage.example.com", "custom_domain": true }
],
"vars": {
"ENVIRONMENT": "production",
"TRAYPAGE_EDITION": "self_hosted",
"APP_ORIGIN": "https://traypage.example.com",
"VIEWER_ORIGIN": "https://view.traypage.example.com",
"VIEWER_HOST": "view.traypage.example.com"
},- 2つのホスト名は、同じアカウントで管理しているゾーンのものにします。DNSレコードと証明書は自動で作られます。
workers_devはfalseにします。workers.devのURLが残っていると、閲覧用ホストの分離を迂回できてしまいます。varsはダッシュボードではなくwrangler.jsoncに書きます。ダッシュボードで変えた値は、次のデプロイでwrangler.jsoncの内容に戻ります(secretは残ります)。
pushするとWorkers Buildsが再デプロイします。https://traypage.example.com/health が200、https://view.traypage.example.com/ が404(閲覧用ホストは共有ページの本文しか返しません)になることを確認してください。
5. placementをDBのリージョンに合わせる
指定しないと、Workerは利用者に近い場所で動き、クエリのたびにDBとの間を往復して遅くなります(検証では、バンクーバーと東京のDBの組み合わせで1操作あたり0.6〜1.6秒かかりました)。wrangler.jsonc で、DBのリージョンを指定します。
"placement": { "mode": "targeted", "region": "aws:ap-northeast-1" },| DBのリージョン | region に書く値 |
|---|---|
PlanetScale aws-ap-northeast-1(東京) | aws:ap-northeast-1 |
PlanetScale aws-us-east-1 | aws:us-east-1 |
PlanetScale gcp-us-central1 | gcp:us-central1 |
Neon aws-ap-southeast-1(シンガポール) | aws:ap-southeast-1 |
Neon aws-us-east-2 | aws:us-east-2 |
Neon azure-eastus2 | azure:eastus2 |
規則は「プロバイダ名:リージョン名」です。再デプロイ後、応答ヘッダの cf-placement が remote-NRT(東京の場合)のようになっていれば、DBの近くで動いています。
curl -sI https://traypage.example.com/api/organizations | grep -i cf-placement6. メールを設定する(Cloudflare Email Service)
ログインはメールのリンクで行います。メールはCloudflare Email Serviceで送り、APIキーは要りません。
-
ダッシュボードの Email Service → Domains で、送信元にするドメインを登録します(SPFやDKIMのDNSレコードが追加されます)。
-
wrangler.jsoncのvarsに送信元を追加してpushします。"EMAIL_FROM": "TrayPage <noreply@traypage.example.com>" -
アプリ用ドメインの
/loginから、自分のメールアドレスにログインリンクが届くことを確認します。
注意: Email Serviceは公開ベータです。送信元ドメインの登録が終わるまで、ログインリンクは届きません。
wrangler.jsoncのsend_emailにallowed_sender_addresses(送信元アドレスの許可リスト)を足すと、EMAIL_FROM以外のアドレスからは送れなくなります。以前のバージョンで案内していた
RESEND_API_KEYは、設定されていればResendを優先して使います(Email Serviceに切り替えるには、このsecretを削除します)。
任意の設定
| 設定 | 方法 |
|---|---|
| Googleログイン | secret GOOGLE_CLIENT_ID・GOOGLE_CLIENT_SECRET を追加し、vars に "NEXT_PUBLIC_GOOGLE_AUTH": "1" を追加。Google Cloud ConsoleのリダイレクトURIは https://<アプリ用ドメイン>/api/auth/callback/google |
| Enterpriseのライセンス | secret TRAYPAGE_LICENSE_KEY(ライセンスとEnterprise機能) |
| PDF出力 | BROWSER binding(Browser Run)。テンプレートに含まれています。なくても「印刷・PDFとして保存」は使えます |
| リアルタイム通知 | REVIEW_SESSIONS(Durable Objects)。テンプレートに含まれています。なくても定期的な確認で動きます |
secretは、ダッシュボードの Workers & Pages → (Worker) → Settings → Variables and Secrets、または npx wrangler secret put <名前> で追加します。すべての設定項目は 設定項目のリファレンス にあります。
更新
コピーしたリポジトリに本家の変更を取り込んでpushすると、Workers Buildsが再デプロイします。DBのマイグレーションは、新しいバージョンの初回アクセスで自動で適用されます。
git remote add upstream <TrayPageのリポジトリのURL>
git fetch upstream && git merge upstream/main # wrangler.jsonc で自分の設定と競合したら、自分の値を残す
git pushうまく動かないとき
| 症状 | 確認すること |
|---|---|
| 応答が遅い(1操作で1秒前後) | 手順5のplacement。cf-placement ヘッダ |
Exceeded CPU time limit / 1102エラー | Workers Paidになっているか |
/health は200だが、ほかが500 | Hyperdriveの設定(DBの接続文字列とパスワード)。npx wrangler tail でログを見る |
| ログインリンクが届かない | 手順6(送信元ドメインの登録と EMAIL_FROM)と、手順6の注意(古い RESEND_API_KEY が残っていないか) |
| 閲覧用ドメインでアプリの画面が出る | VIEWER_HOST と routes(手順4) |