GitHub Actions Runners
Self-hosted GitHub Actions runners powered by ARC (Actions Runner Controller)
on the honey cluster. GitHub is the primary runner product surface.
GitLab remains a compatibility surface elsewhere in the repo.
Core Repo Dogfood Contract
GloriousFlywheel must prove its own runner substrate by using it. Required
first-party workflows for this repository run on shared tinyland-*
capability-class lanes, not GitHub-hosted runners.
This includes:
ValidateSecret DetectionGF REAPI AC Attestation Chaos- Bzlmod/Bazel source, external-input, and vendor-mode proof workflows
- tranche/status renderers that claim runner authority
- docs, FlakeHub, image-mirror, and release publication paths
GitHub-hosted runners are not a first-party fallback for this repo, including
control-plane publication paths such as GitHub Pages or release metadata.
just dogfood-contract-audit fails hosted runs-on usage and fails any
attempt to add a hosted-runner exception. Queue pressure, billing, artifact
quota, and runner availability problems are runner-product problems to expose
and repair, not reasons to hide first-party evidence on GitHub-hosted runners.
For public fork PRs, self-hosted jobs should be skipped rather than running untrusted fork code inside the cluster-backed runner pool. A maintainer can move the change onto a trusted branch or same-repo PR to run the normal dogfood gates.
Available Labels
Use these runs-on values in your GitHub Actions workflows:
| Label | Runner Type | Use Case |
|---|---|---|
tinyland-nix |
nix | Nix builds and reproducible flake workflows |
tinyland-nix-heavy |
nix | Memory-heavy Rust/Nix jobs in environments that enable the additive heavy lane |
tinyland-nix-kvm |
nix | Shared KVM-backed VM execution, Honey-first with gated Sting overflow |
tinyland-nix-gpu |
nix | Bounded GPU and WebGPU smoke workloads |
tinyland-docker |
docker | General CI: linting, testing, builds |
tinyland-dind |
dind | Docker-in-Docker: container image builds |
Not every environment exposes every additive lane. tinyland-nix-kvm and
tinyland-nix-gpu are explicit infrastructure on honey, not assumptions of
the baseline shared pool. Some owner-scoped compatibility sets still exist below
the intended product contract because ARC still needs a unique
runnerScaleSetName for registration and GitHub personal repos do not have
org-style runner groups.
Treat those repo-anchored sets as control-plane debt, not as normal runner
taxonomy. The workflow-facing contract should still be shared capability
labels, not repo-shaped labels. Secondary owner overlays may use
owner-distinct runnerScaleSetName values while adding shared tinyland-*
labels through ARC scale-set labels.
Owner-specific GitHub App installs and any private registration anchors belong in implementation overlays. They should not appear in the core GloriousFlywheel ARC stack as active product lanes.
The ordinary shared tinyland-nix lane can carry its own placement override
when the baseline Nix pool should not compete with the KVM-sensitive honey
surface. Keep that override narrow; do not widen the KVM lane just to relieve
ordinary shared Nix queue pressure.
Quick Start
jobs:
build:
runs-on: tinyland-nix
steps:
- uses: actions/checkout@v6
- uses: tinyland-inc/GloriousFlywheel/.github/actions/nix-job@main
with:
command: nix build .#default
No tokens, no registration for the ordinary shared-lane case. But do not overstate that into blanket reachability: installed GitHub App scope does not by itself prove that every repo or owner boundary can already start jobs on the shared lane. Treat repo inventory, shared-label reachability, and a real default-branch proof as separate checks.
For a downstream repo migration checklist and a canonical consumer example, see Downstream Migration Checklist.
Composite Actions
GloriousFlywheel provides composite actions that auto-configure cache endpoints on self-hosted runners.
setup-flywheel
Base action that detects the runner environment and configures cache endpoints.
On self-hosted runners, ATTIC_SERVER, ATTIC_CACHE, ATTIC_PUBLIC_KEY, and
BAZEL_REMOTE_CACHE are set automatically by the runner/bootstrap contract.
When the Attic server, cache name, and public key are present, the action also
adds the Attic substituter and trusted public key to NIX_CONFIG. On GitHub
Actions it also adds the job token as a Nix github.com access token so flake
and source fetches do not depend on unauthenticated GitHub tarball downloads.
This action does not install Nix. On self-hosted runners that already provide
Nix, it also ensures the runner user can reach the daemon and build-user runtime
before later steps call nix develop, nix build, or just through a flake
shell.
steps:
- uses: tinyland-inc/GloriousFlywheel/.github/actions/setup-flywheel@main
nix-job
Nix job helper. Bootstraps Nix explicitly, configures GloriousFlywheel cache/runtime hints, and runs your command.
steps:
- uses: actions/checkout@v6
- uses: tinyland-inc/GloriousFlywheel/.github/actions/nix-job@main
env:
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN || '' }}
with:
attic-public-key: ${{ vars.ATTIC_PUBLIC_KEY || '' }}
command: nix build .#default
push-cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && secrets.ATTIC_TOKEN != '' && 'true' || 'false' }}
Public reads from the shared main cache need ATTIC_PUBLIC_KEY, not
ATTIC_TOKEN. Keep ATTIC_TOKEN for trusted cache writes and private-cache
reads.
Ordinary pull-request workflows should remain read-only. Trusted
default-branch publication can be token-gated with push-cache, while broad
repo proof workflow writes stay under their separate RustFS/backend guardrail.
The default proof workflows use scripts/cache-attachment-contract.sh --strict --strict-nix on Nix lanes. --strict proves the Bazel remote-cache endpoint is
attached; --strict-nix proves NIX_CONFIG carries the configured Attic
substituter and public key.
docker-job
Standard CI job with Bazel cache configured.
steps:
- uses: actions/checkout@v6
- uses: tinyland-inc/GloriousFlywheel/.github/actions/docker-job@main
with:
command: make build
Cache Integration
ARC runners access caches via cluster-internal DNS. No public management path, no extra ingress layer, and no extra credential exposure:
- Attic:
http://attic.nix-cache.svc.cluster.local - Bazel:
grpc://bazel-cache.nix-cache.svc.cluster.local:9092
Environment variables are injected automatically:
| Variable | Runner Types | Value |
|---|---|---|
ATTIC_SERVER |
nix | Attic API endpoint |
ATTIC_CACHE |
nix | Cache name (default: main) |
ATTIC_PUBLIC_KEY |
nix | Public key required for Nix substituter reads when Attic is configured |
BAZEL_REMOTE_CACHE |
nix, docker, dind | Bazel cache gRPC endpoint |
BAZEL_REMOTE_EXECUTOR |
nix, docker, dind | Only when the ARC lane explicitly sets a backend-neutral executor endpoint |
GF_BAZEL_SUBSTRATE_MODE |
nix, docker, dind | shared-cache-backed by default, executor-backed only with an executor endpoint |
GF_BAZEL_REMOTE_EXECUTION_PLATFORM |
nix, docker, dind | Platform hint injected only with an executor endpoint |
NIX_CONFIG |
nix | Feature flags, GitHub fetch token, plus Attic substituter when the public key is available |
The executor variables are opt-in ARC module wiring. Their presence only selects the repo-managed executor-backed wrapper path; it does not make every workflow or target remote-execution eligible.
Nix Bootstrap Boundary
tinyland-nix is the Nix-oriented runner lane, not a promise that every
self-hosted runner already has Nix preinstalled forever.
Recommended current rule:
- use
nix-jobwhen you want GloriousFlywheel to bootstrap Nix for you - or run
DeterminateSystems/determinate-nix-action@v3before rawnixcommands in self-hosted workflows - treat Attic and Bazel as acceleration layers, not as publication surfaces
- treat FlakeHub as future publication/discovery work, not part of the primary GitHub Actions contract today
- use
tinyland-nix-heavywhen a workflow routinely exceeds the baseline8Gitinyland-nixenvelope and the additive heavy lane is available in the target environment - use
tinyland-nix-kvmfor shared org-facing hardware-virtualization workloads instead of assuming the baselinetinyland-nixpods can see/dev/kvm - keep owner-scoped compatibility proofs out of the canonical lane contract until the control plane can expose the same shared capability classes truthfully
Architecture
ARC uses a controller + scale set model. The controller watches for
workflow_job webhook events and scales runner pods up/down:
arc-systems namespace
└── ARC controller (gha-runner-scale-set-controller)
arc-runners namespace
├── shared nix scale set -> runs-on: tinyland-nix
├── shared KVM nix add-on -> runs-on: tinyland-nix-kvm
├── shared docker scale set -> runs-on: tinyland-docker
└── shared dind scale set -> runs-on: tinyland-dind
ARC scale sets keep committed minRunners = 0 and create runner pods on
demand when a workflow job matches the runs-on label. The live honey Nix
lane also has a scheduled warm-pool CronJob that temporarily raises
minRunners during the configured business-hours window and scales it back
down off-hours.
Important scaling boundary:
- ARC scales the number of runner pods in a scale set
- ARC does not autosize the CPU or memory envelope of one runner pod
- the current
tinyland-nix,tinyland-docker, andtinyland-dindpod limits still come from thearc-runnersstack values - if one job needs more memory than its runner pod limit, the right fix is runner-envelope or builder-lane design, not assuming cluster-wide free RAM will automatically flow into that pod
Infrastructure
The ARC stack is managed by OpenTofu through the root operator path:
export KUBECONFIG=~/.kube/kubeconfig-honey.yaml
export KUBE_CONTEXT=honey
cp tofu/stacks/arc-runners/terraform.tfvars.example tofu/stacks/arc-runners/dev.tfvars
ENV=dev just tofu-preflight arc-runners
ENV=dev just tofu-init arc-runners
ENV=dev just tofu-plan arc-runners
ENV=dev just tofu-apply arc-runners
Use the transitional cloud context only for explicit compatibility testing, not as the primary deployment authority.
GitHub App Setup
ARC authenticates via a GitHub App installed on the target organizations. The App requires:
- Self-hosted runners (Organization): Read & Write
- Metadata (Repository): Read-only
- Actions (Repository): Read-only
Webhook event: workflow_job
Credentials are stored as a Kubernetes secret in the arc-systems namespace.
The implementation overlay or operator secret path for that owner creates the
secret; do not copy another deployment’s private values into core config.
See Also
- Multi-Org / Cross-Repo Runners — add runners for personal repos or other orgs
- Downstream Migration Checklist — canonical downstream consumer pattern and rollback
- Self-Service Enrollment — GitLab runner enrollment
- Cache Integration — cache configuration details
- Runner Selection — choosing the right runner type