Cache Rust dependencies with Docker build

Seems like you are not alone in your endeavor to cache rust dependencies via the docker build process. Here is a great article that helps you along the way: https://blog.mgattozzi.dev/caching-rust-docker-builds/

The gist of it is you need a dummy.rs and your Cargo.toml first, then build it to cache the dependencies and then copy your application source later in order to not invalidate the cache with every build.

Dockerfile

FROM rust
WORKDIR /var/www/app
COPY dummy.rs .
COPY Cargo.toml .
RUN sed -i 's#src/main.rs#dummy.rs#' Cargo.toml
RUN cargo build --release
RUN sed -i 's#dummy.rs#src/main.rs#' Cargo.toml
COPY . .
RUN cargo build --release
CMD ["target/release/app"]

CMD application name "app" is based on what you have specified in your Cargo.toml for your binary.

dummy.rs

fn main() {}

Cargo.toml

[package]
name = "app"
version = "0.1.0"
authors = ["..."]
[[bin]]
name = "app"
path = "src/main.rs"

[dependencies]
actix-web = "1.0.0"

src/main.rs

extern crate actix_web;

use actix_web::{web, App, HttpServer, Responder};

fn index() -> impl Responder {
    "Hello world"
}

fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(web::resource("/").to(index)))
        .bind("0.0.0.0:8080")?
        .run()
}

You can use cargo-chef to leverage Docker layer caching using a multi-stage build.

FROM rust as planner
WORKDIR app
# We only pay the installation cost once, 
# it will be cached from the second build onwards
RUN cargo install cargo-chef 
COPY . .
RUN cargo chef prepare  --recipe-path recipe.json

FROM rust as cacher
WORKDIR app
RUN cargo install cargo-chef
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json

FROM rust as builder
WORKDIR app
COPY . .
# Copy over the cached dependencies
COPY --from=cacher /app/target target
RUN cargo build --release --bin app

FROM rust as runtime
WORKDIR app
COPY --from=builder /app/target/release/app /usr/local/bin
ENTRYPOINT ["./usr/local/bin/app"]

It does not require Buildkit and works for both simple projects and workspaces. You can find more details in the release announcement.


With the (still experimental) Docker Buildkit you can finally properly cache build folders during a docker build step:

Dockerfile:

# syntax=docker/dockerfile:experimental
from rust
ENV HOME=/home/root
WORKDIR $HOME/app
[...]
RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/home/root/app/target \
    cargo build --release

Then run:

DOCKER_BUILDKIT=1 docker build . --progress=plain

Subsequent docker builds will reuse the cargo and target folders from cache, hence massively speeding up your builds.

To clear the docker cache mount: docker builder prune --filter type=exec.cachemount

If you don't see proper caching: Make sure to confirm the location of your cargo/registry and target folders in the docker image, if you don't see proper caching.

Minimal working Example: https://github.com/benmarten/sccache-docker-test/tree/no-sccache