Zero-idle self-hosted GitHub Actions runners for multi-tenant Kubernetes.
Warning
One required upgrade step, and nothing in this release is breaking. Apply the chart's CRDs before upgrading, or four new wire fields will not exist. Every other change here defaults to the previous behaviour. Read Upgrading below.
Highlights
Cross-namespace EgressProxy sharing, actually enforced. sharing.allowedNamespaces was served in the v2beta1 API with nothing honouring it, so an operator could set a field that did nothing. This release builds the path and guards it: a consent check on the providing EgressProxy, CA distribution, and dual-side NetworkPolicy. Absent or empty sharing denies, so the pre-existing same-namespace posture stays the default and the unset case (#1347).
A curated runner template library. Templates CI already validates ship as a kustomize base you can patch: a plain baseline plus the kata-dind and privileged-dind templates the dogfood e2e suite exercises. No new CRD. The constraint that only CI-exercised templates ship is a gate rather than a convention: make template-library-check reconciles the shipped and exercised sets both ways, and every entry is admitted by a real apiserver on each integration run (#1344).
An abandoned job's run is force-cancelled and re-run automatically, on both acquisition tiers. A worker reaped before its container ever started used to leave GitHub waiting out its ~15-minute unstarted-job timeout. The run is now cancelled in about a second, then re-run once the owner places a worker pod again. Shipped classic-only, then ported to the scale-set tier inside this same release, so no tag ever published the asymmetry (#1298, #1336, #1366).
Worker-capacity conditions are Prometheus gauges on v2 RunnerSets. Four new gauges mirror the conditions a v2 RunnerSet already reported, so quota pressure, unschedulable workers and a declined capacity gate are alertable without reading conditions. WorkerCapacityDeclined carries its reason as a label (#1200, #1225).
Note
Job duration and pod-creation latency are still classic-tier only. actions_gateway_job_duration_seconds and actions_gateway_pod_creation_latency_seconds are observed only on the classic acquisition path, so on a ScaleSet set, the default tier, they are absent, and the two ActionsGatewayPodCreationLatency alerts, four recording rules and their dashboard panels read blank. A silent latency alert there is a missing series, not a met SLO. This is not new in 1.4; it predates v1.3.0. Tracked as Q713.
Upgrading
One step, and it is the same step every upgrade of this chart needs. Full procedure in the upgrade guide, which documents rollback per component: GMC, AGC, proxy, and worker image.
1. Apply the chart's CRDs. Part of every upgrade of this chart, not a one-off: Helm installs the chart-root crds/ directory on a fresh install only.
helm show crds oci://ghcr.io/actions-gateway/charts/actions-gateway \
--version 1.4.0 | kubectl apply -f -Charts carry no leading v. Images are tagged v1.4.0, charts 1.4.0.
Three changes that need no action, but will be visible.
- An abandoned run is force-cancelled and re-run automatically — a job whose worker was reaped before it ran now concludes
cancelledin about a second instead of at GitHub's timeout, and is re-run when capacity returns. Both tiers (#1298, #1336, #1366). v1alpha1now warns on every apply — deprecated, not removed; see Deprecations below (#1199).allowedInfraPriorityClassesis new and needs no action unless you set it. ThePriorityClassAllowlistCR gained a second list for infrastructure classes, sourced from the watched CR so an admin can add one without a GMC rollout. Unset or empty forbids every named class, so the secure posture is the unset case (#1198).
Deprecations
Nothing is removed in this release. Both notices below point at v2.0.0, unchanged from v1.3.0 except that one of them now warns.
v1alpha1 is deprecated, and as of this release the apiserver warns on every apply (#1199). That closes the asymmetry v1.3.0's notes flagged: v2alpha1 warned and v1alpha1 did not, so nothing reminded an operator it was going away. Tenants still on v1alpha1 migrate with gag-migrate: Migrating a tenant to v2.
v2alpha1 and Classic are removed at v2.0.0 too. v2alpha1 has warned since v1.3.0. Moving to v2beta1 is a re-apply rather than a migration tool: Before you upgrade past v2.0.0.
Everything since v1.3.0
192 commits. These 16 change the shipped binaries and chart, and two more change the reference dashboards operators apply; build, CI, test, tooling, and docs work is left out, and is summarised under Project and tooling below.
New features (10)
Cross-namespace egress proxy sharing
- Enforce cross-namespace
EgressProxysharing, v2 API M4 (#1347)
- Ship a curated runner template library as a kustomize base (#1344)
- Force-cancel an abandoned job's run for a ~1s honest ending (#1298)
- Auto re-run a force-cancelled abandoned run when capacity returns (#1336)
- Port the force-cancel and auto re-run to the scale-set tier (#1366)
- Export v2
RunnerSetworker-capacity conditions as gauges (#1200) - Gauge
WorkerCapacityDeclinedwith its reason label (#1225)
- Source the infra PriorityClass allowlist from the watched CR (#1198)
- Make the apiserver warn on
v1alpha1, not just the docs (#1199) - Persist the scale-set conclusion guards across a hard kill (#1300)
Fixes (6)
Job conclusions and the scale-set listener
- A never-run job must not conclude its run green (#1277)
- A worker removed before it ran is reported abandoned, and its assignment released (#1197)
- Read the queue's outstanding conclusions on the way out (#1308)
- Bound the scale-set listener's replay guards to the queue (#1196)
DeleteMessagereports whether the wire deleted anything (#1193)
- Bound the reconcile retry backoff so a reap deadline survives an error (#1244)
Shipped dashboards (2)
The reference Grafana dashboards under deploy/monitoring/ are applied by operators, so changes to them are user-visible even though they are in no image.
- Populate the scale-set reap panel and gauge the capacity gate (#1303)
- Show the v2
RunnerSetcapacity gauges on the tenant dashboard (#1204)
API and metric surface
Four wire fields, one condition reason, and six metrics are published for the first time. No field, enum value, default, metric, or Event reason was removed or renamed, and no enum constraint or default changed. Field descriptions are in the generated API reference, which covers v2beta1; a deprecated-version field still needs kubectl explain.
New spec fields (4)
spec.allowedInfraPriorityClasses: the infrastructure-class allowlist, read from the watched CR. Unset or empty forbids every named class (#1198)
spec.proxyRef: theEgressProxythis set uses. Optional, because direct egress is a defined behaviour rather than a failure (#1347)
spec.defaultProxyRef: inherited only byRunnerSets that set noproxyRef, so the narrower field always wins (#1347)
ProxyObjectRef (carried by both refs above)
New condition reasons (1)
ProxyShareNotGranted: a cross-namespaceEgressProxyreference whose provider has not consented. Fails closed (#1347)
New metrics (6), and one label added to two existing ones
All prefixed actions_gateway_, listed here without it. Remedies and alerting guidance are in observability-metrics.
abandoned_run_force_cancels_total: force-cancels by tier and outcome (#1298, #1366)abandoned_run_rerun_waits_total: re-runs waiting on capacity, by how the wait ended (#1336, #1366)
runnerset_worker_quota_exceeded(#1200)runnerset_worker_quota_pressure(#1200)runnerset_workers_unschedulable(#1200)runnerset_worker_capacity_declined: carries its reason as a label (#1225)
One label added. Both abandoned-run counters gained a tier label (classic, scaleset) when the recovery was ported (#1366). An existing query that does not group by it keeps its meaning; one that aggregates across tiers now sums both. The shipped dashboards were updated to split on it.
No new Kubernetes Event reasons, and none removed: this release reuses the existing 24.
Validation
Both release candidates produced a passing verdict, and no leg failed in either. v1.4.0-rc.1 passed, then the scale-set port (#1366) and a docs sweep landed after its commit, so rc.1 stopped being the candidate: a candidate's pass does not transfer to a later one carrying new product code. v1.4.0-rc.2 was cut and validated in full.
Scoped to what the record supports: validate-release.sh landed the day v1.1.0 was tagged, so v1.0.0 and v1.1.0 predate it, and no plan doc records a run for v1.2.0. The 1.3 line is the only prior line with a recorded validation history, and it needed four candidates before one returned a verdict at all. So this is the first line in which no candidate failed a leg, not the first time that has ever happened.
74/74 e2e specs on live GKE, 62 ok and 0 failed: the four legs, and what none of them assert
Run against the signed release artifacts rather than a build (run), 26m59s end to end:
- The full e2e matrix on gateway-provisioned runners, 74/74 specs, 62 ok, 0 failed, 12 skipped
- Both sizing profiles actuating on real worker pods, with the derived value confirmed at the pod:
1500mwhere the templates asked for 2 and 3 CPU, andThroughputActiveon 176 samples - The signed v2 CRD artifact downloaded, signature-verified, applied, and all five CRDs registered
- Teardown back to zero nodes, confirmed against the compute API rather than the gate's own teardown line
That is what the gate asserts. It does not assert cross-namespace proxy sharing, the capacity gate, the namespace-quota rung, or anything GHES. The run targets github.com and builds an autoscaling cluster with headroom rather than a constrained one. Those rest on the unit, envtest, and e2e suites instead.
Artifact verification cleared separately: 7/7 OCI signatures across five images and both charts, the verify-blob signature on the v2 CRD manifest, and a build-provenance attestation whose signer URI ends publish.yml@refs/tags/v1.4.0-rc.2 over the tagged commit. Each green check was re-run against a deliberately wrong identity and each failed, so the passes discriminate rather than merely exiting 0.
Full changelog: v1.3.0...v1.4.0
Project and tooling
Two operator guides are new. The runner template library is linked from its feature above. Personas is reference material with no feature of its own: it names the five roles the operator docs are written for, so a page can say who it is for instead of assuming. Troubleshooting and security operations both grew substantially.
The rest is contributor-facing, listed because a release is also a claim about how the project is built.
Release, CI, and repository tooling
Release process
- Report the semver floor the merged work already forces (#1313)
- Narrow the semver floor past comment-only diffs (#1341)
- Reconcile the roadmap's release gates against the backlog labels (#1367)
The 1.4 pre-flight sweep
A deliberate pass over the marketing and operator surfaces before the tag, which is where several of this release's docs corrections came from. It found an executive summary promising OOM-killed jobs are re-run when the provisioner deliberately excludes them, a worst-case recovery figure quoted as the typical case, and two comparison rows that had gone false at datable upstream releases. It also found the abandoned-run docs disagreeing with each other about tier scope, which is what surfaced the gap #1366 then closed.
Security
No security advisory accompanies this release, and no fix here is embargoed or backported.
Two changes harden trust and isolation rather than patching a reported vulnerability. Cross-namespace EgressProxy sharing (#1347) fails closed on every axis: absent or empty sharing denies, the AGC cannot read a remote EgressProxy at all so the GMC mediates rather than widening tenant RBAC, and both sides get a NetworkPolicy. The infra PriorityClass allowlist (#1198) keeps its secure posture as the unset case.
Every pull request, with no path filter deliberately, runs govulncheck across the workspace and a Trivy scan of the images, and main re-runs both on every push.
Container images
Pin these immutable multi-arch index digests rather than the floating v1.4.0 tag. Each index serves both linux/amd64 and linux/arm64, so one pinned digest schedules on either.
- gmc:
ghcr.io/actions-gateway/gmc@sha256:6da59e421839b022b4605979b6295e535574ae0bc2be8420255eb4a8d27a7570 - agc:
ghcr.io/actions-gateway/agc@sha256:ecfbb75ec912a887cc1a9c640dab85a1b44fdbb9f01d071521ab544d7b4742c8 - proxy:
ghcr.io/actions-gateway/proxy@sha256:b53add9ae5d2f02c30f60b88a2ff3018ffbbd2d06aa41e7ef0ea90973db3bb99 - worker:
ghcr.io/actions-gateway/worker@sha256:8a6535a4f52fa8784020285a7f8177e03aef6bffa168c14dc47d9f839f886fba - wrapper:
ghcr.io/actions-gateway/wrapper@sha256:99e8b1a5044d9e7342b193ab9347d9fbc07c27e9b84c2029aadd7d8ac244e91a
The chart's digest fields ship empty on purpose, so an unconfigured install fails closed rather than running a floating tag. These are the values to put in them.
Verifying this release
We sign every image and both charts.
make verify-release VERSION=v1.4.0The signed v2 CRD manifest and SHA256SUMS ship as release assets with detached cosign bundles, and the publish workflow attests build provenance.