A Rust build cache in GitHub Actions
Updated
A Rust job in GitHub Actions starts with nothing compiled, so every run compiles every crate again. With hushrun's shared store, a job downloads the crates your main branch's CI already compiled instead, and so do teammates' laptops and coding agents' sandboxes, while a pull request can reuse that code but never change it.
If you only need one repository's CI warm, and no laptops or sandboxes, a plain cache may be enough: see Without hushrun.
Before you begin
- hushrun 0.15.0 or later, on every machine and in CI.
- The repository on GitHub Actions, connected with
npx hushrun init, which also gives its jobsid-token: write. - An admin or owner of your organization on hushrun, for the steps in the dashboard.
- One Rust toolchain for laptops and CI, pinned in
rust-toolchain.toml. A machine reuses only code built for its own system and compiler version, so a Mac gets nothing from Linux CI.
Run your cargo commands through hushrun
Only hushrun run NAME -- COMMAND uses the shared store. npx hushrun init wraps the cargo lines it finds in your workflows, or you write them yourself:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v5
- uses: hushrun/hushrun/action/[email protected]
- run: hushrun run test -- cargo test --locked
- run: hushrun run build -- cargo build --lockedThe job needs id-token: write: it asks the shared store as itself, with the job's ID token, and without one it shares nothing. The backend comes from the hushrun.yaml that init writes; without one, set HUSHRUN_BACKEND in the job's env (Set up CI).
The same line also skips the whole check when these exact files already passed it (How it works). Clippy, rustdoc and coverage builds can go through hushrun too, but their compiles aren't reused.
Take out other compile caches
In the jobs that run through hushrun:
- Remove
RUSTC_WRAPPER, and sccache's setup step if it sets it. Where it's set, hushrun's build reuse turns itself off for that run, and says so in one line. - Stop caching
target/between jobs. A restored folder proves nothing about how its files were built, so hushrun shares only what the job compiled itself. - Caching cargo's registry,
~/.cargo, is fine.
Then push to main, so its CI runs hushrun once.
Confirm the repository and trust main
In the dashboard's Shared store page, https://app.hushrun.com/<org>/-/build:
- Under Repositories, open the repository, which shows as seen once its CI has run hushrun, and choose Confirm….
- Under Trusted branches, choose
mainand List…. Its CI now writes the code every other machine reuses. List only branches whose pushes you trust, and protect them on GitHub.
The full rules, and what each choice trusts: Share compiled code.
Turn sharing on, then reuse
- Under Sharing, choose Turn on…. Sharing starts by watching: machines compile everything, upload what they build and compare it with the store, and reuse nothing.
- Push to
mainand open a pull request or two. The Usage panel shows the comparisons, trusted CI's first. - Once trusted CI has compared crates and none differed, choose Start reusing….
From then on, a job's last lines say what came from the store. This line is an example of the output, not a measurement:
hushrun: build: 226 of 360 crates reused (226 from the shared store), 134 built, saving about 2m 3sWhich jobs reuse what
| Job | Reuses | Writes |
|---|---|---|
A push to main, once listed | main's trusted code | main's trusted code |
| A pull request | The trusted code, then what earlier runs of the same pull request built | Code for that pull request alone |
A tag, or a job with an environment: key | The trusted code | Nothing |
| A pull request from a fork | Nothing | Nothing |
A job on a runner that isn't one of GitHub's own writes trusted code only after an admin trusts self-hosted runners (why). The whole table, laptops and sandboxes included: Whose code a machine reuses.
Laptops and sandboxes
- A teammate's laptop reuses the trusted code after
hushrun build on, once per machine. It's off until then. - A coding agent's sandbox with
HUSHRUN_AGENTreuses it with nothing more, as long as it can reach the store's host: see Warm sandboxes for coding agents.
Reuse runs one way: CI's code reaches laptops and sandboxes, and a laptop's never reaches CI.
Without hushrun
A plain cache is the better fit when:
- your CI isn't GitHub Actions: hushrun's shared store doesn't take part in GitLab CI or other CI yet;
- you build on Windows, or compile C, C++ or CUDA as well, which hushrun's compile reuse doesn't cover;
- only CI needs to be warm, and you'd rather not involve an admin or a hosted store.
Cache cargo's folders. Swatinem/rust-cache caches ~/.cargo and the dependencies' build output in ./target, keyed on the compiler, Cargo.lock, the Cargo.toml files and the toolchain files. It doesn't cache your workspace's own crates.
steps:
- uses: actions/checkout@v5
- uses: Swatinem/rust-cache@v2
- run: cargo test --lockedCache compiler output with sccache. mozilla-actions/sccache-action installs sccache, and SCCACHE_GHA_ENABLED keeps its cache in GitHub's. It can't cache bin, dylib, cdylib or proc-macro crates, or incremental builds (sccache's Rust notes).
jobs:
test:
runs-on: ubuntu-latest
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@v5
- uses: mozilla-actions/[email protected]
- run: cargo test --lockedBoth store in GitHub's cache, which holds 10 GB per repository by default and drops what's unused for 7 days. A run restores caches from its own branch and the default branch, and a pull request also from its base branch. More: hushrun and sccache.
Limits
- Only libraries are shared. Programs, tests, proc macros and build scripts are compiled on each machine; a machine's own store still reuses them.
- New code compiles once, wherever it's first built.
hushrun run CHECK, the checks ofhushrun.yaml, uses no shared code in CI. Usehushrun run NAME -- COMMAND.- No speed figure. hushrun hasn't published a measurement of the shared store yet. A download starts only when it should arrive within half the time the crate takes to compile; otherwise the compiler runs (Compiled code).
Related
- Share compiled code: every step and setting.
- hushrun and sccache
- hushrun on CI: what CI stops doing again.
- Reduce your GitHub Actions costs