Choosing how to run self-hosted CI¶
One question decides most of this: must the compute be yours, and do several teams have to share it? If either half is no, something simpler than GitHub Actions Gateway (GAG) is probably right.
When not to choose GAG¶
| If | Use | Not GAG, because |
|---|---|---|
| A vendor can run your jobs | a managed runner service (Blacksmith, Namespace, Depot, WarpBuild, Ubicloud) | they compete on build speed and price per minute; GAG does not (D.14) |
| Managed Kubernetes is cheap and your CI fits one cloud | a cluster per team, in its own project | a project boundary isolates harder than any shared cluster, and costs an API call |
| One team owns the cluster and the runners | ARC | GAG speaks the same protocol through the same client library. This is not a protocol argument (D.3) |
| Your compute is elastic cloud capacity, no cluster wanted | RunsOn, terraform-aws-github-runner, Actuated | a VM per job isolates without any namespace argument (D.11) |
GAG is for what is left
The nodes are big and expensive, several teams have to share them, and that sharing has to be safe.
Location, location, location¶
Where each option can actually run is the fastest filter, and most comparisons omit it: "self-hosted" usually means "self-hosted on AWS".
| Option | Runs on |
|---|---|
| Managed runner services | the vendor's infrastructure |
| RunsOn, terraform-aws-github-runner, ForgeMT | AWS only |
| Actuated | your hardware, vendor-hosted control plane |
| ARC, GAG | any conformant Kubernetes cluster, including on-premises and air-gapped |
On-premises, most of this list is already gone
Only the last row is left, ruled out by the same constraint that made you self-host in the first place, before a single feature is compared.
That also breaks account-per-tenant isolation, which is ForgeMT's model and the serious argument against a shared cluster: an AWS account is a free API call, but its bare-metal equivalent is a hardware purchase, and partitioned capacity cannot be shifted between tenants. Appendix D.9 takes it seriously and states both trade-offs.
Multi-tenant platforms are an order of magnitude more complex¶
Adding teams does not add a feature, it adds roles. Three end up in the room, each with different powers and a different blast radius, and holding them apart is most of the work. ARC models the first two as one person, which is coherent for a single-owner cluster and is why it has no primitive separating them.
| The boundary | How GAG draws it |
|---|---|
| Platform sets a cap the tenant cannot raise | ResourceQuota is platform-owned; the controller has no write verb on it |
| Platform grants privilege, tenant cannot self-grant | Pod Security Admission is a namespace label; privileged shapes come from a platform ClusterRunnerTemplate |
| Platform bounds priority, tenant composes within it | priorityTiers draws from a platform allowlist, editable without a restart |
| Tenant self-serves without cluster-admin | one ActionsGateway provisions controller, proxy, RBAC and policies inside the quota |
| Tenant sees its own data only | metrics and a dashboard per tenant, plus a separate platform view |
| Contributor's code is contained | default-deny egress, per-tenant egress identity, Kata workers |
Each role and what it cannot do: Personas.
A paved road is worth more than a trail map¶
- Feature comparisons tell you what can be done.
- Reference architectures prove it has been validated, and tell you how to do it.
- Runbooks, dashboards, and alerts tell you how to operate it.
Measured 2026-08-06 against ARC 0.14.2 and master.
| GAG | ARC | |
|---|---|---|
| Sandboxed workers | 500-line guide, default in GAG's own end-to-end CI, validated on named kernels, no privileged container | same runtimeClassName field; no doc covers it, absent from chart values, two closed issues ever |
| Observability | 20 alert rules, two dashboards for two audiences, per-tenant metrics, redaction before any log line | one per-scale-set sample dashboard, no alert rules, metrics opt-in; dashboard request open since 2025-01-13 |
No offering is perfect, yet…¶
Here is where GAG loses, and to whom. No single alternative holds all of these, which is why the middle column matters as much as the first.
| What GAG lacks | Who has it | Tracked |
|---|---|---|
| Install base | ARC 6,417 stars, ForgeMT 211, GAG 3 (2026-08-06) | see below |
| Commercial support | ARC, and every managed service | none planned, by design |
Multi-label runs-on |
ARC, since 0.14.0 (2026-03-19) | Q726 |
container:/services: without privilege |
ARC, via containerMode: kubernetes |
Q727 |
| In-cluster cache | GitLab Runner, managed services. Not ARC | worker cache backend |
| GHES tested on a real appliance | ARC | flagged untested; needs an operator with one |
| Bound GitHub runner group | ARC, via runnerGroup |
Q712, gating the next release |
| Default-tier latency metrics | nobody: a defect, not a rival's feature | Q713, gating the next release |
Two footnotes on that table.
Cache is usually misread. actions/cache works on GAG today; what is
missing is a cache inside the cluster. ARC has none either, so this is not a
reason to prefer ARC.
Install base has no engineering answer. If being the first production deployment is unacceptable, that settles it. The only counterweight is the evidence trail: dated measurements, failure modes documented with the incidents that found them, and a published retraction of the project's own best number once it proved unmeasurable.
Reading further¶
Appendix D argues every alternative in full, including Prow, which is why GAG does not claim automatic re-run is novel, and GitLab Runner, which faces the identical problem and resolves it the other way. Why GAG? is the capability-by-capability comparison against ARC.