Docker and Rust: Advanced-Level Application Containerization

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Docker and Rust: Advanced-Level Application Containerization



Containerizing Rust applications with Docker is not just about packaging code into an image. At an advanced level, it is the art of creating minimal, fast, and secure containers that are perfectly suited for a production environment. Rust, as a systems programming language, compiles to native code, which provides unique opportunities for optimizing Docker images. In this article, we will explore techniques that go beyond basic docker build and show how to create efficient containers for Rust applications.



1. Multi-stage Build for Rust



Multi-stage build is a cornerstone of advanced Rust containerization. It allows you to separate the build environment (with the compiler, dependencies, and tools) from the final image, which contains only the compiled binary and necessary runtime dependencies.



Consider an example Dockerfile for a web server using actix-web:



# Stage 1: BuildFROM rust:1.75-slim-bullseye AS builder

WORKDIR /app

# Copy only dependency files for layer cachingCOPY Cargo.toml Cargo.lock ./

# Create an empty main.rs to compile dependenciesRUN mkdir src && echo "fn main() {}" > src/main.rsRUN cargo build --release 2>/dev/null || true

# Now copy the actual source codeCOPY src ./src

# Rebuild only changed files (dependency cache is untouched)RUN cargo build --release

# Stage 2: Final imageFROM debian:bullseye-slim

RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ libssl1.1 \ && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/my_app /usr/local/bin/my_app

USER nobodyEXPOSE 8080

CMD ["/usr/local/bin/my_app"]


Key optimizations:

  • Dependency caching: Only Cargo.toml and Cargo.lock are copied first, then dependencies are compiled. This allows Docker to use the layer cache if the manifest files haven't changed.
  • Minimal final image: debian:bullseye-slim is used instead of the full Rust image. This reduces the size from ~1.5 GB to ~150 MB.
  • Security: The application runs as the nobody user, not as root.


2. Minimizing Image Size: From Alpine to scratch



Rust applications do not require a runtime environment such as a Java virtual machine or a Python interpreter. This allows the use of minimal base images.



Option with Alpine Linux:

FROM alpine:3.19 AS builderRUN apk add --no-cache musl-dev# ... build ...

FROM alpine:3.19RUN apk add --no-cache ca-certificatesCOPY --from=builder /app/target/release/my_app /my_appCMD ["/my_app"]


Option with scratch (empty image):

FROM alpine:3.19 AS builderRUN apk add --no-cache musl-dev# Build with static linkingRUN cargo build --release --target x86_64-unknown-linux-musl

FROM scratchCOPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my_app /my_appCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/CMD ["/my_app"]


Image size comparison:

  • debian:bullseye-slim: ~80-150 MB (depends on the number of system libraries)
  • alpine:3.19: ~10-20 MB (uses musl libc instead of glibc)
  • scratch: ~5-10 MB (only binary and certificates)


Important: When using scratch, you must ensure that your application does not require dynamic libraries. To do this, use static linking with the target

Blogs

Book Recommendations