self-host →

Reproducing Namespace's Rust CI benchmark on GitHub Actions and RunsOn

We reproduced Namespace's spotify-player benchmark on equal 4-vCPU runners. RunsOn's S3 cache cut the warm median to 49 seconds.

For a small Rust build, you do not need a persistent runner or a persistent disk. A fast ephemeral runner plus an S3-backed cache is enough.

Before getting into the numbers: we like Namespace a lot. They currently sit at the top of our overall GitHub Actions CPU benchmark, and we appreciate them publishing a benchmark detailed enough for someone else to reproduce. We spend a lot of time measuring runner performance ourselves, so this is exactly the kind of engineering post we want to see more often. Thanks to the Namespace team for putting it together.

We reproduced Namespace’s spotify-player experiment on RunsOn. Their result was a roughly four-minute GitHub job reduced to about one minute after caching Rust’s target directory. Ours says the same thing:

runnercached Linux durationnon-spot or contracted totalestimated spot total
Namespace Ubuntu + Cache Volume34s average$0.006
RunsOn c8a.xlarge + S3 cache49s median$0.00359 ↓ 40.1%$0.00129 ↓ 78.5%
RunsOn c8a.xlarge + sticky disk52s median$0.00379 ↓ 36.9%$0.00148 ↓ 75.3%

Namespace reports the average cached Ubuntu job and its contracted-unit price; pay-as-you-go and overage usage is priced 1.5× higher. Our RunsOn figures are the median of three warm Linux jobs. Because both finish in under a minute, the comparison prices RunsOn compute at AWS’s 60-second minimum; the sticky total also includes one minute of volume cost. Green percentages show the reduction from Namespace’s $0.006 contracted price. The project and toolchain are the same, but the runner hardware, cache implementation, aggregation method, and pricing model differ.

The S3 cache and sticky disk are effectively tied. The simpler S3 option wins.

A reproduction, not a new benchmark shaped to favor RunsOn#

We kept the important parts of the Namespace setup:

  • the same public aome510/spotify-player project;
  • the same Rust toolchain action with its built-in cache disabled;
  • a 4-vCPU x64 runner for every backend;
  • a cold run followed by three warm runs;
  • Rust’s target directory persisted on the two RunsOn lanes.

The toolchain step is exactly the one used in the original article:

- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false

Disabling that cache matters. Otherwise the workflow mixes the toolchain action’s caching policy with the cache being tested.

For GitHub, we used ubuntu-24.04 and confirmed the public runner exposed four vCPUs. The reference job completed in about four minutes, matching Namespace’s result.

For RunsOn, we used an AWS c8a.xlarge: four AMD EPYC vCPUs and 8 GiB of memory. Its current us-east-1 non-spot price is $0.21554/hour; the lowest spot price observed on July 13, 2026 was $0.0773/hour. The S3 lane enabled the RunsOn s3-cache extra. The sticky lane used a 20 GB gp3 disk at 300 MB/s.

The complete result#

Cold build#

backendjob timecomputecache volumenon-spot totalestimated spot totalsavings vs GitHub (spot)savings vs GitHub (on-demand)
GitHub 4-core3m17s$0.048$0.048
RunsOn S32m01s$0.0084$0.0084$0.0030193.7%82.5%
RunsOn sticky disk1m55s$0.0081$0.00044$0.00854$0.0033593.0%82.2%

Warm builds (median of three)#

backendjob timecomputecache volumenon-spot totalestimated spot totalsavings vs GitHub (spot)savings vs GitHub (on-demand)
GitHub 4-core3m31s$0.048$0.048
RunsOn S349s$0.0040$0.0040$0.0014497.0%91.7%
RunsOn sticky disk52s$0.0041$0.00023$0.00433$0.0017096.5%91.0%

The median uses the middle value from the three warm samples for job time and each cost component.

GitHub’s public-repository runner is free to the repository. The GitHub cost column instead answers the useful private-repository question: what would an equivalent 4-core Linux larger runner cost? GitHub’s current rate is $0.012 per minute, and GitHub rounds each job up to a whole minute. Each displayed warm job therefore bills four minutes, or $0.048.

RunsOn compute uses the EC2 instance lifetime reported by the runner, priced per second after AWS’s 60-second minimum. The non-spot column uses $0.21554/hour. The spot estimate applies the lowest observed July 13 rate of $0.0773/hour to the same lifetime; actual spot prices and placement vary by Availability Zone and time. These figures exclude the RunsOn subscription and shared control-plane costs. Sticky totals add the same prorated cache-volume cost in both columns: 20 GB gp3 at 300 MB/s costs $8.60/month in us-east-1.

The S3 cache cost does not change the conclusion, but it is worth calculating. The observed compressed archive was 585,183,132 bytes, or 558 MiB. At S3 Standard’s $0.023 per GB-month, retaining it for 10 days costs about $0.00418. With the cache service’s current 16 MiB upload parts and 32 MiB download parts, one save plus one restore adds about $0.00019 in request charges, for $0.00437 total. Each additional restore costs about $0.000008.

That storage cost belongs to the cache object, not to an individual job. If ten jobs reuse it during the retention window, storage, creation, and restore requests amortize to about $0.00044 per job. We therefore keep it separate from the per-job table instead of choosing an arbitrary cache-hit count.

Why S3 is enough here#

The hot Rust cache is modest and straightforward: restore target, compile what changed, then save it again. Moving that directory through the cache API does not dominate a one-minute job.

A sticky disk removes the transfer, but there is not enough state for that advantage to overcome normal run-to-run variation. Across three warm samples, S3’s median is actually three seconds faster. That is not evidence that S3 storage is faster than a local volume. It is evidence that the difference does not matter for this workload.

This is the cache setup we would ship:

benchmark-spotify-x64 = {
extras = ["s3-cache"]
family = ["c8a.xlarge"]
image = "ubuntu24-full-x64"
}

The workflow continues to use the ordinary GitHub Actions cache interface. RunsOn’s cache service stores the objects in S3 in your AWS account, close to the runner. There is no persistent VM to keep warm and no cache volume to size or garbage-collect.

That last point is the reason to start with S3. Persistent storage is useful when it preserves state that is expensive to serialize. It should not be a default tax on every job.

The practical conclusion#

Namespace’s core result reproduces cleanly: persisting Rust build outputs turns this job from roughly three and a half minutes into roughly one minute.

On RunsOn, the S3-backed cache gets the warm median down to 49 seconds on a current-generation four-vCPU instance. A sticky disk gets 52 seconds. Choose S3.

The interesting question is where this conclusion stops being true. We tested that next with PostHog’s much larger Docker build graph. There, exporting a remote BuildKit cache consumed 268 seconds by itself, and a persistent BuildKit volume cut the controlled frontend rebuild from 8m07s to 4m25s. That follow-up is in When an S3 cache is not enough: PostHog builds with sticky disks.