GitHub Actions Runners

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:

  • Validate
  • Secret Detection
  • GF 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-job when you want GloriousFlywheel to bootstrap Nix for you
  • or run DeterminateSystems/determinate-nix-action@v3 before raw nix commands 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-heavy when a workflow routinely exceeds the baseline 8Gi tinyland-nix envelope and the additive heavy lane is available in the target environment
  • use tinyland-nix-kvm for shared org-facing hardware-virtualization workloads instead of assuming the baseline tinyland-nix pods 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, and tinyland-dind pod limits still come from the arc-runners stack 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

GloriousFlywheel