Run cf in continuous integration (CI) to check every pull request and deploy
your Worker without a person present. In CI, cf authenticates with an API
token and does not wait for input.
This workflow builds the project once for every pull request and every push to
main. It checks the build with a dry run, and deploys the same build only on
pushes to main.
-
Add
cfas a development dependency, so every run uses the same release:npm i -D cfyarn add -D cfpnpm add -D cfbun add -d cfProjects created with
cf initor migrated withcf migratealready include it. -
Commit
cloudflare.config.ts. If the project does not have one yet, refer to Commit the project configuration. -
Add
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDas repository secrets. Grant the token only the permissions the workflow needs. To create a token, refer to Create an API token. -
Add the workflow file:
.github/workflows/deploy.ymlyaml name: Deploy on: pull_request: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest env: CF_SEND_TELEMETRY: "false" steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 22 cache: npm - run: npm ci - run: npx cf build - run: npx cf deploy --prebuilt --mode production --dry-run - if: github.event_name == 'push' run: npx cf deploy --prebuilt --mode production env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
The workflow assumes a project created with cf init, with
package-lock.json committed. Only the final deploy step receives secrets, so
the pull request steps also work for pull requests from forks. If you pass
another --mode to cf build, use the same mode in both deploy steps. The
following sections explain each part.
cf auth login needs a person to approve access in a browser. In CI, set these
environment variables instead:
| Variable | Purpose |
|---|---|
CLOUDFLARE_API_TOKEN |
Authenticates API requests. Takes priority over any stored login. |
CLOUDFLARE_ACCOUNT_ID |
Selects the account. Without it, cf uses accountId from cloudflare.config.ts, then an account saved by an earlier run in the same checkout, then the only account available. |
CLOUDFLARE_ZONE_ID |
Selects a zone for zone-level commands when you do not pass --zone. |
If the token can access more than one account and no account is set or saved, the command fails instead of prompting:
More than one account available but unable to select one in non-interactive mode.
Please set the appropriate `account_id` in your cf config file or assign it to the `CLOUDFLARE_ACCOUNT_ID` environment variable.
Available accounts are (`<name>`: `<account_id>`):
`(redacted)`: `<ACCOUNT_ID>`In CI, cf replaces account names with (redacted). To fix the error, set
CLOUDFLARE_ACCOUNT_ID, or add accountId to the default export in
cloudflare.config.ts.
For scripts on your own machine, cf commands that call the Cloudflare API
can also read these variables from a .env file in the current directory.
They do not read .env.local. Variables already set in the environment take
priority. For details, refer to
Load credentials from a .env file.
With cf installed as a development dependency, npx cf <COMMAND> runs the
project's copy. On a developer's machine, a globally installed cf also hands
each command to the project's copy. Collaborators, coding agents, and CI then
run the same version. One-off runs such as npx cf@<VERSION> are not handed
off.
Use Node.js 22.18 or later. For details, refer to Requirements.
cf treats a run as non-interactive when it has no terminal, or when it detects
a CI environment, for example through the CI environment variable. In a
non-interactive run:
- Prompts use their default answer. Prompts without a default fail, so pass the value as an option instead.
- Destructive commands abort without
--force. They printAborted.to standard error, make no change, and exit with status0. cf initneeds a directory, such ascf init my-workerorcf init .. A new project uses the package manager that startedcf, or npm. Pass--package-managerto choose another.
Before you pass --force in a job, read the command's --help output. On some
commands, --force is also an API parameter.
In a project without cloudflare.config.ts, every command that builds runs
automatic configuration first. In CI, automatic configuration accepts the
detected settings, installs packages, and edits project files without asking.
To avoid unreviewed changes, run cf init . on your own machine. Review the
result, and commit cloudflare.config.ts with the other changes. For what
automatic configuration changes, refer to
Automatic configuration.
To deploy exactly what an earlier step built, build the project once:
npx cf buildyarn cf buildpnpm cf buildThen deploy that build with --prebuilt and the mode it recorded:
npx cf deploy --prebuilt --mode productionyarn cf deploy --prebuilt --mode productionpnpm cf deploy --prebuilt --mode production--prebuilt skips the build and automatic configuration, and uses the Build
Output in .cloudflare/output. Vite builds record production unless you pass
--mode. If you build with --mode staging, deploy with --mode staging. When
the modes do not match, the deploy stops before it uploads anything and prints
the mode to use.
If the Build Output contains more than one Worker, cf deploys the default
Worker unless you pass --worker <NAME>. For the full rules, refer to
Deploy a prebuilt build.
cf deploy --dry-run builds the project and validates the result without
uploading it or sending API requests. It needs no credentials, so it also works
for pull requests from forks, where CI secrets are not available.
npx cf deploy --dry-runyarn cf deploy --dry-runpnpm cf deploy --dry-runTo check a build from an earlier step instead, add --prebuilt and the
recorded --mode, as the example workflow does.
cf previews deploy builds the project and deploys a Worker Preview. It needs
credentials and has no --dry-run option.
npx cf previews deployyarn cf previews deploypnpm cf previews deployThe preview name defaults to the current branch. In GitHub Actions, cf reads
GITHUB_HEAD_REF or GITHUB_REF_NAME. In GitLab CI/CD, it reads
CI_COMMIT_REF_NAME. Otherwise, it uses the current Git branch. On a detached
checkout without these variables, pass the preview name as an argument. For
the output and options, refer to Deploy a preview.
cf sends anonymous usage telemetry, which records whether it runs in CI. To
turn it off for a job, set CF_SEND_TELEMETRY=false or DO_NOT_TRACK=1 in the
job environment. cf does not check npm for updates in CI.
- Develop, build, and deploy describes the project commands.
- Use cf with coding agents covers command discovery and safe operation.