Rust Dockerfile for Axum: Cached Deps, Distroless Nonroot

This Rust Dockerfile builds an Axum service in release mode on rust:1-slim-bookworm and ships the binary on gcr.io/distroless/cc-debian12:nonroot. A stub main.rs trick caches compiled dependencies in their own layer, so code changes rebuild in a fraction of the time. The service runs as a nonroot user on port 8080.

CI status
Build passing
Image size (uncompressed)
25.5 MB
Build time
37 s
Runtime image
gcr.io/distroless/cc-debian12:nonroot
Port
8080
Last built
2026-10-15
Dockerfile
# syntax=docker/dockerfile:1
FROM rust:1-slim-bookworm AS build
WORKDIR /src
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src
COPY src ./src
RUN touch src/main.rs && cargo build --release

FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=build /src/target/release/app /app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]
.dockerignore
.git
target/
.env
Dockerfile
.dockerignore

Before you build

How this Dockerfile works

  1. FROM rust:1-slim-bookworm AS build

    Builds on Debian bookworm on purpose: its glibc version matches the distroless debian12 runtime, so the binary's dynamic links resolve at runtime.

  2. COPY Cargo.toml Cargo.lock ./

    Copying only the manifests first means the dependency build below is cached until your dependencies change.

  3. RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src

    Compiles every dependency against an empty main.rs. This slow step is cached as its own layer, which is the main speed-up of this file.

  4. RUN touch src/main.rs && cargo build --release

    After the real source is copied, touch makes Cargo rebuild your crate instead of reusing the stub binary. Only your code compiles in this step.

  5. FROM gcr.io/distroless/cc-debian12:nonroot

    Distroless cc contains glibc and the C runtime libraries a Rust binary needs, plus CA certificates, but no shell or package manager.

  6. COPY --from=build /src/target/release/app /app

    Only the compiled release binary is copied into the runtime image.

  7. USER nonroot:nonroot EXPOSE 8080 ENTRYPOINT ["/app"]

    Runs the service as the nonroot user built into the image, using exec form because there is no shell.

Build and run

terminal
docker build -t rust-app .
docker run --rm -p 8080:8080 rust-app

The first build compiles all dependencies and takes longest. Change a handler and rebuild: Docker reuses the dependency layer and only your crate is recompiled.

Other Rust Dockerfile variants

Distroless cc suits most Rust services, which link against glibc by default. Fully static musl builds can run on scratch for an even smaller image, and cargo-chef offers more precise dependency caching for workspaces. Verified variants appear in the table below.

VariantRuntime imageImage sizeCI
Cargo build + distroless cc (nonroot)gcr.io/distroless/cc-debian12:nonroot25.5 MBPassing

Troubleshooting

/app: /lib/x86_64-linux-gnu/libc.so.6: version 'GLIBC_2.38' not found
The binary was built on a newer Debian than the runtime image. Keep the build stage on a bookworm tag to match distroless debian12.
The build still recompiles all dependencies after a small change
Cargo.toml or Cargo.lock changed, or a dependency was added. The cached layer only survives while the manifests stay identical.
COPY failed: /src/target/release/app: not found
Your package has a different name. Set name = "app" in Cargo.toml, or change the COPY source to target/release/<your-package>.

FAQ

Why distroless cc instead of static?
Rust binaries link to glibc by default. Distroless cc includes glibc and libgcc, while distroless static has no libc and only runs fully static binaries.
Should I use cargo-chef instead of the stub trick?
cargo-chef handles workspaces with several crates more precisely. For a single-crate service, the stub main.rs approach achieves similar caching with no extra tool.
How do I debug without a shell?
Use the debug tag of the distroless image temporarily, which includes busybox, or attach an ephemeral debug container in Kubernetes.