你要解决的不是「能不能自动部署」,而是生产发布时谁能拿到 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: writebroker 临时生成或下发受限 API token有 Vault、云 IAM、内部凭据服务的团队需要自己维护交换层,Cloudflare Wrangler 没有公开直连 OIDC 参数
GitHub Actions + GitHub environment secretCLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_ID受限 API token一个人项目、小团队最快落地仍是 secret,要做过期、审批和轮换
Cloudflare Workers Builds不在 GitHub Actions 放部署 tokenCloudflare 自动生成或选择 API token部署命令简单、愿意用 Cloudflare Git 集成不走你的 Actions job;复杂测试、发布门禁要另接

GitHub OIDC 解决的是「GitHub job 如何证明自己是谁」。Cloudflare Workers 部署是否能完全无长期 Cloudflare 凭据,取决于你有没有一个能验证 GitHub OIDC claim、再发放短期 Cloudflare 凭据的中间层。

OIDC 在这条链路里到底管哪一段?

GitHub 文档给出的 OIDC 模型是:workflow job 请求一个由 GitHub 签发的 JWT,外部云服务验证 issaudsubrepositoryrefenvironment 等 claim,验证通过后发放短期 access token。permissions: id-token: write 只允许 job 请求这个 JWT,不会给仓库或云资源写权限。

生产部署至少要把信任条件写到这几个字段:

claim 或条件建议值为什么要管
repositoryyour-org/your-repo阻止同组织里别的仓库借用部署角色
refrefs/heads/main只让主分支触发生产发布
environmentproduction配合 GitHub environment 审批和 secret 隔离
aud自定义,例如 cloudflare-deploy-broker让 token 只能交给你的凭据交换层
repository_visibilityprivate 或组织策略要求公共仓库要更严格限制 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"
      }
    }
  }
}

namemaincompatibility_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_TOKENCLOUDFLARE_ACCOUNT_ID。如果你的账号还不能走上面的 OIDC broker,就用受保护 environment secret 做兜底,但别用全局 API key,也别把一个 token 给所有项目复用。

Worker 功能token 权限从哪里开始什么时候再加权限
只部署 Worker scriptWorkers Scripts 写权限,限制到目标 account只有这个 Worker 需要发版时
使用自定义 routeWorkers Routes 写权限,限制到目标 zone需要创建或更新路由时
使用 KVWorkers KV Storage 写权限CI 会创建 namespace 或写 KV 时
使用 R2Workers R2 Storage 写权限部署过程需要创建 bucket 或写对象时
使用 D1、Queues、Hyperdrive对应产品写权限Wrangler 配置或迁移确实会改这些资源时
只看日志Workers Tail 读权限单独给排障 token,不和部署 token 混用

Cloudflare 的 API token 支持资源策略、IP 条件和 TTL。用 API 创建 token 时可以写 not_beforeexpires_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 拒绝交换audsubenvironment 不匹配打印非敏感 claim 摘要,确认是不是 PR、tag 或手动触发
Wrangler 401/403Cloudflare 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 同时承担变更审批、密钥库、审计系统和发布编排。

相关阅读