# RunsOn: an AWS runner control plane whose repository ships no application source

> RunsOn launches a fresh EC2 runner for every GitHub Actions job and bills it straight to your AWS account. The repository is unusually thin for a commercial infrastructure product: a LICENSE, a README, a SECURITY file, a SPONSORSION file, a CloudFormation directory and an OpenTelemetry directory, with no source tree for the control plane itself.

**runs-on/runs-on** — Self-hosted GitHub Actions runners made simple. For AWS. 10x cheaper, up to 2x faster, and unlimited caching. Best alternative to Actions Runner Controller.

- Repository: https://github.com/runs-on/runs-on
- Website: https://runs-on.com
- Stars: 1,353 · Forks: 51
- Language: Unknown
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/runs-on-runs-on

## The repository root has no application source in it

The complete top level listing is seven entries: a GitHub directory, a LICENSE, a README, a SECURITY file, a SPONSORSION file, a cloudformation directory and an otel directory. There is no src directory, no package manifest, no language marker, and the primary language field comes back unknown.

So the thing that launches a runner for every job on your account is not in this repository. What is in it is the material you would deploy: infrastructure as code for the CloudFormation path, and something under otel for the telemetry story. That is a coherent choice for a product whose stated position is that the AWS account is the policy boundary, since you can read the stack that touches your account even when you cannot read the service provisioning it.

It also changes what code review means here. You are reviewing the blast radius of a template and a set of labels, not the lifecycle logic. Anyone whose decision to adopt depends on reading the runner lifecycle code should notice this before the trial rather than after, because no amount of reading this repository will produce it.

## The runs-on value is a query string, and it embeds the run id

In the Flex mode the label the workflow author writes is the whole configuration surface:

```yaml
# .github/workflows/ci.yml
jobs:
  build:
    runs-on: runs-on=${{ github.run_id }}/family=c8a+m8a/cpu=4/ram=8+16/image=ubuntu24-full-x64/extras=s3-cache/volume=100gb:gp3
    steps:
      - uses: actions/checkout@v7
      - run: make test
```

One string carries compute family, CPU count, a RAM range, the image, the caching extras and the volume type and size. It opens with the literal prefix runs-on= followed by an expression that expands to the run id, and then path segments separated by slashes. The run id is doing double duty: it tells the control plane which workflow run the request belongs to, and it makes every label unique so two jobs asking for the same shape do not collide.

The format is compact and readable, and it is also untyped. Nothing in the workflow file validates that a segment is spelled correctly, so a typo in a volume type surfaces as a provisioning failure rather than a schema error at lint time. The RAM range is written as two values joined by a plus sign, which is how a job asks for either size rather than picking one.

The same string can also ask for a GPU, ARM64, a custom AMI, a static IP or private networking, so the label grammar is the documented way to reach every hardware option. That concentration is the trade: one expressive field instead of a matrix of self-hosted labels, at the cost of moving validation from the workflow linter to the control plane.

## Fleet is early access, and only Terraform can change a fleet

Fleet is the other operating model, and it inverts who chooses the hardware. Instead of a per-job label, the platform team publishes named shapes as Terraform inputs:

```hcl
# fleet.auto.tfvars
runners = {
  linux-build = {
    cpu    = 4
    ram    = [8, 16]
    family = ["c8a", "m8a"]
    image  = "ubuntu24-full-x64"
    extras = ["s3-cache"]
  }
}

fleets = {
  linux-build = {
    timezone     = "UTC"
    runner       = "linux-build"
    runner_group = "platform"
    max_runners  = 200
  }
}
```

A workflow then selects the published shape by name rather than describing one. The guarantee is one-directional and worth stating plainly: the catalog is the only place a fleet's CPU, image, cache, runner group or capacity can change. Workflow authors get a name and a capacity ceiling, and the ceiling is set in the same file as the shape.

The caveat is explicit. Fleet is in early access, and the file asks you to pin an exact Terraform module version before rolling it out broadly. An early-access control surface plus a version pin is a reasonable combination, and it is also the combination that makes upgrades deliberate rather than automatic. Flex and Fleet are separate deployments that can share an account and one licence, so a team can standardise the common path with Fleet and leave the unusual jobs on Flex.

## The benchmark footnote narrows every headline number above it

The comparisons are stated as multiples and then constrained by a footnote. Two vCPU Linux work is put at $0.0009 per minute on c8a.large against $0.0060 on GitHub-hosted. A T4 GPU job is put at $0.0041 on g4dn.xlarge against $0.0520. Single-thread CPU performance is given as a PassMark score of 4,299 on m8azn against 2,269 for GitHub-hosted Linux.

The footnote matters more than it looks. It fixes the comparison to US East spot prices with a 30 GB gp3 root volume, and it states that AWS prices change. So the multiples are arithmetic on a specific region, a specific purchase model and a specific volume configuration. A reader in another region, paying on-demand rather than spot, or needing a larger root volume, gets a different ratio, and nothing in the headline numbers says so.

The document does route you onward, pointing at a live calculator for your region and workload rather than asking you to trust the table. That is the right instinct. The same pattern shows up in the cost section, which offers spot instances with on-demand fallback, so the fallback path is the one that determines your bill when capacity is scarce.

## The project summary promises 2x faster, the page reports 89 percent

The one line summary attached to the project claims a cost reduction of ten times, a speed improvement of up to two times, and unlimited caching, and calls itself the best alternative to the Actions Runner Controller. The page underneath makes different claims. It says often 5 to 13 times less than GitHub-hosted, up to 13 times lower GPU cost, and up to 89 percent higher single-thread CPU performance, and the largest number in the scale section is a throughput figure rather than a speed figure: 24.7 fresh runners per second sustained across a day, from a total of 2.13 million jobs in that day.

None of those are contradictions in the arithmetic. A 4,299 against 2,269 PassMark score is roughly a 1.9 times improvement, which is where the two times figure comes from. What differs is the framing, and the summary makes claims in the absolute form the page hedges.

The runner launch rate is also not the same measurement as job duration. Twenty-four point seven runners per second says the control plane can start machines quickly at scale. It does not say a given job finishes sooner, because most job time is spent inside the job. Read the two claims separately, because combining them is how a per-second provisioning figure turns into a claim about build speed it cannot support.

## The compatibility promise is a 15-day image rebuild schedule

The reason a self-hosted runner can be a drop-in for a hosted one is that the AMI is not your AMI. The Linux images are described as built from GitHub's full runner images and refreshed every 15 days, which means existing actions and toolchains keep working because GitHub's own toolchain definitions are being copied on a schedule rather than maintained by you.

That schedule is the dependency. A job that assumes a tool installed on the hosted image works here because the image tracks the hosted image, and it keeps working as long as the tracking does. The project says you can bring your own AMI when needed, which is the escape hatch for teams whose toolchain has drifted or whose security team requires an image pipeline they own.

Alongside that sit the caching choices: a built-in S3 cache, a Docker pull-through cache, EFS, and EBS sticky disks. The sticky disk option is the interesting one for stateful work, since an ephemeral per-job runner has nowhere to keep a dependency cache between runs. The summary calls caching unlimited, and the platform section promises cost observability through OpenTelemetry export with per-job costs and AWS budgets, so the caching story and the cost story are meant to be read together.

The scale claim sits alongside both. Twenty-four point seven fresh runners per second, sustained across a full day, from 2.13 million jobs, is a figure about the control plane provisioning machines concurrently rather than about any single job. The last push to the repository was 2026-10-02 and the v3.4.0 tag went out the same day, with v3.3.2 a fortnight earlier, so the release line is moving on a weekly cadence.

## The ARC comparison is the strongest argument in the file

The document lays out three models and describes what you give up in each. RunsOn owns the AWS boundary and nothing else. ARC, the Actions Runner Controller, fits Kubernetes-first teams who want runner pods beside other cluster workloads, at the cost of your team operating the cluster, the controller, autoscaling and the runner images. A hosted runner service operates the platform for you in exchange for accepting its hardware catalog, its billing model and its execution trust boundary.

Stated that way the choice is not runner versus no runner, it is which layer you want to own. The comparison is unusually even handed, including the point that ARC is a reasonable answer for a team that already runs Kubernetes and would rather colocate than operate a second provisioning path.

The security file and the trust boundary language connect to the same theme. Keeping the control plane, the runners, the cache and the GitHub credentials inside your account means job code runs on instances you account for, under your IAM and network controls, and the credentials used to talk to GitHub never leave that boundary. Whether the control plane software itself is inspectable is a separate question, and the absence of a source directory in this repository is the honest limit on how far that argument goes.

## Conclusion

RunsOn is worth evaluating if your team already runs on AWS and has hit GitHub-hosted runner bills, because the model is per-job EC2 with spot pricing and no per-minute markup, and the compatibility story rests on Linux AMIs rebuilt from GitHub's own runner images. Three things to check before you move a workflow. First, you are adopting a control plane you cannot read: the repository root has no application source directory, so what you can audit is the deployment material and not the software that launches your runners. Second, Fleet, the mode where a platform team publishes an approved catalog, is described as early access and asks you to pin an exact Terraform module version before broad rollout, so treat it as the less settled half. Third, read the benchmark footnote before quoting any of the headline figures, because the multiples depend on US East spot pricing, a 30 GB gp3 root volume and PassMark single-thread scores, all of which move independently of what you will pay. If you are Kubernetes-first and already run ARC, the project makes the argument against itself honestly, and that comparison page is the fastest way to decide whether the tradeoff is real for your team.

## FAQ

### What does RunsOn actually launch for each GitHub Actions job?

An isolated EC2 instance registered as a runner, launched fresh for that job and scaled back to zero afterwards. It uses Linux AMIs built from GitHub's own runner images and refreshed every 15 days, which is what keeps existing actions and toolchains working. Windows, ARM64 and GPU jobs are also supported.

### What is the difference between Flex and Fleet in RunsOn?

Flex lets workflow authors describe the runner per job in the runs-on label, covering compute family, CPU, RAM, image, caching extras and volume. Fleet has the platform team publish approved runner shapes as Terraform inputs, and only that catalog can change a fleet's CPU, image, cache, runner group or capacity. They deploy separately and can share one AWS account and one licence.

### Is Fleet from RunsOn ready for production use?

It is described as early access, and the documentation asks you to pin an exact Terraform module version before rolling it out broadly. That combination means upgrades to the fleet control surface are deliberate rather than automatic, so pin the version and read the changelog between bumps.

### How does RunsOn compare with the Actions Runner Controller?

The file frames three models. RunsOn keeps the control plane, runners, cache and credentials in your AWS account without adding a Kubernetes cluster, controller or autoscaler. ARC suits Kubernetes-first teams who want runner pods beside other workloads and are willing to operate the cluster and the runner images. A hosted runner service operates the platform but sets the hardware catalog, billing model and trust boundary.

### What are the stated cost savings from running jobs on RunsOn?

Two vCPU Linux is quoted at $0.0009 per minute on c8a.large against $0.0060 on GitHub-hosted, and a T4 GPU job at $0.0041 on g4dn.xlarge against $0.0520. The footnote fixes those to US East spot prices with a 30 GB gp3 root volume and notes that AWS prices change, pointing at a live calculator for your region and workload.

### Where is the source code for the RunsOn control plane?

The repository root holds the GitHub directory, a LICENSE, a README, a SECURITY file, a SPONSORSION file, a cloudformation directory and an otel directory, with no application source tree and no recorded primary language. What you can review is the deployment material, not the service logic that launches runners.

## Sources

- [License: MIT](https://github.com/runs-on/runs-on/blob/main/LICENSE)
- [Project website](https://runs-on.com)
- [README](https://github.com/runs-on/runs-on/blob/main/README.md)
- [Releases](https://github.com/runs-on/runs-on/releases)
- [runs-on/runs-on on GitHub](https://github.com/runs-on/runs-on)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/runs-on-runs-on
