Cloudflare Pages + Access セットアップ手順書
新規プロジェクトを「GitHub に push したら自動デプロイ」「Google 認証で守る」状態まで持っていく手順書。anoy-backstage の構築実績(2026-04-28)から汎用化。次のプロジェクトでもそのまま流用可。
01. 全体像
最終形:
[ローカル編集]
│ git push
▼
[GitHub Repo (private)]
│ GitHub Actions
▼
[Cloudflare Pages] ← npm run build → dist/
│ CNAME (auto)
▼
[your-project.pages.dev] ← Access app: *.pages.dev / wildcard
[NN.your-domain.com] ← Access app: NN.your-domain.com
│
▼ (Google IdP, @your-domain only)
[Google ログイン] → 表示
押さえるポイント:Cloudflare API でほぼ全部自動化できる。ブラウザ作業は (1) GitHub App 認可(オプション、direct upload なら不要)と (2) API トークン作成だけ。
本リポジトリの実装インスタンス
| 項目 | 値 |
|---|---|
| 本番ドメイン | 02.anoy8.com |
| Pages プロジェクト名 | anoy-backstage |
| Pages 標準ドメイン | anoy-backstage.pages.dev |
| GitHub repo | anoydesign/anoy-backstage (private) |
| Cloudflare Account ID | 88bad59c29ed5bb510f66f081f32a0d9 |
| Zone (anoy8.com) ID | 067f88c4e80b1d29f867d291d97f9676 |
| Google IdP ID | f6a8bd5f-e552-4975-906e-176e9a1981d0 |
| Access Application 数 | 3(本番 / pages.dev / *.pages.dev wildcard) |
以下の手順は テンプレ(次プロジェクト立ち上げ用)で、PROJECT_NAME 等のプレースホルダを置き換えて使う想定。本リポジトリの設定値はこの表に記録済み。
02. 前提
- Cloudflare アカウント(ここでは Account ID =
88bad59c29ed5bb510f66f081f32a0d9= Anoy) - ドメインを Cloudflare に登録済み(ここでは
anoy8.com、Zone ID =067f88c4e80b1d29f867d291d97f9676) - GitHub アカウント(ここでは
anoydesign) - Google IdP が Cloudflare Zero Trust にセット済み(IdP ID =
f6a8bd5f-e552-4975-906e-176e9a1981d0) - API トークン(後述)
必要 IDs の確認方法
curl -s "https://api.cloudflare.com/client/v4/zones?name=YOUR_DOMAIN" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" | jq
curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/identity_providers" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" | jq
03. API トークン作成
Global API Key は最終手段。基本は「スコープ付き API Token」を使う。
必要パーミッション
| 用途 | スコープ | 権限 |
|---|---|---|
| Pages デプロイ | Account | Cloudflare Pages : Edit |
| Access app/policy 操作 | Account | Access: Apps and Policies : Edit |
| カスタムドメインの DNS | Zone | DNS : Edit |
ブラウザで https://dash.cloudflare.com/profile/api-tokens → 「+ トークンを作成する」 → 「カスタムトークンを作成する」 → 上記 3 つのパーミッション + Account/Zone Resources を絞る → 作成。
CRITICAL ─ 「API トークン」と「API キー」を間違えない。上のセクション = API トークン(スコープ可能、推奨)、下のセクション = API キー(Global、使うな)。Cloudflare 自身も画面上部の青い注意書きで「ユーザーに関連付けられていない認証情報を希望する場合、アカウント API トークン推奨」と明示している。
トークン値の保管
生成された値は一度しか見られない。ターミナルで直接 .env に保存:
cat >> ~/proj/.env <<'EOF'
CF_API_TOKEN=cfp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EOF
.env は .gitignore 済みであることを必ず確認。チャットや Slack には貼らない。
04. Cloudflare Pages プロジェクト作成
GitHub 連携 (type: github) と直接アップロード (no source) の二択。GitHub App の事前インストールが面倒なら direct upload で進めて、後から GitHub Actions で自動化する方が早い。
A. 直接アップロード型(推奨:すぐ動く)
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects" \
-H "X-Auth-Email: $CF_API_EMAIL" \
-H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "PROJECT_NAME",
"production_branch": "main"
}'
成功すれば PROJECT_NAME.pages.dev が確保される。
B. GitHub 連携型
事前に Pages → Create → Connect to Git から GitHub App をインストールして対象 repo に権限を付与しておくこと。インストール無しで API を叩くとエラー 8000011 が返る。
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "PROJECT_NAME",
"production_branch": "main",
"source": {
"type": "github",
"config": {
"owner": "GH_ORG",
"repo_name": "REPO",
"production_branch": "main",
"deployments_enabled": true,
"production_deployment_enabled": true,
"preview_deployment_setting": "all"
}
},
"build_config": {
"build_command": "npm run build",
"destination_dir": "dist",
"root_dir": ""
}
}'
05. 初回デプロイ(direct upload 経路)
cd /path/to/project
npm run build
CLOUDFLARE_EMAIL="$CF_API_EMAIL" \
CLOUDFLARE_API_KEY="$CF_API_KEY" \
CLOUDFLARE_ACCOUNT_ID="$CF_ACCOUNT_ID" \
npx --yes wrangler@latest pages deploy dist \
--project-name=PROJECT_NAME --branch=main --commit-dirty=true
成功すると一意な preview URL が返る(https://xxxxxxxx.PROJECT_NAME.pages.dev)。
06. カスタムドメイン
例:NN.anoy8.com を Pages プロジェクトに紐付ける。
a. Pages に domain を登録
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/PROJECT_NAME/domains" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{"name": "NN.anoy8.com"}'
b. CNAME を作成(自動作成されない場合)
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "NN",
"content": "PROJECT_NAME.pages.dev",
"proxied": true,
"comment": "Anoy Backstage (Cloudflare Pages)"
}'
1〜3 分で TLS 証明書(Google CA)が発行され、status: active になる。
DNS CACHE GOTCHA ─ ローカルマシンの DNS resolver が NXDOMAIN を負キャッシュしていると、ブラウザで開いてもエラー。macOS なら
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder+ Chrome のchrome://net-internals/#dnsから「Clear host cache」。あるいは Chrome 完全再起動が一番確実。
07. Cloudflare Access(前段認証)
Application を 1 個作成 → Include policy で「@your-domain メールのみ許可」。後から外部公開が必要になれば、別 Application を /public/* 等に当てて Bypass。
a. Application 作成
APP=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/apps" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "Project Display Name",
"domain": "NN.anoy8.com",
"type": "self_hosted",
"session_duration": "24h",
"allowed_idps": ["'$CF_IDP_GOOGLE'"],
"auto_redirect_to_identity": true,
"app_launcher_visible": true
}')
APP_UID=$(echo "$APP" | jq -r '.result.uid')
b. Include policy(@anoy.jp 全員)
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/apps/$APP_UID/policies" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "Anoy team (@anoy.jp)",
"decision": "allow",
"include": [{"email_domain": {"domain": "anoy.jp"}}],
"precedence": 1
}'
c. preview / wildcard も忘れず保護
Pages の PROJECT.pages.dev および *.PROJECT.pages.dev は別ホスト名扱い。本番ドメインだけに Access を当てると preview URL から漏洩する。同じ allow policy を持つ App を 2 個追加:
PROJECT.pages.dev用 App*.PROJECT.pages.dev用 App(deployment-specific URL を網羅)
d. パスベース ポリシー設計(外部共有が発生したとき)
最初は本番ドメイン全体に Include policy で OK。外部共有が必要になった時点で、別 Application を path 付きで切り、Bypass policy を当てる。Cloudflare Access は domain を HOST/PATH* 形式で指定可能(より specific が優先)。
| パス | 扱い | 理由 |
|---|---|---|
/ | Include(Google 必須) | index に internal カードが含まれるため |
/_assets/* | Bypass | CSS / ロゴ / search-public.json は誰でも取れて OK |
/<proj>/internal/* | Include | 機密(デフォルト) |
/<proj>/public/* | Bypass | 外部共有用(プロジェクト別公開) |
/public/ | Bypass | 匿名向け入口(public プロジェクト一覧) |
/sitemap.xml /robots.txt /404.html | Bypass | 静的メタ・SEO・404 |
Bypass App 作成例(/<proj>/public/*):
curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/access/apps" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "Project — Public assets",
"domain": "NN.anoy8.com/PROJ/public",
"type": "self_hosted",
"session_duration": "0s",
"auto_redirect_to_identity": false
}'
# → 取得した APP_UID に対して decision: "bypass" の policy を 1 個入れる
e. 漏洩防止のビルド側チェック
Bypass で /<proj>/public/* を匿名公開するとき、public ページの nav やインデックスから internal タイトルが漏れていないかを必ず確認。本リポジトリのビルダーは以下を自動でやる:
- public ページの nav から internal セクションを除外(
buildNav(audience: 'public')) /public/ランディングは internal プロジェクトを含まない- internal ページに
<meta name="robots" content="noindex,nofollow"> robots.txtで/を Disallow、/public/のみ Allowsitemap.xmlは public パスだけ列挙
ビルド後 grep で漏洩がないことを確認:
grep -r 'internal' dist/<proj>/public/ # internal 文字列が混入していないか
grep -rE 'href="[^"]*/internal/' dist/<proj>/public/ # internal リンクが漏れていないか
08. GitHub Actions 自動デプロイ
git push → 約 40 秒で本番反映。
a. デプロイ専用トークン作成
「Pages : Edit」だけのスコープで API token を作る(API でも作れる):
# Pages Write の permission group ID は固定: 8d28297797f24fb8a0c332fe0866ec89
TOKEN=$(curl -s -X POST "https://api.cloudflare.com/client/v4/user/tokens" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "PROJECT-gh-actions-deploy",
"policies": [{
"effect": "allow",
"permission_groups": [{"id": "8d28297797f24fb8a0c332fe0866ec89"}],
"resources": {"com.cloudflare.api.account.'$CF_ACCOUNT_ID'": "*"}
}]
}' | jq -r '.result.value')
b. GitHub Secrets に登録
gh secret set CLOUDFLARE_API_TOKEN --repo=GH_ORG/REPO --body="$TOKEN"
gh secret set CLOUDFLARE_ACCOUNT_ID --repo=GH_ORG/REPO --body="$CF_ACCOUNT_ID"
unset TOKEN # ローカルから即削除
c. Workflow ファイル
.github/workflows/deploy.yml:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
deployments: write
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 5
concurrency:
group: cf-pages-deploy
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=PROJECT_NAME --branch=main
push 後、gh run watch <run_id> で進捗確認。30〜45 秒で完了。
09. トラブルシュート
エラー 8000011 — internal issue with Cloudflare Pages Git installation
→ GitHub App が未インストール。dashboard から手動インストール、または direct upload に切り替え。
curl Could not resolve host: NN.your-domain.com
→ ローカル DNS の負キャッシュ。dig @1.1.1.1 NN.your-domain.com +short が IP 返すなら DNS は OK。sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder。
Chrome で「このページに到達できません」
→ Chrome の DNS キャッシュも別。chrome://net-internals/#dns → Clear host cache。または再起動。
Pages 上で MD 内 HTML がコードブロックとして表示される → MD 内の HTML が 4 スペース以上インデントされている(Markdown は 4+ space をコードブロックと解釈)。HTML 行の leading whitespace を削除。
Access ログイン後も「Forbidden」
→ Include policy に該当しないメールでログインしている。@your-domain ドメインアカウントでログインし直す。Cookie 削除も併用。
10. ローテーション・運用
- Global API Key を業務で使った場合は即ローテート。Profile → API Tokens 下部の「変更」ボタン。
- スコープ付きトークンは半年に 1 回ローテート目安。Pages Write しか権限がないので漏洩しても被害は Pages デプロイのみ。
- Access の Allow policy を追加変更したときは、preview URL 用 App / 本番ドメイン用 App / wildcard 用 App の 3 つ全てを更新するのを忘れない。
- 運用中に
preview_deployment_settingをオフにして preview を完全に止めたいなら:
curl -X PATCH "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/PROJECT_NAME" \
-H "X-Auth-Email: $CF_API_EMAIL" -H "X-Auth-Key: $CF_API_KEY" \
-H "Content-Type: application/json" \
--data '{"deployment_configs":{"preview":{"compatibility_date":"2026-04-29"}}}'
11. 新規プロジェクト立ち上げチェックリスト
PROJECT BOOTSTRAP
- GitHub に private repo 作成(
gh repo create OWNER/REPO --private --source=. --push) .envにCF_API_TOKEN(またはCF_API_KEY) /CF_ACCOUNT_ID/CF_ZONE_IDをセット- Pages プロジェクト作成(API 1 発、direct upload 型)
- 初回
npm run build && npx wrangler pages deploy distでアップロード - カスタムドメイン申請 + CNAME 作成
- Access App 3 個作成(本番 / pages.dev / *.pages.dev)+ Include policy
- GitHub Actions 用デプロイ専用トークン作成 → Secrets 登録
.github/workflows/deploy.yml配置 → push してテストデプロイ- 本番 URL をブラウザで確認(DNS キャッシュ要注意)
12. 参考
- Cloudflare API: https://developers.cloudflare.com/api/
- wrangler: developers.cloudflare.com/workers/wrangler/commands/#pages
- Cloudflare Access: developers.cloudflare.com/cloudflare-one/applications/
- cloudflare/wrangler-action: github.com/cloudflare/wrangler-action
本手順書は anoy-backstage の構築ログ(2026-04-28〜29、Air → mini → Pages 経路再考の末に確定した最短経路)から逆算した。次のプロジェクトでも、固有値(PROJECT_NAME / GH_ORG / 対象ドメイン)を差し替えるだけで同一手順で立ち上がる想定。