CI runners standard
Standard scope
Applies to: all HCS-governed repos whose CI (Continuous Integration) pipelines run on a self-hosted agent, on GitHub Actions or GitLab CI Domain: ci-runners Status: Active
This is the single source of truth for the registered CI runner fleet: every GitHub Actions self-hosted runner and every GitLab CI runner HCS operates, on Linux and Windows, plus the planned macOS activation. If you need to know what agent label to put in a workflow or pipeline file, the answer is here.
GitLab runners exist for TierPoint client work only (
tierpoint/prodtech) — see the platform-boundary note in Governance. Every HCS-owned repo uses the GitHub runners below; the GitLab rows in this doc do not apply to HCS-owned projects.
Runner vs. build environment
A runner is a CI agent registered with GitHub or GitLab that picks up jobs automatically when a workflow or pipeline triggers — you never log into it directly, you only target it by label or tag. A build environment is a host you invoke yourself, by name, outside of CI (WSL, bld-01) — there is no registration, no queue, and no automatic dispatch. If a job is defined in .github/workflows/ or .gitlab-ci.yml and picked up by an agent, it runs on a runner (this standard). If you are running a script by hand or from an orchestrating pipeline step that calls out to a named host, it runs on a build environment (see Build Environments).
The five runner identities
| # | Runner | Compute | Scaling | Labels / tags |
|---|---|---|---|---|
| 1 | GitHub Linux | Azure Container Apps (ACA) Job, one per active GitHub org | KEDA (Kubernetes Event-driven Autoscaling) github-runner scaler, minExecutions: 0 | self-hosted,linux,x64,hcs |
| 2 | GitLab Linux | ACA App, minReplicas: 1 | Always-on — see below for why | hcs,linux,x64 |
| 3 | GitHub Windows | Shared Spot Virtual Machine Scale Set (VMSS) | Scale-to-zero, capacity 0 at rest | self-hosted,windows,x64,hcs |
| 4 | GitLab Windows | Same VMSS as #3 — second agent registered on the same instance | Same as #3 | hcs,windows,x64 |
| 5 | macOS | AWS EC2 Mac Apple Silicon Dedicated Host; family selected by live discovery | Interactive M4 host deployed; CI registration disabled | self-hosted,macos,arm64,hcs |
GitLab Linux runs always-on, not scale-to-zero, because KEDA has no gitlab-runner scaler. The KEDA scaler catalog covers github-runner, azure-pipelines, and forgejo — GitLab CI has no equivalent, so queue-depth-triggered autoscaling is not available for GitLab the way it is for GitHub. This is a deliberate, documented exception, not an oversight: the GitLab Linux runner is provisioned as an always-on ACA App (minReplicas: 1) at a small fixed size instead.
Why Windows runners are VM-based and Linux runners are containers
Azure Container Apps is Linux-only. Windows containers are not supported and are not on Microsoft's roadmap for the service. This is the hard architectural fork driving the whole fleet design: every Linux runner identity above runs as an ACA Job or ACA App, and every Windows runner identity runs on a Virtual Machine Scale Set instead, because there is no containerized alternative.
Why registration is per-org, not shared across the estate
Every GitHub org in the HCS estate is on the GitHub Free plan — there is no GitHub Enterprise account anywhere. Enterprise runner groups, which let one runner serve multiple orgs, require an Enterprise account and therefore don't exist here. On the Free plan, a self-hosted runner is registered to exactly one org (or one repo). This means the runner platform is shared, but runner registrations are not: one VNet (Virtual Network), one Container Apps Environment, and one user-assigned managed identity serve the entire fleet, but on top of that shared base, each active GitHub org gets its own GitHub Linux runner job, and Windows runners are shared per-platform (one GitHub agent, one GitLab agent) rather than per-org.
Targeting a runner from workflow or pipeline YAML
GitHub Actions — Linux
jobs:
build:
runs-on: [self-hosted, linux, x64, hcs]
steps:
- uses: actions/checkout@v4
- run: make buildGitHub Actions — Windows
jobs:
build:
runs-on: [self-hosted, windows, x64, hcs]
steps:
- uses: actions/checkout@v4
- run: .\build.ps1GitLab CI — Linux
build:
tags:
- hcs
- linux
- x64
script:
- make buildGitLab CI — Windows
build:
tags:
- hcs
- windows
- x64
script:
- .\build.ps1Never use ubuntu-latest or windows-latest (GitHub-hosted runners) unless the pipeline genuinely cannot use the HCS self-hosted fleet — GitHub-hosted minutes are metered and unnecessary for work the self-hosted fleet already covers.
Registration and token rotation
Runners are registered and their tokens rotated by .ado/cicd/Update-RunnerRegistration.ps1. Do not register or de-register a runner by hand through the GitHub or GitLab UI — always go through this script so the registration state matches what's declared in Bicep.
Registration tokens and credentials live in kv-hcs-vault-01, never inline in YAML or Bicep:
| Secret | Purpose |
|---|---|
hcs-gh-runner-pat (or a per-org GitHub App installation token equivalent) | GitHub Actions runner registration |
hcs-gitlab-runner-token | GitLab CI runner registration |
hcs-winbuild-vm-password (or its .ado/cicd/ equivalent) | Windows VMSS instance administrator credential |
See Key Vault for the secret-handling rules that apply to all three.
Cost model
| Runner | Before | Target |
|---|---|---|
| GitLab Linux | ~$30/mo (always-on B2s VM) | ~$4–14/mo (ACA App, 0.25 vCPU always-on) |
| GitHub Linux | ~$0 (already scale-to-zero) | ~$0–3/mo (ACA free grant covers most usage) |
| Windows (shared) | n/a | ~$0 idle; ~$0.041/hr Spot while running |
macOS — interactive host active, CI registration pending
The first real Apple application requirement has activated the design and quota-discovery work. The ProdTech interactive M4 build host has passed account, Region, Availability Zone, price, quota, and physical-capacity gates. It remains unregistered as a CI runner until the permitted repositories and signing trust boundary are approved.
Rules for the macOS tier:
- Use an Apple Silicon EC2 Mac family compatible with the required Xcode and macOS versions. Do not hard-code the retired
mac2.metalplaceholder as the permanent choice. - EC2 Mac runs one instance per Dedicated Host. AWS enforces a 24-hour minimum host allocation; stopping the instance does not stop host billing.
- Query live per-family, per-Region Dedicated Host quotas and prices before each first allocation. Published examples are not cost approvals.
- Human operators authenticate with IAM Identity Center. The host uses an IAM instance profile. CI assumes an IAM role through workload federation; no AWS access key is stored in Key Vault.
- Management and GUI tunnels use AWS Systems Manager. The security group has no public inbound SSH or Screen Sharing rule.
- Register the runner only for trusted repositories and protected refs. Do not execute untrusted pull-request code on an interactive Mac that also holds Apple signing material.
- The consuming scope owns account aliases, SSO URLs, IAM roles, quota requests, and deployment state. The base standard contains no tenant-specific AWS identifiers.
Infra location
D:\git\platform\.ado\cicd\
Related standards
- Automation — pipeline design: triggers, secrets, gates. Governs when/what a pipeline does; this standard governs what it runs on.
- Build Environments — hosts you invoke directly (WSL,
bld-01), not CI-registered runners. Governs manual build hosts, not the CI fleet. - Key Vault — secret-handling rules for the registration tokens and credentials referenced above.