Accelerated Rust CI for GitHub Actions
Speed up Rust builds by up to 2x while cutting costs by 90%, using RunsOn's optimized runners and Magic Cache. With real-world benchmarks.
Rust is notoriously slow to compile in CI environments, especially on GitHub Actions due to underpowered machines and the GitHub Actions cache limits.
With RunsOn, you can alleviate many of these issues, for a fraction of the price you pay for GitHub official runners:
| Rust | RunsOn |
|---|---|
| Has slow compilation times | Provides runners on current-generation CPUs, at any size |
| Builds and tests benefit heavily from multiple CPUs | Provides a ton of different runners with various CPU/memory configs to optimize build speed |
| Caches are often gigantic and get invalidated quickly due to GitHub’s 10GB cache limit | Has Magic Cache with unlimited size and faster restore/save via S3 |
| Can be expensive to run on powerful machines | Uses spot capacity for ~90% cost savings |
Benchmarks#
Lychee#
Lychee ↗ is a fast, async, stream-based link checker written in Rust. It finds broken URLs and mail addresses inside Markdown, HTML, reStructuredText, websites and more.
It already has an optimized CI workflow, but due to the low speed of GitHub runners, switching to RunsOn still managed to provide at least 30% faster builds, while being ~90% cheaper.
Benchmarks show that even when switching to a much larger runner size (16 CPUs instead of 4), it is still ~80% cheaper than GitHub, while being more than 2x faster.
How to speed up your Rust builds#
Once you have installed RunsOn in your AWS account, you can get all the speedups by adding two lines:
- Switch
runs-on:definition to your runner specification (here: a 4 CPUs x64 Linux runner) - Enable the Magic Cache by adding the label
extras=s3-cache, and theruns-on/action@v2action.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml jobs: test: runs-on: ubuntu-latest runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache" steps: - uses: runs-on/action@v2 - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 - name: Compile run: cargo check --locked - name: Test run: make testChoosing a cache#
We measured the common caching setups on openai/codex ↗, a workspace of 161 crates (about 1,470 with dependencies), on a 32 vCPU runner with local NVMe. Each job runs cargo test --workspace. Median job time over three rounds:
| Setup | Cold cache | Same commit again | Small change on a branch | Change in a core crate |
|---|---|---|---|---|
Swatinem/rust-cache | 13 min 20 s | 12 min 59 s | 12 min 40 s | 12 min 41 s |
rust-cache + sccache | 14 min 02 s | 11 min 23 s | 11 min 17 s | 13 min 09 s |
What the numbers say:
rust-cachecaches your dependencies, not your crates. After a restore, Cargo still recompiles every workspace crate: a fresh checkout gives every source file a new modification time.- sccache caches those workspace crates. It cut the build step from about 225 to 140 seconds when little had changed. It does not help when a crate many others depend on changes, and it costs about 40 seconds on a cold build.
- No compile cache speeds up
cargo testitself. Here, running the tests took about 500 seconds of every job, cached or not. Cargo runs test binaries one after the other and does not cache test results.
rust-cache and sccache on RunsOn#
Both work unchanged with Magic Cache. sccache has two ways to store its cache:
jobs: test: runs-on: runs-on=${{ github.run_id }}/runner=16cpu-linux-x64/extras=s3-cache env: CARGO_INCREMENTAL: "0" steps: - uses: runs-on/action@v2 with: sccache: s3 - uses: mozilla-actions/sccache-action@v0.0.11 - uses: actions/checkout@v7 - uses: Swatinem/rust-cache@v2 - run: cargo test --workspace --lockedsccache: s3(above) points sccache straight at your stack’s S3 bucket, under one prefix per repository. Every branch reads and writes the same entries.SCCACHE_GHA_ENABLED: "true"andRUSTC_WRAPPER: sccachein the jobenv, instead of thesccacheinput, make sccache use the GitHub cache API, which Magic Cache serves (since v3.5.0). It then follows GitHub’s cache scopes: a pull request reads the default branch’s entries but never writes them.
Both were equally fast in our benchmark. The second keeps pull requests from writing the default branch’s entries, but it is not a trust boundary.
What we don’t recommend#
Keeping Cargo’s target/ directory on a sticky disk. It was the slowest setup we measured, at 18 to 21 minutes per job:
- Size:
target/reached 100 GB and kept growing. - Slow first reads: a restored volume fills in from its snapshot as it is read.
- Cargo rebuilds anyway: it rebuilds the workspace crates on every fresh checkout.
Sticky disks remain a good fit for ~/.cargo/registry and ~/.cargo/git, which the rust mode of runs-on/action ↗ handles.
Conclusion#
Anyone still using the official GitHub Actions runners for their Rust builds is missing out, both in performance and cost.
RunsOn is the only all-in-one solution that can be full self-hosted in your own AWS account, and comes with up-to-date compatible runner images, transparent S3-based caching for unlimited size and very fast transfers, spot instances for 90% cost savings, and a ton of other features.
Get started now and get your Rust builds in GitHub Actions 10x cheaper and much faster with RunsOn.