你要解决的不是「能不能自动部署」,而是生产发布时谁能拿到 Cloudflare Workers 的写权限。一个人 SaaS 最容易把问题做反:先在 GitHub Secrets 里放一个长期 CLOUDFLARE_API_TOKEN,半年后才发现 preview、staging、production 都能碰到同一把钥匙。
GitHub Actions 已经有成熟的 OIDC 机制;Cloudflare 的 Workers 外部 CI/CD 文档和 cloudflare/wrangler-action@v3 示例仍使用 Cloudflare API token 与 account ID。稳妥做法是:能让 GitHub OIDC 去换短期凭据就换;换不了时,把 Cloudflare API token 收到最小权限、短有效期和受保护环境里。
Cloudflare 官方现在支持哪几条部署链?
先把三条路分清楚,避免把「OIDC」当成一句万能口令。
| 路线 | GitHub 里放什么 | Cloudflare 侧凭据 | 适合谁 | 主要限制 |
|---|---|---|---|---|
| GitHub Actions + OIDC + 凭据 broker | 不放 Cloudflare token,只给 id-token: write | broker 临时生成或下发受限 API token | 有 Vault、云 IAM、内部凭据服务的团队 | 需要自己维护交换层,Cloudflare Wrangler 没有公开直连 OIDC 参数 |
| GitHub Actions + GitHub environment secret | CLOUDFLARE_API_TOKEN、CLOUDFLARE_ACCOUNT_ID | 受限 API token | 一个人项目、小团队最快落地 | 仍是 secret,要做过期、审批和轮换 |
| Cloudflare Workers Builds | 不在 GitHub Actions 放部署 token | Cloudflare 自动生成或选择 API token | 部署命令简单、愿意用 Cloudflare Git 集成 | 不走你的 Actions job;复杂测试、发布门禁要另接 |
GitHub OIDC 解决的是「GitHub job 如何证明自己是谁」。Cloudflare Workers 部署是否能完全无长期 Cloudflare 凭据,取决于你有没有一个能验证 GitHub OIDC claim、再发放短期 Cloudflare 凭据的中间层。
OIDC 在这条链路里到底管哪一段?
GitHub 文档给出的 OIDC 模型是:workflow job 请求一个由 GitHub 签发的 JWT,外部云服务验证 iss、aud、sub、repository、ref、environment 等 claim,验证通过后发放短期 access token。permissions: id-token: write 只允许 job 请求这个 JWT,不会给仓库或云资源写权限。
生产部署至少要把信任条件写到这几个字段:
| claim 或条件 | 建议值 | 为什么要管 |
|---|---|---|
repository | your-org/your-repo | 阻止同组织里别的仓库借用部署角色 |
ref | refs/heads/main | 只让主分支触发生产发布 |
environment | production | 配合 GitHub environment 审批和 secret 隔离 |
aud | 自定义,例如 cloudflare-deploy-broker | 让 token 只能交给你的凭据交换层 |
repository_visibility | private 或组织策略要求 | 公共仓库要更严格限制 fork、PR 和手动触发 |
这一步不能省。如果只验证 repository_owner,一个同组织的新测试仓库也可能满足条件;如果不验证 environment,preview job 和 production job 在身份上就没有差别。
Wrangler 项目文件要先固定哪些字段?
Cloudflare 文档建议新项目使用 wrangler.jsonc;Wrangler v3.91.0 起同时支持 JSON、JSONC 和 TOML,但部分新功能只会出现在 JSON 配置里。生产部署不要依赖首次 wrangler deploy 的自动识别,CI 里最怕交互提示和自动生成配置文件。
一个最小 Worker 可以先这样写:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-saas-api",
"main": "src/index.ts",
"compatibility_date": "2026-05-27",
"workers_dev": true,
"observability": {
"enabled": true
},
"env": {
"staging": {
"name": "my-saas-api-staging",
"workers_dev": true
},
"production": {
"name": "my-saas-api",
"workers_dev": false,
"route": {
"pattern": "api.example.com/*",
"zone_name": "example.com"
}
}
}
}
name、main、compatibility_date 是部署 Worker 的最低线。使用 --env production 时,把生产 route、bindings 和变量写进 env.production,不要默认所有顶层字段都会按你想的方式继承。Cloudflare 也提醒 Wrangler 配置应作为 Worker 配置的事实来源;如果你在 Dashboard 临时改变量、路由或 bindings,下次 CI 部署可能把它覆盖。
GitHub Actions workflow 怎么写才不默认放长期密钥?
下面这份 YAML 走的是「GitHub OIDC -> 自己的凭据 broker -> 临时 Cloudflare API token -> Wrangler」路线。BROKER_URL 代表你自己的 Vault、云函数或内部服务;它必须验证 GitHub OIDC JWT 的 claim,再调用 Cloudflare API 创建带 expires_on 的受限 API token,或者下发一个短期可用的部署 token。
这里说的「不放长期密钥」只限定在 GitHub 仓库和 GitHub Secrets 这一侧。Broker 仍然需要一把能创建 Cloudflare API token 的上游凭据,而且 Cloudflare 文档提醒,这类创建 token 的凭据可以影响用户资源访问。它必须被当成生产级 secret 管理:单独存放、限制调用来源、记录每次发放、设置告警,并且不能放回 GitHub Actions。
name: Deploy Cloudflare Worker
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 20
environment: production
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- name: Request GitHub OIDC token
id: oidc
run: |
TOKEN_JSON="$(
curl --fail --silent --show-error \
--header "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=cloudflare-deploy-broker"
)"
TOKEN="$(echo "$TOKEN_JSON" | jq -r '.value')"
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Exchange OIDC for Cloudflare deploy token
env:
BROKER_URL: ${{ vars.BROKER_URL }}
GITHUB_OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
run: |
CF_TOKEN="$(
curl --fail --silent --show-error \
--request POST "$BROKER_URL/cloudflare/workers-token" \
--header "Authorization: Bearer $GITHUB_OIDC_TOKEN" \
--header "Content-Type: application/json" \
--data '{"worker":"my-saas-api","environment":"production"}' \
| jq -r '.token'
)"
echo "::add-mask::$CF_TOKEN"
echo "CLOUDFLARE_API_TOKEN=$CF_TOKEN" >> "$GITHUB_ENV"
- name: Deploy with Wrangler
env:
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
run: npx wrangler deploy --env production
这个例子里没有把 Cloudflare token 写进 GitHub Secrets。真正的风险转移到 broker:它不能只看 token 是否来自 GitHub,还要验证仓库、分支、environment、audience、workflow 文件和触发事件。Broker 侧的创建 token 权限如果泄露,影响不比泄露部署 token 小;小团队没有能力维护这层服务时,受保护 environment secret 往往更可控。
Cloudflare API token 最小权限怎么切?
Cloudflare 官方 GitHub Actions 示例要求在 CI 中提供 CLOUDFLARE_API_TOKEN 和 CLOUDFLARE_ACCOUNT_ID。如果你的账号还不能走上面的 OIDC broker,就用受保护 environment secret 做兜底,但别用全局 API key,也别把一个 token 给所有项目复用。
| Worker 功能 | token 权限从哪里开始 | 什么时候再加权限 |
|---|---|---|
| 只部署 Worker script | Workers Scripts 写权限,限制到目标 account | 只有这个 Worker 需要发版时 |
| 使用自定义 route | Workers Routes 写权限,限制到目标 zone | 需要创建或更新路由时 |
| 使用 KV | Workers KV Storage 写权限 | CI 会创建 namespace 或写 KV 时 |
| 使用 R2 | Workers R2 Storage 写权限 | 部署过程需要创建 bucket 或写对象时 |
| 使用 D1、Queues、Hyperdrive | 对应产品写权限 | Wrangler 配置或迁移确实会改这些资源时 |
| 只看日志 | Workers Tail 读权限 | 单独给排障 token,不和部署 token 混用 |
Cloudflare 的 API token 支持资源策略、IP 条件和 TTL。用 API 创建 token 时可以写 not_before 和 expires_on;这不等于原生 OIDC,但至少能把「永久可用」改成「某个发布窗口内可用」。
没有凭据 broker 时,兜底方案怎么做?
先把生产 job 放进 GitHub production environment,开启 required reviewers。CLOUDFLARE_API_TOKEN 只放 environment secret,不放 repository secret;CLOUDFLARE_ACCOUNT_ID 这类非敏感值可以放 environment variable。
兜底 workflow 可以保留 Cloudflare 官方 action 或直接跑 npx wrangler deploy。我更喜欢直接跑 Wrangler 命令,因为日志里更容易看出是构建失败、配置失败还是 Cloudflare API 权限不足。
permissions:
contents: read
jobs:
deploy:
environment: production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx wrangler deploy --env production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
如果团队成员经常在共享办公、酒店网络或跨地区机器上处理 GitHub、Cloudflare Dashboard 和发布日志,先把审批人、2FA 设备、后台访问环境和回滚联系人固定下来。API token 权限、environment 审批、过期时间和审计记录才是部署安全的主线。
Workers Builds 可以替代 GitHub Actions 吗?
可以,但它是换一条部署链,不是让 Wrangler 获得 GitHub OIDC。Cloudflare Workers Builds 通过 GitHub 或 GitLab 仓库触发构建,默认 deploy command 是 npx wrangler deploy,非生产分支默认用 npx wrangler versions upload 生成 preview version。
Cloudflare 文档还说明,Workers Builds 的 API token 默认由 Cloudflare 自动生成,也可以选择你自己的 token;截至官方文档当前说明,Workers Builds 只支持 user tokens,account-owned token support 仍是后续项。这个路线的优点是 GitHub Actions 不再持有 Cloudflare token,缺点是复杂测试矩阵、制品签名、跨服务发布门禁都要重新接到 Cloudflare Builds 旁边。
我的取舍是:如果项目只是 Worker API、没有复杂 CI,就先评估 Workers Builds;如果你已经有单元测试、数据库迁移、Sentry release、npm provenance 等 Actions 流程,就保留 GitHub Actions,把部署凭据单独收紧。
发版前看哪几个失败点?
| 失败信号 | 常见原因 | 先做什么 |
|---|---|---|
id-token 取不到 | workflow 没有 id-token: write | 查 job-level permissions,不要只写在另一个 job |
| broker 拒绝交换 | aud、sub、environment 不匹配 | 打印非敏感 claim 摘要,确认是不是 PR、tag 或手动触发 |
| Wrangler 401/403 | Cloudflare API token 权限不够或过期 | 查 token 策略、资源范围、expires_on 和 account ID |
| route 部署失败 | zone 权限缺失或 zone_name 写错 | 把 script 权限和 route 权限分开看 |
| staging 能发、production 不能发 | GitHub environment 审批或 secret 不同 | 看 environment 名称、reviewer、secret 来源 |
不要把 timeout-minutes 拉很大来掩盖权限问题。认证失败通常在几秒内就能暴露;真正慢的是依赖安装、构建、上传或 Cloudflare API 短时异常。
哪些部署形态这里没有覆盖?
本文没有测试 GitHub Enterprise Server、自托管 runner、monorepo 多 Worker 批量发布、Cloudflare Organizations 复杂角色、broker 高可用、密钥硬件托管或 SOC2 审计要求。上面的 broker 示例只说明凭据交换链路,不是一套完整安全产品。
如果你的部署链路还包含数据库迁移、跨账号发布、手工审批、制品签名或多区域回滚,把 Wrangler 部署 token 当成其中一个环节处理,不要让 broker 同时承担变更审批、密钥库、审计系统和发布编排。