This is a minimal, manually triggered repository runner. Each POST /runners
request starts a new Cloudflare Sandbox, obtains a short-lived GitHub runner
registration token, and registers an ephemeral runner that accepts one job.
The sandbox stays alive while the runner waits or works, then the image calls
back to the Worker to destroy it when the runner exits.
Read GitHub's self-hosted runner overview before using it.
Use this as a proof of concept. For production autoscaling, consume
workflow_job events or use GitHub's Runner Scale Set Client, add durable
deduplication and retries, forward runner logs to external storage, and use a
GitHub App instead of a personal access token.
-
Confirm you have a Cloudflare Workers Paid plan, Node.js, Docker, and admin access to the target GitHub repository. See Cloudflare's Sandbox setup guide.
-
Download the Gist files and change
GITHUB_REPOSITORYinwrangler.jsonc. -
Install dependencies with
npm install. -
Create a fine-grained GitHub token scoped to the target repository with Administration: write permission. GitHub documents the token permission and token setup.
-
Store the GitHub token and a random controller token of at least 32 characters:
export CONTROL_TOKEN="$(openssl rand -hex 32)" npx wrangler secret put GITHUB_TOKEN printf '%s' "$CONTROL_TOKEN" | npx wrangler secret put CONTROL_TOKEN
-
Deploy with
npm run deploy. -
Target the runner with a dedicated label:
jobs: test: runs-on: cloudflare-sandbox steps: - uses: actions/checkout@v4 - run: npm test
Queue the job, then start one runner:
curl --fail-with-body \ --request POST \ --header "Authorization: Bearer $CONTROL_TOKEN" \ https://YOUR_WORKER.workers.dev/runners
The runner omits GitHub's default self-hosted, OS, and architecture labels so
it cannot accidentally claim generic self-hosted jobs. The image starts a
rootless Docker daemon before registering the runner, so it supports Docker
container actions, job containers, service containers, and ordinary docker
builds. Pass --network=host to workflow docker build and docker run
commands that need network access, as required by Cloudflare's
Docker-in-Docker guide. This satisfies GitHub's
Linux-and-Docker requirement for containerized
workflows.
Cloudflare Containers cannot manipulate iptables, so runner-managed containers
use host networking. Access service containers through localhost and their
container port instead of the service label. Do not declare service ports:
because published-port mappings require iptables.
Inner containers share the sandbox network stack, cannot run privileged, and
lose images and volumes when the sandbox is destroyed. Docker isolation here is
not an additional security boundary beyond the sandbox.
GitHub recommends external log retention for ephemeral runners. The image sends runner diagnostics to stdout, but you must configure durable log export before using this in production. The image retries its authenticated cleanup callback, but add independent reconciliation before production so a prolonged network failure cannot leave a keep-alive sandbox running. Keep the pinned Sandbox SDK, Sandbox image tag and digest, Wrangler, and GitHub runner versions current.