Minimal Docker Images
Rust services fit small containers: multi-stage builds compile in a fat image, then copy a single binary into distroless or scratch (with CA certs if using HTTPS).
Recipe
# syntax=docker/dockerfile:1
FROM rust:1.97-bookworm AS builder
WORKDIR /app
COPY . .
RUN cargo build --release --locked
FROM gcr.io/distroless/cc-debian12
COPY --from=builder /app/target/release/my-service /my-service
USER nonroot:nonroot
ENTRYPOINT ["/my-service"]When to reach for this:
- Production Kubernetes deployments
- Reducing attack surface and image pull time
- Compliance requirements forbidding shells in runtime images
Working Example
FROM rust:1.97-bookworm AS builder
WORKDIR /app
# Cache dependencies
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release && rm -rf src
COPY . .
RUN cargo build --release --locked
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/my-service /usr/local/bin/my-service
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/my-service"]What this demonstrates:
- Dependency layer caching speeds rebuilds
- Runtime image has only binary plus CA bundle
- Non-root user in distroless variant
Deep Dive
Image Size Levers
| Technique | Savings |
|---|---|
| Multi-stage | No Rust toolchain in runtime |
strip = true in release profile | Smaller binary |
musl static binary + scratch | No libc layer |
LTO lto = "thin" | Smaller binary, slower compile |
HTTPS from Distroless
Distroless cc or static variants include CA roots; pure scratch needs manual ca-certificates copy.
Gotchas
- Running as root - container escape impact. Fix:
USER nonrootor distroless nonroot. - Missing
ca-certificates- TLS fails mysteriously. Fix: copy certs or use distroless cc. - Compiling in runtime stage - huge images. Fix: strict multi-stage separation.
- No shell for debugging - harder ad-hoc inspection. Fix: ephemeral debug pods with toolkit image.
- Stale
Cargo.lockin Docker - non-reproducible builds. Fix:--lockedand commit lockfile.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
wolfi / chainguard | Hardened minimal base | Team standardized on distroless |
Full debian:slim + shell | Ops wants docker exec debug | Strict minimal policy |
kaniko / buildkit in CI | No Docker daemon on runner | Local-only workflows |
FAQs
scratch vs distroless?
scratch is empty; you must supply certs and dynamic linker if not fully static. Distroless includes libc.
How big should the image be?
Single-service Axum binaries often land 15-40 MB compressed with slim bases; single-digit MB with musl+scratch.
Workspace builds in Docker?
Copy full workspace; cargo build -p my-service --release from root.
Distroless and glibc?
Match builder Debian version to distroless tag to avoid glibc mismatches.
Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.