Production-Ready Rust Setup
Mastering Rust: Production Configuration & Build Caching
A complete guide to configuring a production-ready Rust environment: sccache, cargo-chef in Docker (Distroless + MUSL), automated CHANGELOGs via git-cliff, and fast CI compilation.
Table of Contents
Sccache & Cargo-Chef
Reduces re-compilation times locally, in CI, and during Docker builds.
Distroless + MUSL
Minimal secure Docker image without extra OS dependencies.
FAT LTO & Mold
Fast compilation linker and maximum release optimization.
Configuration and Caching
.cargo/config.toml (toml)
[build]
# Enable sccache globally or per project (requires sccache installed in PATH)
# rustc-wrapper = "sccache"
[profile.dev]
opt-level = 1 # Fast build in dev with basic dependency optimization
[profile.release]
opt-level = 3
lto = "fat" # Link-Time Optimization for maximum performance
codegen-units = 1 # Maximum optimization for binary size and speed
panic = "abort" # Removes stack unwinding infrastructure (reduces size)
strip = true # Automatically strips debug symbols
[target.x86_64-unknown-linux-gnu]
# Fast linker for builds (requires mold or lld to be installed)
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[lints.rust]
unsafe_code = "forbid"
unreachable_pub = "warn"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
unwrap_used = "warn"Why this is needed: Release profile settings enable FAT LTO, reduced codegen-units, and symbol stripping. The rustc-wrapper setting enables sccache to prevent re-compiling unchanged crate dependencies.
Terminal / Environment Variables (bash)
# 1. Install sccache locally via cargo binstall or cargo install cargo install sccache --locked # 2. Enable sccache via environment variables (e.g. in .bashrc / .zshrc) export RUSTC_WRAPPER=sccache # Disable incremental compilation (required for sccache to work properly) export CARGO_INCREMENTAL=0 # Configure local cache size (default is 10GB) export SCCACHE_DIR=$HOME/.cache/sccache export SCCACHE_CACHE_SIZE="20G" # 3. Check cache usage statistics sccache --show-stats
Why this is needed: sccache intercepts rustc calls and caches compiled object files locally or in remote storage (S3, Redis, GCS, GitHub Actions Cache). This speeds up rebuilds dramatically.
Dockerfile (dockerfile)
# --- STAGE 1: Chef Planner (Dependency Caching) ---
FROM lukemathwalker/cargo-chef:latest-rust-1.80-bullseye AS chef
WORKDIR /app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
# --- STAGE 2: Builder (Building dependencies and application code) ---
FROM chef AS builder
ARG APP_NAME=DEFINE_ME
RUN apt-get update && apt-get install -y musl-tools upx && rustup target add x86_64-unknown-linux-musl
# Build dependencies only (cached by Docker layers)
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json
# Build application source code
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl --bin ${APP_NAME} && strip target/x86_64-unknown-linux-musl/release/${APP_NAME} && upx --best --lzma target/x86_64-unknown-linux-musl/release/${APP_NAME}
# --- STAGE 3: Runtime (Minimal Distroless) ---
FROM gcr.io/distroless/static-debian12:nonroot
ARG APP_NAME=DEFINE_ME
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/${APP_NAME} /usr/local/bin/app
USER nonroot:nonroot
ENTRYPOINT ["/usr/local/bin/app"]Why this is needed: Using cargo-chef instead of fragile "dummy main.rs" hacks guarantees perfect dependency caching across Docker layers. Dependency layers are re-built only when Cargo.lock changes.
cliff.toml (toml)
[changelog]
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
body = """
{% if version %}\
{% if previous.version %}\
## [{{ version | trim_start_matches(pat="v") }}](<REPO>/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## {{ version | trim_start_matches(pat="v") }} - {{ timestamp | date(format="%Y-%m-%d") }}
{% endif %}\
{% else %}\
## unreleased
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | upper_first }} - ([{{ commit.id | truncate(length=7, end="") }}](<REPO>/commit/{{ commit.id }}))\
{% endfor %}
{% endfor %}\n
"""
footer = """
<!-- generated by git-cliff -->
"""
trim = true
postprocessors = [
{ pattern = '<REPO>', replace = "https://github.com/t1ltxz-gxd/tiltflake" },
]
[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))" },
{ pattern = ' +$', replace = "" },
{ pattern = ' +', replace = " " },
{ pattern = ' *(:\w+:|[\p{Emoji_Presentation}\p{Extended_Pictographic}](?:\u{FE0F})?\u{200D}?) *', replace = "" },
]
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->๐ Features" },
{ message = "^fix", group = "<!-- 1 -->๐ Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->๐ Documentation" },
{ message = "^perf", group = "<!-- 4 -->โก Performance" },
{ message = "^refactor", group = "<!-- 2 -->๐ Refactor" },
{ message = "^style", group = "<!-- 5 -->๐จ Styling" },
{ message = "^test", group = "<!-- 6 -->๐งช Testing" },
{ message = "^chore\(release\): prepare for", skip = true },
{ message = "^chore\(deps.*\)", skip = true },
{ message = "^chore\(pr\)", skip = true },
{ message = "^chore\(pull\)", skip = true },
{ message = "^chore|^ci", group = "<!-- 7 -->โ๏ธ Miscellaneous Tasks" },
{ body = ".*security", group = "<!-- 8 -->๐ก๏ธ Security" },
{ message = "^revert", group = "<!-- 9 -->โ๏ธ Revert" },
]
filter_commits = false
topo_order = false
sort_commits = "oldest"Why this is needed: The git-cliff configuration parses commit messages based on Conventional Commits, automatically strips emojis (Gitmoji) from titles, and generates links to GitHub issues.
release.toml (toml)
pre-release-commit-message = ":rocket: chore(release): version {{version}}"
shared-version = true
sign-commit = false
tag-message = "{{version}}"
tag-prefix = ""
publish = false
push = true
tag = true
pre-release-hook = ["git", "cliff", "-o", "CHANGELOG.md", "--tag", "{{version}}"]Why this is needed: Integration of cargo-release with git-cliff. Automatically updates versions, runs CHANGELOG.md generation, and creates a release tag.
rust-toolchain.toml / rustfmt.toml (toml)
# rust-toolchain.toml [toolchain] channel = "1.80.0" components = ["rustfmt", "clippy"] # rustfmt.toml edition = "2021" hard_tabs = true max_width = 100 use_small_heuristics = "Max" reorder_imports = true
Why this is needed: Pinning the toolchain ensures consistent code formatting and linter behavior across all team developers and CI servers.
.github/workflows/ci.yml (yaml)
name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust Toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Run sccache-action
uses: mozilla-actions/sccache-action@v2
- name: Set Environment Variables for Sccache
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Check Formatting
run: cargo fmt --all -- --check
- name: Run Clippy Lints
run: cargo clippy --all-targets -- -D warnings
- name: Run Tests
run: cargo test --allWhy this is needed: Using mozilla-actions/sccache-action alongside `SCCACHE_GHA_ENABLED=true` routes compiled object files directly into GitHub Actions Cache, cutting CI build times by up to 70%.