We use cookies to understand how people use Depot.
Container Builds

Optimal Dockerfile for Rust with cargo-chef and sccache

Below is an example Dockerfile that we recommend at Depot for building images for Rust applications.

# syntax=docker/dockerfile:1

FROM rust:1.90 AS build

RUN cargo install cargo-chef sccache --locked

ENV RUSTC_WRAPPER=sccache \
    SCCACHE_DIR=/sccache

WORKDIR /app

COPY Cargo.toml Cargo.lock ./

RUN cargo chef prepare --recipe-path recipe.json

RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
    --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \
    --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
    cargo chef cook --release --recipe-path recipe.json

COPY . .

RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
    --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \
    --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
    cargo build --release --bin app

FROM ubuntu:24.04 AS runtime

RUN groupadd -g 1001 appgroup && \
    useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuser

COPY --from=build --chown=appuser:appgroup /app/target/release/app /usr/local/bin/app

USER appuser

ENTRYPOINT ["/usr/local/bin/app"]

Explanation of the Dockerfile

At a high level, here are the things we're optimizing in our Docker build for a Rust application:

  • Multi-stage builds with standard Rust base and Ubuntu runtime
  • cargo-chef for dependency separation and caching
  • sccache for individual compilation artifact caching
  • BuildKit cache mounts for persistent caching
  • Security optimizations with non-root users

Stage 1: FROM rust:1.90 AS build

FROM rust:1.90 AS build

RUN cargo install cargo-chef sccache --locked

We use the official Rust 1.90 image as the base for reliable builds. We install cargo-chef for dependency management and sccache for compilation artifact caching.

sccache configuration

ENV RUSTC_WRAPPER=sccache \
    SCCACHE_DIR=/sccache

We configure sccache by setting RUSTC_WRAPPER=sccache to wrap Rust compiler calls and SCCACHE_DIR=/sccache to specify the cache directory location.

Dependency preparation with cargo-chef

WORKDIR /app

COPY Cargo.toml Cargo.lock ./

RUN cargo chef prepare --recipe-path recipe.json

cargo-chef creates a recipe from the dependency files, enabling Docker to cache dependency builds separately from source code changes.

Dependency compilation with cache mounts

RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
    --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \
    --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
    cargo chef cook --release --recipe-path recipe.json

Dependencies are compiled using cargo-chef with three types of cache mounts:

  • Registry cache: Downloaded crate files from crates.io
  • Git cache: Git-based dependencies
  • sccache: Individual compilation artifacts

Application compilation

COPY . .

RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
    --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \
    --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
    cargo build --release --bin app

The application is compiled using the same cache mounts as dependency compilation. This ensures that sccache can reuse compilation artifacts between dependency and application builds.

Stage 2: FROM ubuntu:24.04 AS runtime

FROM ubuntu:24.04 AS runtime

RUN groupadd -g 1001 appgroup && \
    useradd -u 1001 -g appgroup -m -d /home/appuser -s /bin/bash appuser

COPY --from=build --chown=appuser:appgroup /app/target/release/app /usr/local/bin/app

USER appuser

ENTRYPOINT ["/usr/local/bin/app"]

The runtime stage uses Ubuntu 24.04 for a reliable runtime environment. We create a non-root user for security and copy the compiled binary from the build stage.

Understanding BuildKit Cache Mounts

Cache mounts are one of the most powerful features for optimizing Docker builds with Depot. This Dockerfile uses the following cache mount syntax:

RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
    --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \
    --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
    cargo chef cook --release --recipe-path recipe.json

Cache Mount Parameters Explained

  • type=cache: Specifies this is a cache mount that persists across builds.

  • Multiple cache targets:

    • /usr/local/cargo/registry: Cargo package registry cache
    • /usr/local/cargo/git: Git-based dependency cache
    • $SCCACHE_DIR: sccache compilation artifact cache (resolves to /sccache)
  • sharing=locked: Ensures exclusive access during compilation, preventing cache corruption.

Using cargo-chef for dependency management

cargo-chef solves a fundamental caching problem in Rust Docker builds. When you run cargo build, Docker treats the entire compilation as a single operation. Any change to your source code invalidates the cache and forces recompilation of all dependencies.

cargo-chef separates dependency compilation from source compilation by:

  1. cargo chef prepare: Analyzes Cargo.toml and Cargo.lock to create a dependency recipe
  2. cargo chef cook: Compiles only the dependencies based on the recipe
  3. cargo build: Compiles the application code using cached dependencies

This separation allows Docker to cache dependency compilation independently, only rebuilding dependencies when they actually change.

Using sccache for additional optimization

Even with cargo-chef separating dependencies from source code, compiling dependencies is still treated as a single operation. If a single dependency changes, all dependencies need to be recompiled.

sccache provides fine-grained caching at the compiler level by:

  1. Wrapping rustc calls: The RUSTC_WRAPPER=sccache environment variable intercepts compiler invocations
  2. Caching compilation artifacts: Individual object files and compilation outputs are cached
  3. Reusing artifacts: Unchanged code can reuse cached compilation results
  4. Cross-context sharing: Artifacts can be shared between dependency and application builds

This means only the specific crates that have changed need to be recompiled, while unchanged crates can reuse their cached artifacts.

For more information regarding Rust cache mounts, please visit the official sccache documentation and cargo-chef documentation.