Cache Cargo and Rust Builds in GitHub Actions
Last updated
To cache Cargo in GitHub Actions, the simplest option is Swatinem/rust-cache. It caches the Cargo registry, git dependencies and the compiled dependencies in target, and it trims the cache before saving. The explicit actions/cache form works too, but caching target blindly gets huge and slow.
Option 1: Swatinem/rust-cache
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --locked
According to the rust-cache README (checked October 2026), the action caches ~/.cargo (installed binaries, the registry index and crate cache, and git dependencies, with registry/src left out) and ./target. It does not cache your own workspace crates, only their dependencies. The key is built from the job name, the Rust version, and the hash of Cargo.lock and manifest files, and it sets CARGO_INCREMENTAL=0.
Before saving, it removes anything that is not a current dependency, incremental artifacts, and build artifacts older than a week. That cleanup is the main reason to prefer it over a hand-written cache.
Inputs you will actually use:
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
. -> target
tools/xtask -> target
shared-key: ci
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-on-failure: true
workspaceslistsworkspace -> targetpairs for repos with more than one Cargo workspace.shared-keymakes several jobs share one cache instead of one per job name. Good whencheck,clippyandtestbuild the same dependencies, though debug and release profiles differ, so share only between jobs with the same profile.save-ifsaves only frommain, keeping PR branches from filling your quota.cache-on-failuresaves even if the job fails, useful because a failing test run has still compiled all dependencies.
Option 2: explicit actions/cache
- uses: actions/cache@v6
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: cargo-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-${{ runner.arch }}-
- run: cargo build --locked
This is the pattern Cargo's own guidance and the actions/cache examples use. The key has OS, architecture and Cargo.lock. Add the compiler version (rustc --version) if you do not pin a toolchain with rust-toolchain.toml, since artifacts built by one rustc are not reused by another.
What to cache, and what not to
| Path | Cache? | Reason |
|---|---|---|
~/.cargo/registry/index and registry/cache | Yes | Avoids re-downloading crates |
~/.cargo/git/db | Yes | Git dependencies |
~/.cargo/registry/src | No | Unpacked sources; Cargo recreates from cache quickly |
target/ dependencies | Yes | The big win: compiled dependency crates |
target/ of your own crates | No | Changes every commit and bloats the cache |
target/**/incremental | No | Large and rarely useful in CI |
~/.cargo/bin | Careful | Cache installed tools by version, not wholesale |
The "rolling cache" trap: with an explicit key on Cargo.lock, the cache is only saved when the lockfile changes. Your workspace crates are never saved, which is what you want. If you instead key on github.sha, the cache grows with every commit's build output. rust-cache avoids this by cleaning before save.
When target is too big
A Rust target directory for a large workspace can be several GB, and restore time grows with it. Options:
- Turn off debug info for CI:
CARGO_PROFILE_DEV_DEBUG=0(ordebug = "line-tables-only"). It shrinks artifacts and speeds linking. - Use
sccacheas a compiler wrapper with the GitHub Actions cache backend instead of cachingtarget. It caches per-crate compile results rather than whole directories. - Cache only
~/.cargoand accept rebuilding dependencies. Often worse, but cheaper on quota. - Run on more cores. Compile time scales with CPU; see faster runners. In the compiler.dev measured run for React plus Rust (Axum) with Postgres, full CI went from 2m37s to 1m45s (see benchmarks).
Other speed-ups that matter more than the cache
cargo test --lockedfails ifCargo.lockis out of date, so CI never resolves silently.cargo nextestruns tests in parallel across binaries; pair it with test sharding for large suites.- Use
cargo checkfor lint-only jobs. It skips code generation. moldorlldas the linker cuts link time for big binaries.
How to verify a cache hit
- rust-cache logs
Cache restored from key: ...(look forRestoring cacheand the key it matched) and a post-step summary of what it saved or why it did not. - In build output, a warm run shows
Compilingonly for your crates. A cold run lists every dependency. - Compare the time of
cargo buildbetween runs with the same lockfile. gh cache list --key rust --limit 20shows size and last-used.
Common mistakes
- Different jobs fighting over one key. Debug and release builds, or
clippyandtest, invalidate each other. Use per-profile keys. - Not using
--locked. A lockfile that drifts silently changes your key's meaning. - Caching the whole
targetfrom every PR. PR caches are not shared across PRs; save frommain. See Caching in GitHub Actions. - Hitting the quota. Rust caches are large. Read cache limits.
FAQ
Should I cache the target directory?
Cache dependency artifacts in it, yes, and let a tool like rust-cache remove your own crates' output. Caching everything grows without bound.
Is sccache better than rust-cache?
They solve different problems. rust-cache restores a directory; sccache caches individual compile units and works across branches and profiles. For large workspaces sccache can win; for most projects rust-cache is simpler.
Why does CARGO_INCREMENTAL=0 matter?
Incremental compilation writes large artifacts that help local rebuilds but are of little use on a fresh CI machine.
How do I cache cargo install tools?
Key on the tool and its version, or use a prebuilt-binary action such as taiki-e/install-action.
Next steps
Related reading: speeding up Rust CI end to end, Go modules caching for a similar two-directory pattern, and how to speed up GitHub Actions. To try Rust CI on 8 vCPU machines, see compiler.dev pricing.
Related
- Cache Go Modules and Build Cache in GitHub ActionsCache Go modules (GOMODCACHE) and the build cache (GOCACHE) in GitHub Actions with setup-go or actions/cache. Keys, go.sum, and verifying hits.
- Cache Gradle Builds in GitHub ActionsCache Gradle dependencies and build cache in GitHub Actions with gradle/actions/setup-gradle or actions/cache. Keys, read-only PRs, cleanup and checks.
- Cache npm Dependencies in GitHub ActionsCache npm dependencies in GitHub Actions with setup-node or actions/cache. Working YAML, the right key, why not node_modules, and how to confirm a hit.
- Cache pip Dependencies in GitHub ActionsCache pip in GitHub Actions with setup-python or actions/cache, plus the faster uv option. Keys, what not to cache, and how to verify a hit.