GitHub App Adoption
GloriousFlywheel uses a GitHub App to authenticate ARC (Actions Runner Controller) with GitHub. This guide explains how to install the app on your organization and configure runner access.
What is GloriousFlywheel?
GloriousFlywheel is a GitHub App (ID 2953466) that enables self-hosted
GitHub Actions runners via ARC. It listens for workflow_job webhook
events and scales runner pods on demand.
Install on a New Organization
- Navigate to the GitHub App settings page
- Click Install and select the target organization
- Choose All repositories or select specific repos
- Approve the installation
Required Permissions
| Permission | Scope | Access |
|---|---|---|
| Self-hosted runners | Organization | Read & Write |
| Metadata | Repository | Read-only |
| Actions | Repository | Read-only |
Webhook Events
The app subscribes to workflow_job events. These trigger runner
pod creation when a job matches a self-hosted runner label.
Kubernetes Secret Setup
ARC authenticates using a Kubernetes secret containing the GitHub App credentials. This secret must exist in both namespaces:
arc-systems— used by the ARC controller for API authenticationarc-runners— used by runner scale sets for registration
# Create the secret in both namespaces
for ns in arc-systems arc-runners; do
kubectl create secret generic github-app-secret \
--namespace="$ns" \
--from-literal=github_app_id=2953466 \
--from-literal=github_app_installation_id=<INSTALLATION_ID> \
--from-file=github_app_pem=<PATH_TO_PEM_FILE>
done
Runner Group Configuration
The default runner group must allow public repositories if you want self-hosted runners available to public repos:
gh api -X PATCH /orgs/<ORG>/actions/runner-groups/1 \
-f allows_public_repositories=true
Without this, workflows in public repos will fail to match self-hosted runner labels.
Deploy the ARC Stack
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
This deploys:
- ARC controller in
arc-systems - Three runner scale sets (gh-nix, gh-docker, gh-dind) in
arc-runners - Optional additive scale sets from
dev-extra-runner-sets.tfvars, including the current heavy Nix canarytinyland-nix-heavy
For the current Tinyland rollout, honey is the only physical cluster target.
The Civo cluster has been decommissioned (April 2026).
Composite Actions
GloriousFlywheel provides composite actions that auto-configure cache endpoints on self-hosted runners:
| Action | Description |
|---|---|
setup-flywheel |
Detect runner environment and configure cache/runtime hints |
nix-job |
Bootstrap Nix explicitly and run a Nix job with cache hints |
docker-job |
Standard CI job with Bazel cache |
Usage
jobs:
build:
runs-on: tinyland-nix
steps:
- uses: actions/checkout@v4
- uses: tinyland-inc/GloriousFlywheel/.github/actions/nix-job@main
with:
command: nix build .#default
push-cache: "true"
setup-flywheel configures cache/runtime hints on self-hosted runners. It does
not install Nix by itself. Use nix-job or run
DeterminateSystems/determinate-nix-action@v3 before raw nix commands.
Current scaling boundary:
- ARC can scale the number of runner pods up and down
- ARC does not automatically increase the CPU or memory limit of one runner pod
- the baseline
tinyland-nixlane is still an8Gimemory-limit runner
Workflow Examples
Simple Nix Build
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: tinyland-nix
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@v3
- uses: tinyland-inc/GloriousFlywheel/.github/actions/setup-flywheel@main
- run: nix build
Docker Build
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: tinyland-docker
steps:
- uses: actions/checkout@v4
- run: make test
build-image:
runs-on: tinyland-dind
needs: test
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp .
Multi-Org / Cross-Repo Runners
By default, ARC runner scale sets register at the organization level —
every repo in that org can use runs-on: tinyland-nix. But personal repos
or repos in other orgs can’t reach those runners.
The extra_runner_sets variable lets you deploy additional scale sets
scoped to a different org or a single repository, all on the same cluster
and sharing the same ARC controller.
How githubConfigUrl Scoping Works
| URL pattern | Scope |
|---|---|
https://github.com/ORG |
All repos in the org |
https://github.com/OWNER/REPO |
Single repository only |
Steps
-
Install GloriousFlywheel on the target GitHub account/org (Settings → Applications → Install).
-
Create a K8s secret for the new installation:
for ns in arc-systems arc-runners; do kubectl create secret generic github-app-secret-chapel \ --namespace="$ns" \ --from-literal=github_app_id=2953466 \ --from-literal=github_app_installation_id=<NEW_INSTALLATION_ID> \ --from-file=github_app_pem=<PATH_TO_PEM_FILE> done -
Add an
extra_runner_setsentry in your tfvars:extra_runner_sets = { chapel-nix = { github_config_url = "https://github.com/Jesssullivan/chapel" github_config_secret = "github-app-secret-chapel" runner_label = "chapel-nix" runner_labels = ["self-hosted", "nix", "linux"] runner_type = "nix" max_runners = 3 cpu_limit = "4" memory_limit = "8Gi" } }For a heavier Nix lane, use the same pattern with a distinct label and a larger static envelope:
extra_runner_sets = { tinyland-nix-heavy = { github_config_url = "https://github.com/tinyland-inc" github_config_secret = "github-app-secret" runner_label = "tinyland-nix-heavy" runner_labels = ["self-hosted", "nix", "linux"] runner_type = "nix" max_runners = 2 cpu_limit = "8" memory_limit = "16Gi" } }This is the current GloriousFlywheel-style answer for memory-heavy Nix jobs. ARC will scale the number of
tinyland-nix-heavypods, but it will not resizetinyland-nixitself on demand.For browser-driven repos that already queue on a repo-owned label contract, keep the workflow labels stable and swap the backing infrastructure instead:
extra_runner_sets = { massageithaca-browser = { github_config_url = "https://github.com/Jesssullivan/MassageIthaca" github_config_secret = "github-app-secret-massageithaca" runner_label = "massageithaca" runner_labels = ["self-hosted", "Linux", "X64", "honey"] runner_type = "docker" runner_image = "ghcr.io/tinyland-inc/actions-runner-browser:latest" max_runners = 3 } }That pattern preserves a repo’s current
runs-oncontract while replacing long-lived pet runners with ARC-managed pods. If the existing GitHub App or PAT secret is scoped too narrowly for the new repo, create a dedicated secret first instead of reusing a secret that only covers a different repository.Important boundary:
- prefer a dedicated GitHub App installation and secret for active
repo-scoped lanes on
Jesssullivan/*or other non-org surfaces - treat PAT-backed secrets as a temporary compatibility bridge only
- a shared PAT-backed secret burns one user’s REST core budget, so one hot repo or one noisy automation loop can strand every repo-scoped lane that reuses that PAT
- if listener logs show
failed to get runner registration tokenwithAPI rate limit exceeded for user ID ..., the fix is app auth, not more runner capacity
- prefer a dedicated GitHub App installation and secret for active
repo-scoped lanes on
-
Apply — the new scale set appears in
arc-runnersalongside the existing ones:ENV=dev just tofu-plan arc-runners ENV=dev just tofu-apply arc-runners -
Use the label in a workflow:
jobs: build: runs-on: tinyland-nix-heavy steps: - uses: actions/checkout@v4 - uses: DeterminateSystems/determinate-nix-action@v3 - uses: tinyland-inc/GloriousFlywheel/.github/actions/setup-flywheel@main - run: nix build
See Also
- GitHub Actions Runners — runner labels, cache integration, architecture
- Downstream Migration Checklist — canonical downstream consumer pattern
- Cross-Forge CI — GitLab CI vs GitHub Actions comparison
- Runner Selection Guide — choosing the right runner type