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
# 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"].git
target/
.env
Dockerfile
.dockerignoreBefore you build
- Commit Cargo.lock so dependency versions are reproducible.
- Name the package app, or change the binary path in the COPY line to match your package name.
- Bind the server to 0.0.0.0:8080 rather than localhost.
How this Dockerfile works
FROM rust:1-slim-bookworm AS buildBuilds on Debian bookworm on purpose: its glibc version matches the distroless debian12 runtime, so the binary's dynamic links resolve at runtime.
COPY Cargo.toml Cargo.lock ./Copying only the manifests first means the dependency build below is cached until your dependencies change.
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf srcCompiles 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.
RUN touch src/main.rs && cargo build --releaseAfter the real source is copied, touch makes Cargo rebuild your crate instead of reusing the stub binary. Only your code compiles in this step.
FROM gcr.io/distroless/cc-debian12:nonrootDistroless cc contains glibc and the C runtime libraries a Rust binary needs, plus CA certificates, but no shell or package manager.
COPY --from=build /src/target/release/app /appOnly the compiled release binary is copied into the runtime image.
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
docker build -t rust-app .
docker run --rm -p 8080:8080 rust-appThe 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.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| Cargo build + distroless cc (nonroot) | gcr.io/distroless/cc-debian12:nonroot | 25.5 MB | Passing |
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.