Claude Skill

github-runner

Deploy, manage, and troubleshoot self-hosted GitHub Actions runners. Covers systemd service, Docker containers, Kubernetes (Actions Runner Controller), and the Scale Set Client. Use when setting up a CI runner, debugging registration failures, designing autoscaling, or hardening

LLM Mart · 0 points · 10 views 27 listing impressions 0 install-command copies

#kubernetes #devops

Virus-scanned Reviewed automatically before listing.

Full trust report

Download magnus919-agent-skills-github-runner-addad86.zip · 20 KB
Part of magnus919/agent-skills — 145 skills

Install

skills CLI npx skills add https://github.com/magnus919/agent-skills/tree/main/github-runner
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install magnus919-agent-skills@llmmart
Git git clone https://github.com/magnus919/agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole magnus919/agent-skills collection as a plugin from our marketplace. Git is the plain clone.

README

Self-Hosted GitHub Actions Runners

Deploy, manage, and troubleshoot self-hosted GitHub Actions runners. Covers systemd, Docker, Kubernetes (ARC), and Scale Set Client deployments.

Why Install This Skill

When your agent loads this skill, it becomes a CI/CD infrastructure engineer who can:

  • Choose the right deployment — systemd for single machines, Docker for homelabs, ARC for Kubernetes teams
  • Troubleshoot registration failures — the critical ACCESS_TOKEN vs RUNNER_TOKEN distinction
  • Design autoscaling — ARC, Scale Set Client, ephemeral runners
  • Harden runner security — public repo risks, JIT tokens, runner groups
  • Build custom runner images — Dockerfiles with custom toolchain, Python, Ruby
  • Solve network issues — firewall rules, TLS, proxy configuration

What You Get

Directory Purpose
SKILL.md Deployment spectrum, quick reference, label strategy
references/ 7 reference files: deployment, management, scaling, security, custom images, network, monitoring

Quick Start

Start with the setup and first workflow in SKILL.md, then use the linked resources for the specific task you need to complete.

Triggers

Load this when setting up CI, troubleshooting runner registration failures, designing autoscaling, or hardening runner security.

Requirements

Linux, macOS, or Windows target hosts. Docker for containerized runners. Kubernetes for ARC deployments.

Skill manifest

Self-Hosted GitHub Actions Runner

Deploy and manage self-hosted GitHub Actions runners — the machines that execute CI/CD workflow jobs. Self-hosted runners give you control over hardware, OS, and toolchain, at the cost of requiring you to maintain the environment.

When to Use

Trigger What to do
"Set up CI for my project", "deploy a self-hosted runner for [repo/org]" Read deployment — choose systemd, Docker, or ARC
"Runner won't register / keeps failing", "runner gets 404", "registration token expired" Read management — ACCESS_TOKEN vs RUNNER_TOKEN, groups
"How to scale runners automatically", "auto-scale runners", "too many queued jobs" Read scaling — ARC, Scale Set Client, ephemeral
"Secure my self-hosted runners", "hardening runners", "runner security", "public repo risk" Read security — public repo risks, ephemeral, JIT, groups
"Make a custom runner image", "build a runner Dockerfile", "custom runner with Python" Read custom-images — Dockerfile, ARC container modes
"What domains does a runner need to reach?", "runner firewall rules", "runner network setup" Read network — firewall rules, TLS, proxy
"Labels, groups, or both for routing?", "how to target specific runners" Read management — labels and groups sections
"Monitor / troubleshoot runner issues", "runner offline", "runner not picking up jobs" Read management — monitoring and troubleshooting sections

Quick Reference

Deployment Spectrum

Approach Complexity Autoscaling Best For
systemd service Low Manual Single machine, simple CI
Docker container Medium Manual replicas Homelab, small team
ARC (Kubernetes) High Built-in Teams with K8s expertise
Scale Set Client High Custom Non-K8s platform teams

Critical: ACCESS_TOKEN vs RUNNER_TOKEN

This is the most common setup failure. The myoung34/github-runner Docker entrypoint unexports RUNNER_TOKEN at startup — it's only used for de-registration. Registration requires ACCESS_TOKEN (a GitHub PAT).

Token Purpose Expiry
ACCESS_TOKEN Registration — generates fresh tokens via GitHub API Long-lived (PAT)
RUNNER_TOKEN De-registration only — NOT for initial registration 60 min

PAT scopes:

  • Repo-level: repo
  • Org-level: admin:org
  • Enterprise-level: manage_runners:enterprise

Labels Strategy

Default labels: self-hosted + OS (linux/windows/macOS) + arch (x64/ARM/ARM32/ARM64)

Common convention: self-hosted,<hostname>,<os>,<arch>,<project>. In workflows:

runs-on: [self-hosted, linux, x64, gpu]

All labels must match (AND logic). Use --no-default-labels to strip OS/arch auto-labels.

Key Pitfalls

Problem Cause Fix
404 on POST to runner-registration RUNNER_TOKEN used instead of ACCESS_TOKEN Switch to ACCESS_TOKEN with PAT
"Could not find any self-hosted runner group named 'Default'" Org uses different group name Check groups via gh api, set RUNNER_GROUP
"Ephemeral option is enabled" when not wanted EPHEMERAL=0 — truthy in bash Use EPHEMERAL=false (string)
docker compose down -v wipes credentials Named volumes deleted With ACCESS_TOKEN, auto-recovers
Runner can't see host filesystem paths Runner runs inside Docker container Write deploy configs inline in workflow
Hugo build: "Go not found" No Go on Ubuntu 20.04 runner hugo mod vendor and commit _vendor/
GHCR pull "unauthorized" No Docker registry auth in deploy job Add docker/login-action@v4
Runner offline >14 days Auto-removed by GitHub Register a new runner

Sequential Workflow

1. Choose deployment approach

Read deployment and select systemd, Docker, ARC, or Scale Set Client.

2. Figure out what runner scope you need

  • Repo-level: Runner scoped to a single repo — you need admin access
  • Org-level: Runner shared across repos in an org — you need org owner access
  • Enterprise-level: Runner shared across orgs in an enterprise — you need enterprise access

3. Register the runner

Read the architecture reference for the registration flow.

4. Route jobs to the runner

Use runs-on with labels and optionally groups. Read management labels section.

5. Monitor and troubleshoot

Read the management troubleshooting section when things go wrong.

6. Plan for security and scaling

Read security and scaling for production deployments.

Templates

Reference Files

File Load when
references/architecture.md You need to understand registration flow, job lifecycle, or runner communication
references/deployment.md You need to deploy a runner — systemd, Docker, ARC, or Scale Set Client
references/security.md You need to harden runners, set up ephemeral/JIT, configure groups
references/scaling.md You need autoscaling — ARC, Scale Set Client, or webhook-driven
references/management.md You need groups, labels, monitoring, troubleshooting, or cleanup
references/custom-images.md You need a custom runner Dockerfile, ARC Kubernetes mode
references/network.md You need firewall rules, proxy config, or are troubleshooting connectivity
Files (agent-skills)
  • evals
    • evals.json 2.8 KB
      {
        "schema_version": 1,
        "skill_name": "github-runner",
        "evals": [
          {
            "id": "github-runner-core-workflow",
            "prompt": "Use github runner to handle a realistic primary task. Explain the inputs, ordered workflow, and concrete output.",
            "expected_output": "A github runner response defines the task boundary, identifies required inputs, applies the documented workflow, and produces a concrete output with verification.",
            "assertions": [
              "Names the github runner task and required inputs",
              "Applies an ordered workflow rather than generic advice",
              "Produces a concrete output and verification step"
            ]
          },
          {
            "id": "github-runner-failure-diagnosis",
            "prompt": "A github runner task is failing with an ambiguous symptom. Diagnose it and give a bounded recovery path.",
            "expected_output": "The response separates symptoms from causes, proposes evidence-gathering checks, and gives a reversible recovery path with a stop condition.",
            "assertions": [
              "Separates symptom, hypothesis, and evidence",
              "Uses targeted diagnostic checks",
              "Includes a reversible recovery and stop condition"
            ]
          },
          {
            "id": "github-runner-safety-boundary",
            "prompt": "Plan a github runner change that could affect user data or external state. Show the safety gate before acting.",
            "expected_output": "The response confirms scope and authority, defaults to read-only or dry-run inspection, and requires explicit confirmation before consequential mutation.",
            "assertions": [
              "Confirms target, scope, and authority before mutation",
              "Uses read-only or dry-run inspection first",
              "Requires explicit confirmation for consequential changes"
            ]
          },
          {
            "id": "github-runner-edge-case",
            "prompt": "Apply github runner when requirements conflict or an important input is missing. Decide what to do next.",
            "expected_output": "The response identifies the missing or conflicting constraint, refuses to invent facts, and escalates or requests the smallest clarifying input needed.",
            "assertions": [
              "Identifies the missing or conflicting constraint",
              "Does not invent unavailable facts",
              "Requests clarification or escalates with a bounded next step"
            ]
          },
          {
            "id": "github-runner-evidence-handoff",
            "prompt": "Create a review-ready github runner handoff for another practitioner.",
            "expected_output": "The handoff records assumptions, decisions, artifacts, validation evidence, and unresolved risks so another practitioner can reproduce the result.",
            "assertions": [
              "Records assumptions and decisions",
              "Links concrete artifacts to validation evidence",
              "States unresolved risks and reproducible next steps"
            ]
          }
        ]
      }
      
  • references
    • architecture.md 3.3 KB
      # Runner Architecture
      
      ## Registration Flow
      
      1. **Token generation**: A GitHub PAT (or short-lived registration token) is used to call `POST /actions/runners/registration-token` via the GitHub API. Tokens expire in ~60 minutes.
      
      2. **Configuration**: `config.sh --url <scope> --token <token>` creates `.credentials` and `.runner` files.
         - `--labels`: comma-separated custom labels
         - `--runnergroup`: target group (fails if group doesn't exist)
         - `--ephemeral`: one-job-only mode
         - `--disableupdate`: opt out of auto-updates
         - `--no-default-labels`: strip OS/arch auto-labels
      
      3. **Connection**: `run.sh` establishes an HTTPS long-poll connection to `*.actions.githubusercontent.com`:
         - Sends "listening for jobs" heartbeat
         - Receives job assignments in real-time
         - Output: `√ Connected to GitHub` followed by `Listening for Jobs`
      
      ## Job Assignment Lifecycle
      
      1. Workflow triggers → GitHub Actions service dispatches jobs matching `runs-on` labels/groups
      2. Runner receives "Job Available" message via long-poll
      3. If idle and online, runner acknowledges and accepts the job
      4. If the runner doesn't pick up the assigned job within 60 seconds, the job is re-queued
      5. Runner downloads job details, executes steps sequentially
      6. Streams logs and status back to GitHub via HTTPS
      7. For ephemeral runners: runner deregisters automatically after job completion
      8. For persistent runners: runner returns to Listening state
      
      ## Routing Precedence
      
      - GitHub matches `runs-on: [self-hosted, linux, x64, gpu]` — runner must match ALL labels
      - Runner groups can be specified alongside labels:
        ```yaml
        runs-on:
          group: ubuntu-runners
          labels: ubuntu-24.04-16core
        ```
      - If no matching runner is online, the job queues for up to 24 hours
      - If a runner doesn't pick up an assigned job within 60 seconds, the job is re-queued
      
      ## Service Management
      
      | Platform | Command | Notes |
      |----------|---------|-------|
      | Linux (systemd) | `sudo ./svc.sh install && sudo ./svc.sh start` | Creates unit at `/etc/systemd/system/actions.runner.*` |
      | macOS (launchd) | `./svc.sh install && ./svc.sh start` | Creates plist in user's LaunchAgents |
      | Windows | Part of config script | Managed via Services app or PowerShell |
      | Docker | Container entrypoint handles lifecycle | Named volume persists credentials |
      
      ## Service Commands (Linux/macOS)
      
      ```bash
      ./svc.sh install [username]   # Install service (Linux: optional user arg)
      sudo ./svc.sh start           # Start service
      sudo ./svc.sh status          # Check service status
      sudo ./svc.sh stop            # Stop service
      sudo ./svc.sh uninstall       # Remove service
      ```
      
      ## Key Files (on-disk runner installation)
      
      | File | Purpose |
      |------|---------|
      | `.runner` | Configuration — scope, URL, runner name |
      | `.credentials` | Encrypted auth credentials (persisted across restarts) |
      | `.credentials_rsaparams` | RSA key pair for authentication |
      | `.service` | Service name (written by svc.sh install) |
      | `_diag/` | Log files — `Runner_<timestamp>.log`, `Worker_<timestamp>.log` |
      | `_update/` | Self-update binaries and logs |
      
      ## Automatic Cleanup
      
      - **Persistent runner** offline > 14 days: automatically removed by GitHub
      - **Ephemeral runner** offline > 1 day: automatically removed by GitHub
      - **JIT runners**: removed after single job or automatically if never used
      
    • custom-images.md 3.4 KB
      # Custom Runner Images
      
      ## GitHub's Official Runner Image
      
      Published at `ghcr.io/actions/actions-runner`. Based on `mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy`.
      
      **Contents:**
      - Runner binaries
      - Runner container hooks (for Kubernetes mode with ARC)
      - Docker CLI (for Docker-in-Docker mode)
      
      Tags accompany each runner release + `latest`.
      
      ## Building Custom Runner Images
      
      Requirements:
      1. Base image must run the runner application (Linux with standard system dependencies)
      2. Runner binary at `/home/runner/`
      3. Launch via `/home/runner/run.sh`
      4. For ARC Kubernetes mode: container hooks at `/home/runner/k8s`
      
      **Example Dockerfile:**
      
      ```dockerfile
      FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
      
      ARG RUNNER_VERSION="2.322.0"
      ARG RUNNER_ARCH="x64"
      ARG RUNNER_CONTAINER_HOOKS_VERSION="0.3.1"
      
      ENV DEBIAN_FRONTEND=noninteractive
      ENV RUNNER_MANUALLY_TRAP_SIG=1
      ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
      
      RUN apt update -y && apt install curl unzip -y
      
      RUN adduser --disabled-password --gecos "" --uid 1001 runner \
          && groupadd docker --gid 123 \
          && usermod -aG sudo runner \
          && usermod -aG docker runner \
          && echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers
      
      WORKDIR /home/runner
      
      RUN curl -f -L -o runner.tar.gz \
            https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
          && tar xzf ./runner.tar.gz && rm runner.tar.gz
      
      RUN curl -f -L -o runner-container-hooks.zip \
            https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \
          && unzip ./runner-container-hooks.zip -d ./k8s && rm runner-container-hooks.zip
      
      USER runner
      ```
      
      > **Keep versions current:** The `RUNNER_VERSION` and `RUNNER_CONTAINER_HOOKS_VERSION` build args above are examples. Check [github.com/actions/runner/releases](https://github.com/actions/runner/releases) and [github.com/actions/runner-container-hooks/releases](https://github.com/actions/runner-container-hooks/releases) for the latest versions before building. Runners with `--disableupdate` must be updated within 30 days of a new release or GitHub stops assigning jobs.
      
      ## Installing Software
      
      Options for adding tools to runner environments:
      
      1. **Pre-installed image**: Build a custom Dockerfile with needed tools baked in
      2. **Setup actions**: Use `actions/setup-*` in workflows — recommended for standard tools
      3. **Inline install**: Add `apt-get` or `pip install` steps to workflow — adds to job runtime
      4. **VM base image**: For VM runners, bake tools into the base image
      
      ## ARC Container Modes
      
      | Mode | Description | Use Case |
      |------|-------------|----------|
      | Docker-in-Docker | Runner pod runs Docker inside; steps use Docker actions | Workflows that need containers |
      | Kubernetes | Each step runs as its own K8s pod; no Docker needed | Lighter footprint, cleaner isolation |
      | Default | Runner binary runs directly in container | Simple workflows, no containers needed |
      
      ## Runner Version Management
      
      - Check latest release: https://github.com/actions/runner/releases
      - Subscribe to releases for notifications
      - **30-day update window** for `--disableupdate` runners — after 30 days, GitHub stops assigning jobs
      - **Critical security updates** block jobs immediately until updated
      - For Docker runners: update image tag and recreate containers
      - For systemd runners: download new binary, stop service, replace, restart
      
    • deployment.md 5.8 KB
      # Deployment Approaches
      
      Four primary approaches to deploying self-hosted runners.
      
      ## 1. systemd Service (Linux)
      
      Simplest approach. Download the runner binary, configure, install as a service.
      
      ```bash
      mkdir actions-runner && cd actions-runner
      curl -o runner.tar.gz -L \
        https://github.com/actions/runner/releases/download/v2.322.0/actions-runner-linux-x64-2.322.0.tar.gz
      tar xzf runner.tar.gz
      ./config.sh --url https://github.com/org/repo --token <token>
      sudo ./svc.sh install
      sudo ./svc.sh start
      ```
      
      **Pros:** Minimal dependencies, full control, direct filesystem access
      **Cons:** Manual updates, no built-in lifecycle management, state lives on the host
      
      ## 2. Docker Container (myoung34/github-runner)
      
      Ubuntu 20.04-based image wrapping the runner binary with Docker-specific lifecycle management.
      
      ```yaml
      services:
        runner:
          image: myoung34/github-runner:latest
          container_name: runner
          restart: unless-stopped
          environment:
            - RUNNER_NAME=my-runner
            - RUNNER_SCOPE=org
            - ORG_NAME=myorg
            - ACCESS_TOKEN=ghp_...
            - RUNNER_GROUP=self-hosted
            - LABELS=self-hosted,linux,x64
            - DISABLE_AUTO_UPDATE=1
            - EPHEMERAL=false
          volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - runner-data:/runner
          networks:
            - traefik
      
      volumes:
        runner-data:
      
      networks:
        traefik:
          external: true
      ```
      
      **Pros:** Container isolation, named volume for credentials, Docker socket for DinD builds, easy multi-replica
      **Cons:** Cannot see host filesystem paths, Ubuntu 20.04 base (Python 3.8), needs Docker socket for builds
      
      **Critical environment variables:**
      
      | Variable | Purpose | Notes |
      |----------|---------|-------|
      | `ACCESS_TOKEN` | GitHub PAT for registration | Use this, NOT `RUNNER_TOKEN` |
      | `RUNNER_SCOPE` | `org` or `repo` | Determines registration endpoint |
      | `ORG_NAME` | GitHub org | Required for org scope |
      | `REPO_URL` | Full repo URL | Required for repo scope |
      | `RUNNER_GROUP` | Target group | Fails if group doesn't exist |
      | `LABELS` | Comma-separated | Job routing |
      | `EPHEMERAL` | `false` or `true` | Use string, not `0` |
      | `DISABLE_AUTO_UPDATE` | `1` | Docker handles version management |
      | `RUNNER_WORKDIR` | `/runner/work` | Job working directory |
      
      ```bash
      # Use .env file for the token
      echo "ACCESS_TOKEN=ghp_..." > .env
      # In docker-compose.yml: ${ACCESS_TOKEN} or inline
      ```
      
      ## 3. Actions Runner Controller (ARC) — Kubernetes
      
      GitHub's reference implementation — a Kubernetes operator that orchestrates runner scale sets.
      
      **Architecture:**
      1. Controller manager deploys in specified namespace
      2. AutoScalingRunnerSet resource registers runner scale set with GitHub API
      3. Runner ScaleSet Listener establishes HTTPS long-poll connection
      4. When a job arrives, listener patches EphemeralRunnerSet with desired replica count
      5. EphemeralRunner Controller requests JIT tokens and creates runner pods
      6. Runner pod executes job, deregisters, pod is deleted
      
      **Quickstart:**
      ```bash
      # Install the controller
      helm install arc-controller \
        oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller \
        --namespace arc-system --create-namespace
      
      # Deploy a runner scale set
      helm install arc-runner-set \
        oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
        --namespace arc-runners --create-namespace \
        --values values.yaml
      ```
      
      **Container modes:**
      - **Docker-in-Docker (dind)**: Runner pod runs Docker inside — heavier, for workflows needing Docker actions
      - **Kubernetes mode**: Steps run as individual pods — lighter, cleaner isolation
      - **Default**: Runner binary runs directly in container
      
      **Required CRDs:**
      - `AutoScalingRunnerSet`
      - `EphemeralRunnerSet`
      - `RunnerScaleSetListener`
      
      ## 4. GitHub Actions Runner Scale Set Client
      
      Standalone Go module for building custom autoscaling outside Kubernetes. Handles GitHub API interactions while you handle infrastructure provisioning.
      
      **Use case:** Platform teams who need custom autoscaling across VMs, containers, on-prem, or cloud. Supports Windows, Linux, macOS.
      
      **Key properties:**
      - Orchestrates GitHub API interactions for scale set registration
      - Leaves infrastructure provisioning to you
      - Multiple labels for flexible job routing
      - Real-time telemetry for job execution
      - Extensible — customize for specific requirements
      
      **Note:** This is NOT a replacement for ARC. ARC remains the reference Kubernetes implementation. The Scale Set Client is for non-Kubernetes environments.
      
      **Repository:** `actions/scaleset` on GitHub
      
      ## Decision Matrix: Which Approach to Choose
      
      | Scenario | Recommended | Why |
      |----------|-------------|-----|
      | Single machine, simple CI, <10 runs/day | systemd | Minimal dependencies, full filesystem access, no Docker needed |
      | Small team (3-10), multiple servers, ~10-100 runs/day | Docker | Container isolation, easy management, restart-on-failure, DinD for builds |
      | 10+ devs, growing CI volume, existing K8s cluster | ARC | Production autoscaling, K8s-native, JIT tokens, clean per-job isolation |
      | Platform team, non-K8s infra, custom provisioning | Scale Set Client | Full control over provisioning, VM/hybrid/multi-platform, customized scaling logic |
      
      **Decision flow:**
      1. Do you have a Kubernetes cluster you already maintain? → **ARC**
      2. No K8s but need isolation and container management? → **Docker**
      3. Only 1 machine, low volume, no containers needed? → **systemd**
      4. Platform team with custom infrastructure and non-K8s scale requirements? → **Scale Set Client**
      
      ## Deployment Comparison
      
      | Approach | Complexity | Autoscaling | Security | Best For |
      |----------|------------|-------------|----------|----------|
      | systemd | Low | Manual | Low | Single machine, simple CI |
      | Docker | Medium | Manual replicas | Medium | Small team, homelab |
      | ARC (K8s) | High | Yes (Kubernetes) | High | Teams with K8s expertise |
      | Scale Set Client | High | Yes (custom) | High | Platform teams, non-K8s |
      
    • management.md 7.6 KB
      # Management & Operations
      
      ## Runner Groups
      
      Groups control which repos can access which runners at the org level.
      
      **Key operations:**
      - Create via UI: Settings → Actions → Runner groups → New runner group
      - Create via gh CLI:
        ```bash
        gh api --method POST orgs/<org>/actions/runner-groups \
          -f name="<group-name>" -f visibility="selected"
        gh api --method POST orgs/<org>/actions/runner-groups \
          -f name="<group-name>" -f visibility="all"
        ```
      - Register into group: `./config.sh --runnergroup <name>`
      - Move runner between groups: GitHub UI → Settings → Actions → Runners → click runner → Runner group dropdown
      - List groups: `gh api orgs/<org>/actions/runner-groups --jq '.runner_groups[].name'`
      - Default group exists in every org; unnamed runners land there
      - Restrict repo access: Selected repositories vs All repositories
      - Delete group: all runners must be moved or removed first
      
      **Security:** Groups prevent repos in group A from using runners in group B.
      
      ## Labels
      
      Labels control job routing at the runner level.
      
      **Default labels:** `self-hosted`, OS (`linux`/`windows`/`macOS`), arch (`x64`/`ARM`/`ARM32`/`ARM64`)
      
      **Custom labels:**
      - Add at registration: `./config.sh --labels gpu,fast-ssd`
      - Add/remove after registration: via GitHub UI
      - Use in workflows: `runs-on: [self-hosted, linux, x64, gpu]`
      - All labels must match (AND logic)
      - `--no-default-labels`: strip OS/arch auto-labels
      - Combine with groups: `runs-on: { group: ubuntu-runners, labels: ubuntu-24.04-16core }`
      
      ## Monitoring
      
      ### Status in GitHub UI
      - **Idle**: Connected, ready for jobs
      - **Active**: Currently executing a job
      - **Offline**: Not connected
      
      ### Log Files
      Located in `_diag/` directory:
      - `Runner_<timestamp>.log`: App lifecycle, connection status, updates
      - `Worker_<timestamp>.log`: Per-job execution details
      
      ### Journalctl (Linux systemd runners)
      ```bash
      # Find service name
      cat ~/actions-runner/.service
      
      # Follow logs
      sudo journalctl -u actions.runner.<scope>.<name>.service -f
      ```
      
      ### Docker Runners
      ```bash
      docker logs <container-name> --tail 20
      ```
      
      ### gh CLI for Runner Status
      ```bash
      # List runners for a repo
      gh api repos/<owner>/<repo>/actions/runners --jq '.runners[] | "\(.name) (\(.status))"'
      
      # List runners for an org
      gh api orgs/<org>/actions/runners --jq '.runners[] | "\(.name) (\(.status))"'
      
      # Check runner groups
      gh api orgs/<org>/actions/runner-groups --jq '.runner_groups[].name'
      ```
      
      ### Network Connectivity Check
      ```bash
      ./config.sh --check --url <url> --pat <pat_with_workflow_scope>
      ```
      Tests each required endpoint (github.com, api.github.com, *.actions.githubusercontent.com, etc.) and outputs PASS/FAIL per endpoint. Logs in `_diag/`.
      
      ## Runner Offline Diagnostic Procedure
      
      When a runner shows "offline" in GitHub UI, follow these steps in order:
      
      **1. Check container/service logs**
      ```bash
      docker logs <container> --tail 50          # Docker runner
      sudo journalctl -u actions.runner.* -f     # systemd runner
      ```
      Look for: connection failures, auth errors, or the runner starting then immediately losing connection.
      
      **2. Test network connectivity from the runner**
      ```bash
      ./config.sh --check --url <url> --pat <pat>
      ```
      This tests all required endpoints (github.com, api.github.com, *.actions.githubusercontent.com, etc.) and reports PASS/FAIL per endpoint. Logs in `_diag/`.
      
      **3. Verify runner registration with gh CLI**
      ```bash
      gh api repos/<owner>/<repo>/actions/runners --jq '.runners[] | "\(.name) (\(.status))"'
      ```
      If the runner name doesn't appear, it was auto-removed (offline >14 days). With ACCESS_TOKEN set, the container should re-register on restart.
      
      **4. Check IP allow lists**
      If your org uses IP allow lists, the runner's outbound IP must be added. Find it via:
      ```bash
      curl -s ifconfig.me
      ```
      
      **Most likely cause for "worked yesterday, offline today":** Network/firewall change or expired ACCESS_TOKEN.
      
      ## Troubleshooting
      
      | Issue | Likely Cause | Fix |
      |-------|-------------|-----|
      | 404 on registration | Using `RUNNER_TOKEN` instead of `ACCESS_TOKEN` | Switch to `ACCESS_TOKEN` PAT |
      | "Not configured" crash | No valid credentials; registration failed | Check logs; verify ACCESS_TOKEN or generate fresh token |
      | "Could not find any self-hosted runner group named 'Default'" | Org uses differently-named group | Set `RUNNER_GROUP` to the actual group name |
      | Ephemeral mode when not wanted | `EPHEMERAL=0` (truthy in bash) | Use `EPHEMERAL=false` |
      | `docker: not found` | Docker not installed | Install Docker or the job doesn't need it |
      | `Permission denied` on Docker socket | Runner user not in docker group | Add user to docker group or use root |
      | `cd /home/user/path` fails inside Docker container | Runner can't see host paths | Write deploy configs inline; use Docker socket only |
      | Runner offline >14 days | Auto-removed by GitHub | Register a new runner |
      | GHCR pull "unauthorized" | No Docker registry auth in deploy job | Add `docker/login-action@v4` with `secrets.GITHUB_TOKEN` |
      
      ## Removing a Runner
      
      **If you have access to the runner machine:**
      ```bash
      # Run the removal command shown in GitHub UI
      ./config.sh remove --token <token>
      ```
      
      **If you don't have access:** Use Force remove in the GitHub UI.
      
      **To re-register without re-downloading:** Delete the `.runner` file in the runner directory. Runner can then be re-configured.
      
      ## Runner Software Updates
      
      - **Default**: Self-update enabled — runner auto-updates when a job is assigned or within 1 week
      - **Disabled**: `--disableupdate` flag — you manage updates via container image
      - **30-day window**: If disabled, runner must be updated within 30 days or GitHub stops assigning jobs
      - **Critical security updates**: Immediately block jobs until updated
      - **Recommendation for Docker runners**: Set `DISABLE_AUTO_UPDATE=1` and update the container image tag instead
      
      Check latest release: https://github.com/actions/runner/releases
      
      ## Runner Lifecycle Hooks
      
      Runner lifecycle hooks let you run scripts before and after each job using environment variables.
      
      | Hook | When it runs | Use cases |
      |------|-------------|----------|
      | `ACTIONS_RUNNER_HOOK_JOB_STARTED` | After job assignment, before any workflow steps | Workspace cleanup, secret injection, telemetry start |
      | `ACTIONS_RUNNER_HOOK_JOB_COMPLETED` | After all workflow steps, before job completion | Teardown, log shipping, metric collection |
      
      **Setup:**
      1. Create a script (`.sh` or `.ps1`) anywhere on the runner machine
      2. Make it executable: `chmod +x /path/to/script.sh`
      3. Set the env var in the runner's `.env` file, systemd unit, or Docker environment
      
      **Docker example:**
      ```yaml
      environment:
        - ACTIONS_RUNNER_HOOK_JOB_STARTED=/opt/runner/workspace-cleanup.sh
      ```
      
      **Cleanup script example:**
      ```bash
      #!/bin/bash
      # Cleans workspace before each job
      WORKSPACE="${RUNNER_WORKSPACE:-${GITHUB_WORKSPACE:-/home/runner/_work}}"
      rm -rf "${WORKSPACE:?}"/* || true
      ```
      
      **Testing:** Run a job and check workflow logs for a "Set up runner" section containing "Hook output:". Non-zero exit from JOB_STARTED prevents the job from running.
      
      **Note:** JOB_COMPLETED runs synchronously during job cleanup — it cannot be used to destroy the runner itself. Use `--ephemeral` for that.
      
      ## Common Pitfalls
      
      1. **ACCESS_TOKEN vs RUNNER_TOKEN** — most common failure mode
      2. **EPHEMERAL=false** as string, not `0` — bash truthiness trap
      3. **Missing RUNNER_GROUP** — fails to register if group doesn't exist
      4. **Volume cleanup wipes credentials** — `docker compose down -v` removes named volumes; ACCESS_TOKEN auto-recovers
      5. **Docker container filesystem** — runner cannot see host paths
      6. **Ubuntu 20.04 Python 3.8** — `dict | None` syntax fails; use `from __future__ import annotations`
      7. **Hugo modules need Go** — vendor modules or add `actions/setup-go@v5`
      
    • network.md 3.7 KB
      # Network & Connectivity
      
      ## Communication Model
      
      Self-hosted runners connect to GitHub via **outbound HTTPS (port 443) only** — no inbound ports needed.
      
      They poll for jobs via **HTTPS long-poll connections** to `*.actions.githubusercontent.com`.
      
      **Minimum bandwidth:** 70 kbps upload and download.
      
      ## Required Domains
      
      ### Essential Operations
      ```
      github.com
      api.github.com
      *.actions.githubusercontent.com
      ```
      
      ### Downloading Actions
      ```
      codeload.github.com
      ```
      
      ### Uploading/Downloading Artifacts, Logs, Caches, Summaries
      ```
      results-receiver.actions.githubusercontent.com
      *.blob.core.windows.net
      ```
      
      ### Runner Version Updates
      ```
      objects.githubusercontent.com
      objects-origin.githubusercontent.com
      github-releases.githubusercontent.com
      github-registry-files.githubusercontent.com
      ```
      
      ### OIDC Token Retrieval
      ```
      *.actions.githubusercontent.com
      ```
      
      ### GitHub Packages (Container Registry, etc.)
      ```
      *.pkg.github.com
      pkg-containers.githubusercontent.com
      ghcr.io
      ```
      
      ### Git LFS
      ```
      github-cloud.githubusercontent.com
      github-cloud.s3.amazonaws.com
      ```
      
      ### Dependabot Update Jobs
      ```
      dependabot-actions.githubapp.com
      ```
      
      **Runner group access for Dependabot:** If Dependabot PRs aren't triggering CI on self-hosted runners, check two settings:
      1. **Org/repo setting:** Settings → Actions → General → "Allow Dependabot to use self-hosted runners"
      2. **Runner group API:** If using groups, enable via:
         ```bash
         gh api --method PATCH orgs/<org>/actions/runner-groups/<group-id> \
           -f allows_dependabot=true
         ```
      
      ### Release Assets
      ```
      release-assets.githubusercontent.com
      ```
      
      ## TLS Verification
      
      - **Enabled by default** — the runner verifies GitHub's TLS certificate
      - **Disable for testing only**: Set `GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY=1`
      - **Better approach**: Install GitHub's certificate in the OS trust store
      
      ## Firewall Configuration
      
      For strict egress rules:
      - Allow outbound HTTPS (443) to all domains listed above
      - Some domains use CNAME records — firewalls may need recursive CNAME resolution
      - Note: CNAME records may change; the listed domains are stable
      
      ## IP Allow Lists
      
      If your GitHub organization or enterprise uses IP allow lists, you must add your self-hosted runner's IP address to the allow list. Without this, the runner cannot communicate with GitHub APIs.
      
      ## Proxy Configuration
      
      Self-hosted runners support standard HTTP proxy environment variables:
      - `HTTP_PROXY`
      - `HTTPS_PROXY`
      - `NO_PROXY`
      
      Set these in the service environment or Docker container environment.
      
      ## Connectivity Diagram
      
      ```
      ┌──────────────────┐     Outbound HTTPS (443)     ┌──────────────────────┐
      │  Self-Hosted     │ ────────────────────────────> │  GitHub Actions      │
      │  Runner          │ <──────────────────────────── │  Service             │
      │  (Docker/VM)     │   Long-poll for jobs          │  *.actions.github.   │
      │                  │   Upload logs & artifacts     │  com / api.github.   │
      │                  │   Download action code         │  com / blob.core.    │
      └──────────────────┘                               │  windows.net / CDN   │
              │                                          └──────────────────────┘
              │ Mounted Docker socket (if using DinD)
              ▼
      ┌──────────────────┐
      │  Host Docker      │
      │  Daemon           │
      │  (builds, runs)   │
      └──────────────────┘
      ```
      
    • scaling.md 3.3 KB
      # Autoscaling
      
      Four approaches to autoscaling self-hosted runners.
      
      ## 1. Actions Runner Controller (ARC) — Reference Implementation
      
      ARC is GitHub's recommended Kubernetes-based autoscaling solution.
      
      **How it scales:**
      1. Runner ScaleSet Listener holds HTTPS long-poll connection to GitHub Actions Service
      2. When a job matches the scale set's labels, the listener receives a "Job Available" message
      3. The listener checks if it can scale up (within configured max limits)
      4. If yes, it acknowledges and patches the EphemeralRunnerSet to increase replica count
      5. EphemeralRunner Controller creates runner pods with JIT tokens
      6. Each pod runs one job as ephemeral runner, then is deleted
      7. Idle runners are scaled down when no jobs are queued
      
      **Helm chart configuration controls:**
      - `minReplicas` / `maxReplicas` — scaling boundaries
      - `scaleDownDelaySecondsAfterScaleUp` — cooldown timer
      - `scaleUpAdjustment` / `scaleDownAdjustment` — scaling step size
      - `scaleDownDelaySeconds` — idle timeout before scale down
      
      ## 2. GitHub Actions Runner Scale Set Client
      
      Standalone Go module for custom autoscaling outside Kubernetes.
      
      **Use when:**
      - You need VM-based autoscaling (AWS EC2, Azure VMSS, GCP)
      - You have on-premise infrastructure
      - You need multi-platform support (Windows, Linux, macOS)
      - ARC's Kubernetes dependency is not a fit
      
      The client handles GitHub API interactions for scale sets. You write the infrastructure provisioning layer that creates and destroys runner instances.
      
      **Repository:** `actions/scaleset` on GitHub
      
      ## 3. Webhook-Driven Autoscaling
      
      Use the `workflow_job` webhook to detect job lifecycle events:
      
      | Event Action | Scaling Action |
      |--------------|----------------|
      | `workflow_job` with `action: queued` | Scale up — deploy new runner |
      | `workflow_job` with `action: completed` | Scale down — remove idle runners |
      
      **Considerations:**
      - Webhook delivery is not guaranteed timely — can introduce delays
      - For larger volumes, use ARC or Scale Set Client instead
      - Requires building and maintaining custom automation
      
      ## 4. Ephemeral Runner Pattern (Simple Deployments)
      
      For Docker Compose or script-based setups:
      
      1. Listen for `workflow_job` webhooks at org/repo level
      2. When jobs queue, deploy new ephemeral runner containers
      3. Each container runs with `--ephemeral` flag
      4. After one job, runner deregisters and container exits
      5. Cleanup process removes exited containers and prunes credentials
      
      **Not recommended** for persistent runner autoscaling — GitHub cannot guarantee jobs aren't assigned to runners being shut down.
      
      ## Scaling Recommendations
      
      | Scale | Approach | Complexity | Efficiency |
      |-------|----------|------------|------------|
      | 1-5 runners | Static Docker Compose | Low | Good |
      | 5-50 runners | ARC (K8s) or Scale Set Client | High | Best |
      | 50+ runners | ARC (K8s) | High | Best |
      | Mixed platform | Scale Set Client | High | Best |
      | PoC / low budget | Webhook + Docker | Medium | Moderate |
      
      ## Ephemeral vs Persistent
      
      | Aspect | Persistent | Ephemeral |
      |--------|------------|-----------|
      | Job isolation | Low — shared environment | High — clean per job |
      | Auto-scaling | NOT recommended | Recommended |
      | Deregistration | Manual / auto after 14d offline | Auto after 1 job |
      | Log retention | On-disk in `_diag/` | Must forward externally |
      | Setup complexity | Lower | Higher (need provisioning) |
      
    • security.md 4.1 KB
      # Security Hardening
      
      Self-hosted runners are fundamentally less secure than GitHub-hosted runners because they lack ephemeral, clean-slate VM isolation.
      
      **Core rule:** Self-hosted runners should almost never be used on public repositories. Forks can execute arbitrary code on your runner infrastructure.
      
      ## Mitigation Strategies
      
      ### 1. Runner Groups (Access Boundaries)
      
      Group runners at the org level and restrict which repos can access them. This limits blast radius if a runner is compromised.
      
      ```bash
      # Check existing groups
      gh api orgs/<org>/actions/runner-groups --jq '.runner_groups[].name'
      
      # Register runner into a specific group
      ./config.sh --url <url> --token <token> --runnergroup <group-name>
      ```
      
      **Key points:**
      - Default group allows all repos in the org — highest risk configuration
      - Create custom groups with restricted repository access
      - Public repository access is blocked by default per group (can be overridden)
      - Move runners between groups in the GitHub UI
      
      ### 2. Ephemeral Runners
      
      Ephemeral runners execute at most one job, then deregister automatically.
      
      ```bash
      ./config.sh --url <url> --token <token> --ephemeral
      ```
      
      - Each job gets a clean environment (if provisioning creates one)
      - Logs must be forwarded externally — they're lost when the runner disappears
      - GitHub guarantees only one job per ephemeral runner (cannot guarantee for persistent runners)
      - Requires automation to provision clean environments
      
      ### 3. Just-in-Time (JIT) Runners
      
      Create ephemeral runner configurations via the REST API — no long-lived registration tokens needed.
      
      ```bash
      # Generate JIT config
      curl -X POST https://api.github.com/orgs/<org>/actions/runners/generate-jitconfig \
        -H "Authorization: Bearer <token>" \
        -d '{"name":"jit-runner","runner_group_id":1,"labels":["self-hosted","linux","x64"]}'
      
      # Use the config at startup
      ./run.sh --jitconfig <encoded_jit_config>
      ```
      
      - Runner runs one job, then is automatically removed by GitHub
      - Use automation to ensure a clean environment per JIT run
      
      ### 4. Secrets Management
      
      - Use `GITHUB_TOKEN` with minimum required permissions
      - Never store structured data (JSON, XML, YAML) as a single secret
      - Register generated secrets with `::add-mask::VALUE` so they're redacted from logs
      - Rotate secrets periodically
      - Use environment-level required reviewers for sensitive secrets
      - Consider OpenID Connect (OIDC) for cloud resource auth instead of long-lived secrets
      
      ### 5. Script Injection Mitigation
      
      - Prefer actions over inline scripts when handling user-supplied values
      - Use intermediate environment variables for safe untrusted input:
      
      ```yaml
      - name: Check PR title
        env:
          TITLE: ${{ github.event.pull_request.title }}
        run: |
          if [[ "$TITLE" =~ ^octocat ]]; then
            echo "PR title starts with 'octocat'"
          fi
      ```
      
      - Avoid `pull_request_target` trigger unless absolutely necessary
      
      ### 6. Third-Party Action Security
      
      - Pin actions to a full-length commit SHA (immutable)
      - Audit action source code before using it
      - Pin to tags only when you trust the verified creator
      - Use Dependabot to keep actions updated and receive vulnerability alerts
      - Use OpenSSF Scorecards to flag risky practices
      - Use dependency review to screen new/changed workflow dependencies
      
      ### 7. Workflow-Level Hardening
      
      - Prevent Actions from creating or approving PRs (org/repo setting)
      - Use CODEOWNERS to require review on `.github/workflows/` changes
      - Enable code scanning (CodeQL) with GitHub Actions scanning
      - Audit Actions events via security log and audit log
      
      ### 8. Runner Machine Hardening
      
      - Keep sensitive data off the runner machine (SSH keys, API tokens, internal network access)
      - Use OIDC instead of long-lived cloud credentials
      - Consider clean VM/container per job execution for sensitive workflows
      - Review the GitHub Advisory Database (`ecosystem:actions`) for vulnerabilities
      
      ## Summary
      
      1. **Never** use self-hosted runners for public repositories
      2. **Isolate** runners by group — restrict repo access
      3. **Prefer ephemeral/JIT** runners for workloads needing isolation
      4. **Pin actions** to commit SHAs, not tags
      5. **Use ACCESS_TOKEN** with minimum scopes, not RUNNER_TOKEN with admin scopes
      
  • templates
    • custom-runner.Dockerfile 1.1 KB · in bundle
    • docker-compose.yml 962 B
      services:
        runner:
          image: myoung34/github-runner:latest
          container_name: runner
          restart: unless-stopped
          environment:
            # Required - change these for your environment
            - RUNNER_NAME=my-runner
            - RUNNER_SCOPE=org                          # or "repo"
            - ORG_NAME=myorg                            # required for org scope
            # - REPO_URL=https://github.com/owner/repo  # required for repo scope
            - ACCESS_TOKEN=***      
            # Optional
            - RUNNER_GROUP=self-hosted                  # must match an existing group
            - LABELS=self-hosted,linux,x64
            - RUNNER_WORKDIR=/runner/work
            - DISABLE_AUTO_UPDATE=1
            - EPHEMERAL=false                           # use string "false", not "0"
          volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - runner-data:/runner
      
      volumes:
        runner-data:
      
      # Uncomment if you need a shared network
      # networks:
      #   default:
      #     external:
      #       name: traefik
      
  • README.md 1.4 KB
    # Self-Hosted GitHub Actions Runners
    
    Deploy, manage, and troubleshoot self-hosted GitHub Actions runners. Covers systemd, Docker, Kubernetes (ARC), and Scale Set Client deployments.
    
    ## Why Install This Skill
    
    When your agent loads this skill, it becomes a **CI/CD infrastructure engineer** who can:
    
    - **Choose the right deployment** — systemd for single machines, Docker for homelabs, ARC for Kubernetes teams
    - **Troubleshoot registration failures** — the critical ACCESS_TOKEN vs RUNNER_TOKEN distinction
    - **Design autoscaling** — ARC, Scale Set Client, ephemeral runners
    - **Harden runner security** — public repo risks, JIT tokens, runner groups
    - **Build custom runner images** — Dockerfiles with custom toolchain, Python, Ruby
    - **Solve network issues** — firewall rules, TLS, proxy configuration
    
    ## What You Get
    
    | Directory | Purpose |
    |-----------|---------|
    | `SKILL.md` | Deployment spectrum, quick reference, label strategy |
    | `references/` | 7 reference files: deployment, management, scaling, security, custom images, network, monitoring |
    
    ## Quick Start
    
    Start with the setup and first workflow in SKILL.md, then use the linked resources for the specific task you need to complete.
    
    ## Triggers
    
    Load this when setting up CI, troubleshooting runner registration failures, designing autoscaling, or hardening runner security.
    
    ## Requirements
    
    Linux, macOS, or Windows target hosts. Docker for containerized runners. Kubernetes for ARC deployments.
    
  • SKILL.md 6.9 KB
    ---
    name: github-runner
    description: >-
      Deploy, manage, and troubleshoot self-hosted GitHub Actions runners. Covers systemd
      service, Docker containers, Kubernetes (Actions Runner Controller), and the Scale Set
      Client. Use when setting up a CI runner, debugging registration failures, designing
      autoscaling, or hardening runner security. Do not use this skill for unrelated requests;
      route to the nearest named specialist.
    license: MIT
    compatibility: Linux, macOS, or Windows target hosts. Docker for containerized runners.
      Kubernetes for ARC deployments.
    metadata:
      tags: github-actions, ci-cd, runners, devops, docker, kubernetes, autoscaling
      source: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners
      version: 1.0.2
    ---
    
    # Self-Hosted GitHub Actions Runner
    
    Deploy and manage self-hosted GitHub Actions runners — the machines that execute CI/CD workflow jobs. Self-hosted runners give you control over hardware, OS, and toolchain, at the cost of requiring you to maintain the environment.
    
    ## When to Use
    
    | Trigger | What to do |
    |---------|------------|
    | "Set up CI for my project", "deploy a self-hosted runner for [repo/org]" | Read [deployment](references/deployment.md) — choose systemd, Docker, or ARC |
    | "Runner won't register / keeps failing", "runner gets 404", "registration token expired" | Read [management](references/management.md) — ACCESS_TOKEN vs RUNNER_TOKEN, groups |
    | "How to scale runners automatically", "auto-scale runners", "too many queued jobs" | Read [scaling](references/scaling.md) — ARC, Scale Set Client, ephemeral |
    | "Secure my self-hosted runners", "hardening runners", "runner security", "public repo risk" | Read [security](references/security.md) — public repo risks, ephemeral, JIT, groups |
    | "Make a custom runner image", "build a runner Dockerfile", "custom runner with Python" | Read [custom-images](references/custom-images.md) — Dockerfile, ARC container modes |
    | "What domains does a runner need to reach?", "runner firewall rules", "runner network setup" | Read [network](references/network.md) — firewall rules, TLS, proxy |
    | "Labels, groups, or both for routing?", "how to target specific runners" | Read [management](references/management.md) — labels and groups sections |
    | "Monitor / troubleshoot runner issues", "runner offline", "runner not picking up jobs" | Read [management](references/management.md) — monitoring and troubleshooting sections |
    
    ## Quick Reference
    
    ### Deployment Spectrum
    
    | Approach | Complexity | Autoscaling | Best For |
    |----------|------------|-------------|----------|
    | systemd service | Low | Manual | Single machine, simple CI |
    | Docker container | Medium | Manual replicas | Homelab, small team |
    | ARC (Kubernetes) | High | Built-in | Teams with K8s expertise |
    | Scale Set Client | High | Custom | Non-K8s platform teams |
    
    ### Critical: ACCESS_TOKEN vs RUNNER_TOKEN
    
    This is the most common setup failure. The `myoung34/github-runner` Docker entrypoint **unexports `RUNNER_TOKEN`** at startup — it's only used for **de-registration**. Registration requires `ACCESS_TOKEN` (a GitHub PAT).
    
    | Token | Purpose | Expiry |
    |-------|---------|--------|
    | `ACCESS_TOKEN` | Registration — generates fresh tokens via GitHub API | Long-lived (PAT) |
    | `RUNNER_TOKEN` | **De-registration only** — NOT for initial registration | 60 min |
    
    **PAT scopes:**
    - Repo-level: `repo`
    - Org-level: `admin:org`
    - Enterprise-level: `manage_runners:enterprise`
    
    ### Labels Strategy
    
    Default labels: `self-hosted` + OS (`linux`/`windows`/`macOS`) + arch (`x64`/`ARM`/`ARM32`/`ARM64`)
    
    Common convention: `self-hosted,<hostname>,<os>,<arch>,<project>`. In workflows:
    
    ```yaml
    runs-on: [self-hosted, linux, x64, gpu]
    ```
    
    All labels must match (AND logic). Use `--no-default-labels` to strip OS/arch auto-labels.
    
    ### Key Pitfalls
    
    | Problem | Cause | Fix |
    |---------|-------|-----|
    | 404 on POST to runner-registration | `RUNNER_TOKEN` used instead of `ACCESS_TOKEN` | Switch to `ACCESS_TOKEN` with PAT |
    | "Could not find any self-hosted runner group named 'Default'" | Org uses different group name | Check groups via `gh api`, set `RUNNER_GROUP` |
    | "Ephemeral option is enabled" when not wanted | `EPHEMERAL=0` — truthy in bash | Use `EPHEMERAL=false` (string) |
    | `docker compose down -v` wipes credentials | Named volumes deleted | With `ACCESS_TOKEN`, auto-recovers |
    | Runner can't see host filesystem paths | Runner runs inside Docker container | Write deploy configs inline in workflow |
    | Hugo build: "Go not found" | No Go on Ubuntu 20.04 runner | `hugo mod vendor` and commit `_vendor/` |
    | GHCR pull "unauthorized" | No Docker registry auth in deploy job | Add `docker/login-action@v4` |
    | Runner offline >14 days | Auto-removed by GitHub | Register a new runner |
    
    ## Sequential Workflow
    
    ### 1. Choose deployment approach
    
    Read [deployment](references/deployment.md) and select systemd, Docker, ARC, or Scale Set Client.
    
    ### 2. Figure out what runner scope you need
    
    - **Repo-level**: Runner scoped to a single repo — you need admin access
    - **Org-level**: Runner shared across repos in an org — you need org owner access
    - **Enterprise-level**: Runner shared across orgs in an enterprise — you need enterprise access
    
    ### 3. Register the runner
    
    Read the [architecture](references/architecture.md) reference for the registration flow.
    
    ### 4. Route jobs to the runner
    
    Use `runs-on` with labels and optionally groups. Read [management](references/management.md) labels section.
    
    ### 5. Monitor and troubleshoot
    
    Read the [management](references/management.md) troubleshooting section when things go wrong.
    
    ### 6. Plan for security and scaling
    
    Read [security](references/security.md) and [scaling](references/scaling.md) for production deployments.
    
    ## Templates
    
    - [templates/docker-compose.yml](templates/docker-compose.yml) — Docker runner with `myoung34/github-runner`
    - [templates/custom-runner.Dockerfile](templates/custom-runner.Dockerfile) — Custom runner image for ARC
    
    ## Reference Files
    
    | File | Load when |
    |------|-----------|
    | [references/architecture.md](references/architecture.md) | You need to understand registration flow, job lifecycle, or runner communication |
    | [references/deployment.md](references/deployment.md) | You need to deploy a runner — systemd, Docker, ARC, or Scale Set Client |
    | [references/security.md](references/security.md) | You need to harden runners, set up ephemeral/JIT, configure groups |
    | [references/scaling.md](references/scaling.md) | You need autoscaling — ARC, Scale Set Client, or webhook-driven |
    | [references/management.md](references/management.md) | You need groups, labels, monitoring, troubleshooting, or cleanup |
    | [references/custom-images.md](references/custom-images.md) | You need a custom runner Dockerfile, ARC Kubernetes mode |
    | [references/network.md](references/network.md) | You need firewall rules, proxy config, or are troubleshooting connectivity |
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related