Run GitHub Actions jobs in fresh, disposable Cloudflare Containers. A signed GitHub App workflow_job webhook asks the Worker to provision a repository-scoped just-in-time runner. The Worker starts a disposable Container with GitHub's JIT runner configuration; after it completes its one assigned job, GitHub automatically deregisters it.
With Node.js 20.19+ (or 22.13+), a GitHub browser session, and Wrangler authentication for a Workers Paid Cloudflare account with Containers enabled:
npx -y cloudflare-github-actions-runner@latest setupThe interactive CLI deploys the runner pool and guides you through selecting its GitHub account or organization, creating its GitHub App, and optional private R2 cache setup. Docker and a local checkout are not needed.
Use one label directly in a workflow:
runs-on label |
Machine type | vCPU | Memory | Disk |
|---|---|---|---|---|
cloudflare-lite |
linux/amd64 |
1/16 | 256 MiB | 2 GB |
cloudflare-basic |
linux/amd64 |
1/4 | 1 GiB | 4 GB |
cloudflare-standard-1 |
linux/amd64 |
1/2 | 4 GiB | 8 GB |
cloudflare-standard-2 |
linux/amd64 |
1 | 6 GiB | 12 GB |
cloudflare-standard-3 |
linux/amd64 |
2 | 8 GiB | 16 GB |
cloudflare-ubuntu-latest |
linux/amd64 |
2 | 8 GiB | 16 GB |
cloudflare-standard-4 |
linux/amd64 |
4 | 12 GiB | 20 GB |
All profiles use the same managed Ubuntu 24.04 runner image. Resource values come from the Cloudflare Containers instance types.
For a custom machine, quote the single label:
runs-on: "cloudflare-vcpu:2-memory_mib:6144-disk_mb:12000"Custom values must use 1–4 whole vCPUs, up to 12,288 MiB memory and 20,000 MB disk; memory must be at least 3,072 MiB per vCPU and disk no more than 2 GB per GiB memory. Invalid requests fail visibly in Set up runner before any workflow step starts.
The interactive setup:
- Verifies your Cloudflare user, account, Containers access, and Docker-free remote builder support.
- Lets you select the Cloudflare account that owns and bills for the shared runner pool.
- Deploys the Worker, runner profiles, and managed runner image.
- Maps the selected Cloudflare account to one GitHub personal account or organization, then creates or reuses that pool's GitHub App. Install it for All repositories in that selected account or organization.
- Optionally creates a private R2 cache bucket (default:
cloudflare-github-actions-runner-cache) with a FIFO quota (default: 100 GB).
One Cloudflare runner pool serves exactly one GitHub account or organization. Run setup again to create a separate mapping for another Cloudflare account. An App installation covers all selected repositories in that target, including future repositories. It needs Actions: Read, Administration: Write, and Contents: Read, plus the workflow_job and push webhook events. App keys, installation tokens, and Cloudflare credentials stay out of runner Containers.
The managed runner image is built from this repository's public main branch, so setup does not need a second App installation on biw just to read it. Setup builds the current image; after a published runner-image update, rerun setup to roll that update into your pool. Keep this repository's main protected.
jobs:
ci:
runs-on: cloudflare-ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run checkUse actions/cache normally. JavaScript actions, including actions/cache and actions/setup-node, are transparently backed by the private R2 bucket; artifact uploads and job results continue to use GitHub. PR caches are scoped to their merge ref and can fall back to the default-branch cache, matching GitHub Actions behavior.
This repository takes its CI definition from main with the pull_request_target event. Before it checks out a pull request's merge ref or starts a Cloudflare runner, it checks the pull-request author's repository permission. Administrators proceed immediately. Everyone else waits at the cloudflare-ci-approval protected GitHub environment for an administrator to approve the run. The job receives only a read-only GITHUB_TOKEN; pull-request-target runs also have read-only access to the shared R2 cache.
The protected environment must list the repository's current administrators as required reviewers, with self-review disabled. CI fails closed before Cloudflare provisioning if that rule is missing. GitHub makes required reviewers available on public repositories for all current plans; while this repository remains private without that entitlement, a non-admin PR is therefore blocked rather than run. Keep main protected so the gate itself remains trusted.
- From a checkout, use
pnpm run deployfor normal updates, orpnpm run deploy:dry-runto inspect safely. Neither requires local Docker. - The account-scoped scheduler queues jobs only when the Cloudflare account lacks available vCPU, memory, or disk. It retains idle application capacity without keeping instances alive or incurring runtime charges.
- A new custom size may wait while Cloudflare rolls out an idle custom runner slot. It reuses the shared image; it does not build another image.
- Containers run the job compute; GitHub provides scheduling and logs. Runners are disposable, have no Cloudflare credentials, and cannot use
sudo. Docker-based Actions are outside this shell/JavaScript POC. - Every runner records one-second CPU, memory, and writable-disk samples to D1. Use
cf-resource-mark "phase"in workflow steps andcf-resource-trace summaryto add a job summary. The included workflow uploads the CSV trace as a seven-day artifact. - Machine labels are currently trusted workflow input. Per-repository quotas and spending controls are future work.
Query the persisted metrics for a GitHub job:
CLOUDFLARE_ACCOUNT_ID=<account-id> pnpm run resource-traces -- \
--command="SELECT sample_elapsed_seconds, phase, cpu_cores_avg, memory_current_bytes, root_disk_delta_bytes FROM resource_trace_samples WHERE job_id = '<github-job-id>' ORDER BY sample_elapsed_seconds"MIT