Anoy Backstage v0.3
internal 5 日前に更新

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 repoanoydesign/anoy-backstage (private)
Cloudflare Account ID88bad59c29ed5bb510f66f081f32a0d9
Zone (anoy8.com) ID067f88c4e80b1d29f867d291d97f9676
Google IdP IDf6a8bd5f-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 デプロイAccountCloudflare Pages : Edit
Access app/policy 操作AccountAccess: Apps and Policies : Edit
カスタムドメインの DNSZoneDNS : 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 は domainHOST/PATH* 形式で指定可能(より specific が優先)。

パス扱い理由
/Include(Google 必須)index に internal カードが含まれるため
/_assets/*BypassCSS / ロゴ / search-public.json は誰でも取れて OK
/<proj>/internal/*Include機密(デフォルト)
/<proj>/public/*Bypass外部共有用(プロジェクト別公開)
/public/Bypass匿名向け入口(public プロジェクト一覧)
/sitemap.xml /robots.txt /404.htmlBypass静的メタ・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/ のみ Allow
  • sitemap.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 installationGitHub 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

  1. GitHub に private repo 作成(gh repo create OWNER/REPO --private --source=. --push
  2. .envCF_API_TOKEN(または CF_API_KEY) / CF_ACCOUNT_ID / CF_ZONE_ID をセット
  3. Pages プロジェクト作成(API 1 発、direct upload 型)
  4. 初回 npm run build && npx wrangler pages deploy dist でアップロード
  5. カスタムドメイン申請 + CNAME 作成
  6. Access App 3 個作成(本番 / pages.dev / *.pages.dev)+ Include policy
  7. GitHub Actions 用デプロイ専用トークン作成 → Secrets 登録
  8. .github/workflows/deploy.yml 配置 → push してテストデプロイ
  9. 本番 URL をブラウザで確認(DNS キャッシュ要注意)

12. 参考


本手順書は anoy-backstage の構築ログ(2026-04-28〜29、Air → mini → Pages 経路再考の末に確定した最短経路)から逆算した。次のプロジェクトでも、固有値(PROJECT_NAME / GH_ORG / 対象ドメイン)を差し替えるだけで同一手順で立ち上がる想定。