Operations 10 min read

How to Cut Rust Docker Build Times from Minutes to Seconds with Cargo‑Chef

This article analyzes why Docker builds of Rust projects on Ubuntu get stuck updating the crates.io index for several minutes, explains Docker layer cache invalidation and BuildKit differences, and presents a reliable cargo‑chef based Dockerfile that reduces build time to seconds.

Tech Musings
Tech Musings
Tech Musings
How to Cut Rust Docker Build Times from Minutes to Seconds with Cargo‑Chef

Problem Description

When building a Rust project inside Docker on an Ubuntu 24.04 2‑CPU 4‑GB cloud VM, the cargo build command frequently stalls at “Updating crates.io index” for 500‑900 seconds, while the actual compilation takes only a few seconds.

Root Cause Analysis

Docker layer cache invalidation

Docker invalidates a layer and all subsequent layers whenever any input to that layer changes. In the original Dockerfile the src/ directory and the cargo build command are in the same layer, so any source change forces a full re‑run of the index update and dependency compilation.

The deployment script also runs docker compose down --rmi all, which removes the images and discards all caches, causing a full rebuild each time.

Difference between Docker layer cache and BuildKit cache mount

Docker layer cache : the whole layer is invalidated when its inputs change; cache is removed when the image is removed.

BuildKit cache mount : caches directories on the host, persisting across image deletions and allowing fine‑grained, directory‑level updates.

crates.io sparse index

Since Cargo 1.70, the sparse protocol reduces the number of requests needed to sync the crates.io index, especially when combined with a domestic mirror.

Recommended Solution: cargo‑chef

cargo‑chef is a tool designed for Docker layer caching. It separates dependency compilation from source compilation.

How it works

planner : runs cargo chef prepare --recipe-path recipe.json to generate a recipe.json that lists only the dependency graph.

builder — cook : uses the recipe to restore and compile all dependencies; as long as Cargo.toml and Cargo.lock stay unchanged, this layer is cached.

builder — build : copies the real source and builds the application binary, which usually takes only a few seconds.

cargo‑chef automatically supports the sparse registry via the standard Cargo config file ( $CARGO_HOME/config.toml), so no extra configuration is required.

Complete Dockerfile

# syntax=docker/dockerfile:1

# ── Base: install cargo‑chef (layer cached unless Rust image changes) ────────
FROM rust:1.94.1 AS chef
RUN cargo install cargo-chef
WORKDIR /app

# ── Stage 1: Analyze dependencies → produce recipe.json ─────────────────────
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json

# ── Stage 2: Cook deps + build app ──────────────────────────────────────────
FROM chef AS builder
COPY config/cargo-config.toml /usr/local/cargo/config.toml
COPY --from=planner /app/recipe.json recipe.json
# Cook dependencies — this layer runs only when Cargo.toml / Cargo.lock change
RUN --mount=type=cache,id=cargo-registry,target=/usr/local/cargo/registry \
    --mount=type=cache,id=cargo-git,target=/usr/local/cargo/git \
    cargo chef cook --release --recipe-path recipe.json

# Build the application binary
COPY . .
RUN --mount=type=cache,id=cargo-registry,target=/usr/local/cargo/registry \
    --mount=type=cache,id=cargo-git,target=/usr/local/cargo/git \
    cargo build --release

# ── Stage 3: Minimal runtime image ──────────────────────────────────────────
FROM rust:1.94.1
WORKDIR /app
COPY --from=builder /app/target/release/blog-indexnow /app/blog-indexnow
COPY config/application.yaml /app/config/application.yaml
ENV APP_CONFIG=/app/config/application.yaml
ENTRYPOINT ["/app/blog-indexnow"]

cargo‑config.toml (TUNA sparse mirror)

[source.crates-io]
replace-with = "tuna"

[source.tuna]
registry = "sparse+https://mirrors.tuna.tsinghua.edu.cn/crates.io-index/"

[net]
git-fetch-with-cli = true

Common Chinese sparse mirrors include TUNA, rsproxy, and USTC.

Alternative Methods (tested but unstable in production)

Three approaches from “How to Speed Up Docker Build for Rust Projects” were tried but caused container crashes.

Method 1: Dummy Build Trick

Compile dependencies with an empty main.rs, then replace it with the real source. This requires manual cleanup of specific build artifacts and can break with more complex workspace layouts.

Method 2: Pure BuildKit cache mount

Use --mount=type=cache for the Cargo registry, git, and target directories, then copy the built binary out of the mount before the layer ends.

Method 3: Combine dummy build and cache mount

Mixes the previous two techniques but remains fragile compared to cargo‑chef.

Overall, cargo‑chef provides a reliable, standard‑toolchain solution for speeding up Rust Docker builds.

DockerCacheRustbuild optimizationBuildKitcargo-chef
Tech Musings
Written by

Tech Musings

Capturing thoughts and reflections while coding.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.