CI/CD for Rust
A solid Rust pipeline runs fmt, clippy, test, and build --release on every PR, caches target/ and registry index, and publishes binaries or containers on tags.
Recipe
# .github/workflows/ci.yml
name: ci
on: [push, pull_request]
jobs:
rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.97.0
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- run: cargo fmt --all -- --check
- run: cargo clippy --all-targets --all-features -- -D warnings
- run: cargo test --all-features
- run: cargo build --release --lockedWhen to reach for this:
- Every team repo with more than one contributor
- Before enabling auto-deploy to staging
- When CI time exceeds 10 minutes without cache
Working Example
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
steps:
- run: rustup target add ${{ matrix.target }}
- run: cross build --release --target ${{ matrix.target }}# Local parity
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo testWhat this demonstrates:
- Toolchain pin matches production MSRV
rust-cacherestores dependencies between runs- Matrix validates cross-compiled artifacts
Deep Dive
Cache Layers
| Layer | Tool |
|---|---|
Registry + target/ | Swatinem/rust-cache |
| sccache | Remote compiler cache for large orgs |
| Docker layer cache | Dependency-only Dockerfile step |
Release Automation
Tag v1.2.3 triggers:
cargo build --release- SBOM /
cargo auditable(optional) - Push container or attach GitHub Release assets
Use cargo-release or release-plz for crate versioning in libraries.
Gotchas
- No
--lockedin CI - lockfile drift ships broken deps. Fix: fail CI if lock outdated. - Clippy warnings ignored - debt accumulates. Fix:
-D warningson main branch. - Tests requiring Docker DB - flaky without service container. Fix:
services: postgresin Actions or sqlx offline mode. - Feature matrix explosion - 2^n combinations. Fix: test
defaultplusall-featureson nightly schedule. - Huge integration tests on every PR - slow feedback. Fix: split
cargo test --libfast job.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| GitLab CI | Org on GitLab | Already on Actions |
| Buildkite | Custom agents | Simple OSS project |
cargo nextest | Faster test runner | Tiny test suites |
FAQs
Stable vs pinned toolchain?
Pin 1.97.0 (or MSRV) in CI; document bump process in team guide.
How to cache sqlx offline?
Commit .sqlx/ directory; set SQLX_OFFLINE=true in CI env.
Deploy from CI secrets?
Use OIDC to cloud (no long-lived AWS keys); inject DATABASE_URL at deploy time only.
Nightly rustc in CI?
Optional miri or 2024 edition preview job; do not block PRs on nightly-only failures.
Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.