Fast Rust Builds on GitHub Actions

Fast Rust builds on GitHub Actions come from bigger warp- runners, warm cargo and target caches, and less link work. Sizing tables and cost math inside.

Last verified:

Fast Rust builds on GitHub Actions come from three levers: enough vCPUs to feed cargo's parallel compilation, a cargo cache that survives between jobs, and less link-time work per change. WarpBuild covers the first two directly, with warp- labeled runners from 2 to 32 vCPUs billed per minute and a cache that keeps ~/.cargo and target warm across runs.

Switching takes one line. Change runs-on from a GitHub-hosted label to a warp- label, add a cache step, and the same workflow runs on a machine sized for rustc. The sections below cover the exact configuration, which size fits which workload, and the arithmetic behind the bill.

Overview

Rust spends GitHub Actions minutes in four places: compiling the dependency graph, compiling your own crates, linking binaries, and building test executables. The first is embarrassingly parallel across crates. The second is bounded by your workspace's dependency structure. The third and fourth are dominated by the linker and by how many binaries cargo test produces.

That profile rewards two things a hosted runner controls: core count and cache persistence. Cargo compiles independent crates concurrently, so a 16 vCPU machine chews through a 400-crate dependency graph in far fewer wall-clock minutes than a 2 vCPU machine. And because every dependency compiles from source, a cold ~/.cargo and a cold target directory turn every push into a full rebuild.

For Rust, the Linux runners do the work: the runner catalog lists Ubuntu 22.04, 24.04, and 26.04 images on x64 and Ubuntu 24.04 and 26.04 on ARM64, each in sizes from 2 to 32 vCPUs with 150GB SSDs. The images carry the same tooling as GitHub-hosted runners, so rustup, common linkers, and build essentials are already present. Runners are ephemeral VMs, freshly allocated per job and destroyed afterward.

Cache is enabled by default on all Linux runners. The WarpBuild cache documentation covers the general mechanics; the next section applies them to cargo specifically.

Configuration

Here is a working Rust pipeline on WarpBuild runners. The build and test job runs on 16 vCPUs, the lint job on 4, and both keep their cargo state warm through WarpBuilds/rust-cache, the WarpBuild fork of the standard Rust caching action.

name: rust
on:
  push:
    branches: [main]
  pull_request:

env:
  CARGO_TERM_COLOR: always

jobs:
  build-and-test:
    runs-on: warp-ubuntu-latest-x64-16x
    steps:
      - uses: actions/checkout@v4

      - run: rustup toolchain install stable --profile minimal

      - uses: WarpBuilds/rust-cache@v2
        with:
          cache-provider: warpbuild
          shared-key: rust-ci
          save-if: ${{ github.ref == 'refs/heads/main' }}

      - run: cargo build --workspace --locked
      - run: cargo test --workspace --locked

  lint:
    runs-on: warp-ubuntu-latest-x64-4x
    steps:
      - uses: actions/checkout@v4

      - run: rustup toolchain install stable --profile minimal --component clippy --component rustfmt

      - uses: WarpBuilds/rust-cache@v2
        with:
          cache-provider: warpbuild
          shared-key: rust-lint

      - run: cargo fmt --check
      - run: cargo clippy --workspace --all-targets --locked

Three details matter here.

Install the toolchain before the cache step. The rustc version is part of the cache key, so rustup toolchain install has to run first or the action keys the cache against the wrong compiler.

cache-provider: warpbuild routes storage to WarpBuild Cache. The action caches the cargo registry (index and downloaded crate archives), git dependency checkouts, and the workspace target directories with sensible pruning defaults.

save-if limits cache writes to main. PR branches restore from the main-branch cache and skip the save, which keeps the cache count low and the restore keys predictable. shared-key gives every job in the group one stable cache instead of a per-job key.

If you want explicit control over the paths instead, use WarpBuilds/cache@v1 directly. It is a drop-in replacement for actions/cache@v4, and the three directories that matter for cargo are the registry, the git index, and the target directory:

      - uses: WarpBuilds/cache@v1
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-

Keying on Cargo.lock means the cache refreshes exactly when the dependency set changes, and restore-keys falls back to the newest previous cache so a lockfile bump starts from a mostly warm target rather than from nothing. Cache entries expire after 7 days without use.

For aarch64 targets, swap the label to an ARM64 size such as warp-ubuntu-latest-arm64-8x and the same configuration compiles natively with no cross-compilation toolchain. The Linux ARM64 runner page lists the available sizes.

Sizing

The Linux x64 catalog, with per-minute rates from the WarpBuild pricing page:

Runner labelvCPUMemoryStoragePrice per minute
warp-ubuntu-latest-x64-2x28 GB150GB SSD$0.004
warp-ubuntu-latest-x64-4x416 GB150GB SSD$0.008
warp-ubuntu-latest-x64-8x832 GB150GB SSD$0.016
warp-ubuntu-latest-x64-16x1664 GB150GB SSD$0.032
warp-ubuntu-latest-x64-32x32128 GB150GB SSD$0.064

How to map those sizes to cargo work:

warp-ubuntu-latest-x64-4x fits cargo fmt, cargo clippy, and cargo check jobs, plus full builds of small crates. Lint jobs reuse compiled dependencies from cache and spend most of their time in single-crate analysis, which rarely holds more than a few cores busy.

warp-ubuntu-latest-x64-8x is the default for a mid-size workspace. Dependency compilation fans out across all 8 vCPUs, and cargo test link steps for a handful of test binaries overlap with remaining compilation.

warp-ubuntu-latest-x64-16x earns its rate on large workspaces. Two properties of rustc decide this. First, release builds split each crate into up to 16 codegen units by default, so even a single large crate can keep 16 threads busy during code generation. Second, each integration test file under tests/ links its own binary; a crate with 20 integration test files has 20 link jobs that cargo schedules concurrently when cores are available. Debug builds use 256 codegen units per crate, so compilation parallelism is rarely the constraint there; the constraint is total crate count, and 16 vCPUs drain a wide graph fastest.

warp-ubuntu-latest-x64-32x pays off only when the 16x chart shows sustained full-core saturation. Late in most Rust builds the dependency graph narrows to a few final crates plus their links, and extra cores idle. Check utilization before moving up.

Link-time optimization changes the picture. Fat LTO (lto = true) serializes most code generation into one link step, so a fat-LTO release build gains little beyond 8 vCPUs; thin LTO (lto = "thin") preserves parallelism and scales with the larger sizes. If your release profile uses fat LTO, size the release job separately from the test job.

Worked cost model

GitHub publishes list prices for its hosted runners: the standard Linux runner meters at $0.006 per minute, and Linux larger runners meter at $0.012 for 4 vCPU, $0.022 for 8 vCPU, $0.042 for 16 vCPU, and $0.082 for 32 vCPU. Rates are from the GitHub Actions billing documentation, checked on 2026-08-13.

Take a Rust workspace whose pull request pipeline consumes 40,000 runner-minutes per month on 16 vCPU machines, storing 25 GB of cargo cache and performing 10,000 cache operations:

Line itemRateVolumeMonthly cost
GitHub-hosted Linux 16 vCPU larger runner$0.042 per minute40,000 minutes$1,680.00
warp-ubuntu-latest-x64-16x$0.032 per minute40,000 minutes$1,280.00
WarpBuild cache storage$0.20 per GB-month25 GB$5.00
WarpBuild cache operations$0.0001 per operation10,000 operations$1.00

The WarpBuild total is $1,286.00 against $1,680.00 on GitHub-hosted larger runners for the same minutes, a difference of $394.00 per month. Put differently, warp-ubuntu-latest-x64-16x (16 vCPU, 64 GB) costs $0.032 per minute against $0.042 per minute for the 16-core Linux larger runner (16 vCPU, 64 GB): 24 percent lower list price. GitHub list price checked on 2026-08-13.

Full rates for every size and platform are on the pricing page.

Bottlenecks

Three failure modes account for most slow Rust pipelines on GitHub Actions.

Cold ~/.cargo and target directories. Every runner starts as a fresh VM. Without a cache step, each job fetches the registry index, downloads every crate archive, and compiles the entire dependency graph from source before touching your code. A workspace with 500 transitive dependencies can spend the majority of its wall-clock time rebuilding code that has not changed in months. The fix is the cache configuration above: registry, git checkouts, and target, keyed on Cargo.lock. Restores come from WarpBuild's cache rather than a distant object store, so pulling a multi-gigabyte target directory does not eat the time the cache was meant to save.

Link time on large crates. Linking is the serial tail of a Rust build: one link per binary, and the default linker uses one process regardless of vCPU count. Big integration test suites multiply this, since every file in tests/ is its own binary. Mitigations stack: switch the linker to lld or mold via RUSTFLAGS, merge integration tests into fewer files so there are fewer binaries to link, reduce debug info with debug = "line-tables-only" on the dev profile, and keep fat LTO out of PR builds. More memory also helps here, which is a reason the 16x runner's 64 GB matters for workspaces with large final binaries.

Dependency recompilation on feature-flag churn. Cargo compiles a dependency once per unique feature set. When one workflow job runs cargo check on a subset of the workspace and another runs cargo test --workspace, feature unification can resolve differently and shared dependencies compile twice with different flags, invalidating what the cache stored. Changed RUSTFLAGS, a bumped toolchain, or a --features matrix have the same effect. Keep flags identical across jobs that share a cache key, pass --locked everywhere, and give jobs with genuinely different feature sets their own shared-key so they stop thrashing each other's cache.

Finding which of these applies is a measurement problem. WarpBuild's CI observability streams system metrics from the runner and correlates them with GitHub Actions job logs, so a build stuck at one busy core during a long link step looks visibly different from a build saturating all cores through dependency compilation. Broader workflow-level tactics, including job parallelization and test sharding, are covered in the guide to speeding up GitHub Actions.

The same bottleneck analysis applies with different weights to other compiled languages; see the companion pages on fast Go builds and fast C++ builds.

Proof

Public Rust projects run production workloads on warp- labels, and the workflow files are open to read.

  • near/nearcore runs the NEAR protocol node's GitHub Actions pipeline, including cargo-nextest suites, clippy, and coverage builds, on warp-ubuntu-2404-x64-16x and warp-ubuntu-2404-x64-8x.
  • FuelLabs/sway builds and tests the Sway compiler on warp-ubuntu-latest-x64-4x jobs alongside GitHub-hosted ones.
  • lance-format/lance runs its Rust benchmark suite with cargo bench on warp-ubuntu-latest-arm64-8x.

FAQ

Which WarpBuild runner size should a Rust project start with?

Start on warp-ubuntu-latest-x64-8x at $0.016 per minute. Watch the CPU chart in WarpBuild's CI observability for your longest jobs. Move the build and test jobs to warp-ubuntu-latest-x64-16x when all 8 vCPUs stay saturated through compilation, and drop cargo fmt and clippy jobs down to warp-ubuntu-latest-x64-4x.

Do I have to rewrite my workflow to cache cargo on WarpBuild?

No. Add WarpBuilds/rust-cache@v2 with cache-provider set to warpbuild, or use WarpBuilds/cache@v1 with ~/.cargo/registry, ~/.cargo/git, and target as the cached paths. Both are drop-in compatible with the upstream actions they replace.

Can I build Rust for ARM64 on GitHub Actions with WarpBuild?

Yes. Linux ARM64 labels such as warp-ubuntu-latest-arm64-8x compile aarch64 targets natively, with rates starting at $0.003 per minute for the 2 vCPU size.

Is WarpBuild SOC 2 compliant?

The audit evidence is published at trust.warpbuild.com.

Start with $10 in free credits

Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.