Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Recipe

Set up CI for a Rust project

Use when: a Rust repository has more than one contributor, or ships to more than one platform

Goal: a pipeline that fails any change that is unformatted, warns, fails a test on any target OS, uses an unreviewed dependency version, or has a known advisory

Taught in CI pipelines

  1. 1

    Make sure the lock file is in the repository, for every binary crate.

    Example

    dockrules adds anyhow = "1" to Cargo.toml, but the lock file was made before. cargo test --locked stops with error: cannot update the lock file ... because --locked was passed. Run cargo build on your own machine, then commit Cargo.toml and Cargo.lock together.

    Why: --locked can only protect versions that were committed.

  2. 2

    cargo fmt --all --check, then cargo clippy --all-targets --locked -- -D warnings, in one job.

    Example

    A push has pub fn is_full(bikes:u32,capacity:u32)->bool{ bikes==capacity }. The check job's cargo fmt --all --check prints the diff and fails. Because the test job says needs: check, the three test runs never start.

    Why: formatting and lints fail in seconds and stop the expensive jobs from running on a doomed change.

  3. 3

    A matrix running cargo test --locked on Linux, Windows and macOS, after the checks pass.

    Example

    The test job lists os: [ubuntu-latest, windows-latest, macos-latest] under matrix:, so cargo test --locked runs three times. A test of a path built with format!("{dir}/{name}") can pass on Linux and fail on the Windows run.

    Why: path separators, line endings and C runtimes differ, and only the real OS reveals it.

  4. 4

    cargo audit (or cargo deny check) in its own job.

    Example

    The audit job runs cargo install cargo-audit --locked, then cargo audit. It has no needs:, so it runs alongside the others. If an advisory is published for a crate version already in the lock, the next push turns this job red, even if that push changed no dependency.

    Why: an advisory can appear for a version you already use, without any change to your code.

  5. 5

    Add a Cargo cache keyed on Cargo.lock, and a separate workflow on v* tags that builds --release --locked per target.

    Example

    The check and test jobs each have - uses: Swatinem/rust-cache@v2 right after the toolchain step. The release workflow starts with on: push: tags: ['v*']. Running git tag v0.4.0 and git push origin v0.4.0 starts it, and it runs cargo build --release --locked once per target.

    Why: caching keeps runs fast; building releases from the same locked versions means users get what was tested.