Frontend Labs
⋯

67. Running Rust in containers and Kubernetes

67.1 A small, cache-friendly image

# syntax=docker/dockerfile:1
FROM rust:1 AS build
WORKDIR /src
# Build dependencies in their own layer: rebuilt only when Cargo.toml/Cargo.lock change.
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs && cargo build --release --locked && rm -rf src
COPY src ./src
RUN touch src/main.rs && cargo build --release --locked

FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=build /src/target/release/api /api
USER nonroot:nonroot
EXPOSE 3000
ENTRYPOINT ["/api"]

The final image contains glibc, CA certificates, and your binary: about 30 MB, with no shell and no package manager to exploit. With a musl build, FROM scratch also works (add CA certificates yourself if the program makes outbound TLS calls through the system store. webpki-roots builds them into the binary). cargo-chef is a more robust version of the dependency-layer trick above.

67.2 Behaving well under orchestration

Concern What to do in Rust Across the series
CPU limits std::thread::available_parallelism() reads the cgroup CPU quota on Linux. Tokio sizes its worker pool from it. Go needed GOMAXPROCS fixes for years OS Ch 75
Memory limits no GC heap to tune; memory is what you allocate. Bound caches and queues, and watch RSS against memory.max. The OOM killer sends SIGKILL with no warning OS Ch 76
SIGTERM graceful shutdown (§31.4, §47); finish within terminationGracePeriodSeconds OS Ch 74
PID 1 handle signals explicitly or use tini (§40.3) same
Probes /healthz (liveness: the process works) separate from /readyz (readiness: dependencies are reachable; returns 503 while draining) Go §67
Logs JSON to stdout: tracing_subscriber::fmt().json() (feature json) Go §65
Metrics and traces metrics + a Prometheus exporter, or OpenTelemetry via tracing-opentelemetry HTTPS Ch 23
Security context runAsNonRoot, readOnlyRootFilesystem, drop all capabilities. A static Rust binary needs none of them Security in Depth Ch 15

Real-world example. Teams that rewrite a Go or JVM service in Rust usually report two operational changes. Memory use becomes flat and predictable, because there is no GC heap growing to its limit. Tail latency (p99, p999) improves more than median latency, because there are no collector pauses. Discord's 2020 post about rewriting its "Read States" service from Go to Rust describes exactly this pattern of latency spikes from GC every two minutes, which disappeared after the rewrite. The counterpoint: Go's GC has improved since then, and a well-tuned Go service is often good enough. Measure before you rewrite.


Generated from real-life-rust-guide.md · 8 Oct 2026 16:36