Run on Fly Machines.
AvailableThe shortest production-piloted path. A small controller app starts one Machine per GitHub job in a separate, secret-free worker app.
01 · Scaffold
Install and authenticate flyctl and gh. Start in a trusted private repository.
npx runneryard init \
--provider fly \
--github https://github.com/acme/widgets \
--max-runners 2Ready whenThe generated diff contains one controller config and one isolated canary workflow.
02 · Isolate
Control and worker apps must be separate because every Machine in a Fly app inherits that app's secrets. Workers stay on a separate private network with zero idle capacity.
fly apps create acme-ci-runners --network runneryard-workers
fly apps create acme-ci-controller --network runneryard-control
fly volumes create runneryard_state \
--app acme-ci-controller --region cdg --size 1 --yesReady whenBoth apps exist, the controller volume is attached, and the worker app has no secrets.
03 · Connect GitHub
The CLI opens GitHub to create your private, owner-controlled App. Its one-time key goes straight to the controller app over stdin; you never copy or see it.
npx runneryard auth github create \
--github https://github.com/acme/widgets \
--controller-app acme-ci-controllerReady whenThe CLI reports a verified installation for the exact repository.
04 · Add compute access
Create a deploy token scoped only to the worker app and store it only on the controller. This is a Fly credential, not a GitHub token. Keep reviewed policy such as MAX_RUNNERS in TOML, never in secrets or pull-request-controlled input.
fly tokens create deploy --app acme-ci-runners
fly secrets set --app acme-ci-controller \
FLY_API_TOKEN='<worker-app deploy token>'Ready whenThe worker app has zero secrets; the controller has GitHub App credentials and one worker-app token.
05 · Budget and deploy
Initialize the fail-closed budget ledger exactly once, then run doctor before the controller starts. The generated worker uses two sustained performance CPUs and 8 GB of RAM; shared CPUs remain an explicit option for short, bursty jobs.
fly machine run ghcr.io/gwendall/runneryard:0.5.6 \
--entrypoint "/usr/local/bin/controller-entrypoint" \
--env RUNNER_BUDGET_FILE=/var/lib/runneryard/budget.json \
--app acme-ci-controller --region cdg \
--volume runneryard_state:/var/lib/runneryard --rm \
-- budget init --file /var/lib/runneryard/budget.json
npx runneryard doctor --provider fly \
--controller-app acme-ci-controller \
--worker-app acme-ci-runners
fly deploy --app acme-ci-controller \
--config .runneryard/fly.controller.toml --ha=falseEach shared vCPU has a 6.25% sustained baseline plus burst capacity. Performance workers cost more per second, but avoid the post-burst slowdown that can turn tests into timeouts and reruns. Measure cost per successful job and keep the operator-owned ceiling low until a representative canary proves the workload. Older scaffolds that omit RUNNER_CPU_KIND remain shared on upgrade; set both shape variables to opt in. Review worker sizing.
Ready whenDoctor passes and the controller status is healthy with zero desired workers.
06 · Canary and route
Trigger the generated workflow. A valid canary proves the pinned Node toolcache, Fly-compatible fuse-overlayfs, and a real BuildKit build and container run. It also has a complete controller lifecycle and leaves no worker behind. Only then route a low-risk job.
gh workflow run runneryard-canary.yml --repo acme/widgets
fly ssh console --app acme-ci-controller \
--command '/usr/local/bin/runneryard status'
fly machines list --app acme-ci-runners
npx runneryard route enable \
--github https://github.com/acme/widgets \
--label acme-linux --confirm-canaryKeep ubuntu-latest available as the explicit emergency route. Size capacity and budget, then read the complete Fly guide for rotation and recovery details.
Outboard
Disable the route, finish active jobs, stop the controller, and verify zero workers. Revoke the worker-app token and delete the two apps and volume. Revoke and delete a dedicated GitHub App. For a shared BYO App, remove this controller's local credential; change installation scope only after confirming no other consumer needs it.
npx runneryard route disable --github https://github.com/acme/widgets