This is a hack for incremental Docker builds with Rust.
If you follow a naive approach, that is, just copy your files and run cargo build
, Rust will download and compile all your dependencies each time you build your image.
The layer before the compilation step will change each time you modify your code and cargo build
has no option to only build dependencies.
First create a dummy Cargo package with a library target, copy manifest and lock file, and then perform a release build of the library. I use the library target to avoid having to supply dummy files for (potentially multiple) binary targets specified in the manifest. As long as your dependencies don't change, the layer created by the first release build can be re-used.
After this first step, copy all your files and perform another release build.
It's important to update the timestamp of your lib.rs
(by touch
ing the file), otherwise its last modification will predate the last build, so it won't be compiled and you'll run into errors.
RUN cargo init --lib --vcs none
COPY Cargo.toml Cargo.lock ./
RUN cargo build --release --lib
COPY . .
RUN touch src/lib.rs && cargo build --release
Here is a complete Dockerfile for a Rust project building a binary target bin-target
for reference.
The final image doesn't need the Rust toolchain because we simply copy the binary.
FROM rust:1.66-slim AS builder
WORKDIR /app
RUN cargo init --lib --vcs none
COPY Cargo.toml Cargo.lock ./
RUN cargo build --release --lib
COPY . .
RUN touch src/lib.rs && cargo build --release
FROM debian:bullseye-slim AS runtime
WORKDIR /app
COPY --from=builder /app/target/release/bin-target bin-target
ENTRYPOINT ["./bin-target"]
Not a hacker? If you don't mind pulling in another dependency, you can use cargo-chef instead, a tool by Luca Palmieri created exactly for this purpose.