Docker and Rust: Containerizing Applications for Production

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

Introduction: Why does a Rust developer need Docker?

Rust is famous for its performance and memory safety, but even the most efficient binary file requires the correct environment to run. Docker solves this problem by packaging the application together with all its dependencies into an isolated container. In this article, we will break down how to containerize Rust applications using Docker, employing modern techniques, including multi-stage builds and minimizing image size.

Containerizing Rust projects is especially beneficial because Rust compiles into a static binary — this allows you to create minimal images (alpine or even scratch) that run faster and more securely than their Python or Node.js counterparts.



1. Basics: Dockerfile for a simple Rust application

Let's start with a simple web server in Rust using the actix-web framework. We'll create a basic Dockerfile.



1.1. Project structure

my_rust_app/├── Cargo.toml├── Cargo.lock└── src/    └── main.rs


1.2. Code example (main.rs)

use actix_web::{get, App, HttpResponse, HttpServer, Responder};

#[get("/")]async fn hello() -> impl Responder { HttpResponse::Ok().body("Hello from Rust container!")}

#[actix_web::main]async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new().service(hello)) .bind("0.0.0.0:8080")? .run() .await}


1.3. Simple Dockerfile

FROM rust:1.77 AS builderWORKDIR /appCOPY Cargo.toml Cargo.lock ./RUN mkdir src && echo "fn main() {}" > src/main.rsRUN cargo build --releaseCOPY src/ src/RUN cargo build --release

FROM debian:bookworm-slimCOPY --from=builder /app/target/release/my_rust_app /usr/local/bin/EXPOSE 8080CMD ["my_rust_app"]

Explanation: The first stage (builder) compiles the application, the second stage copies only the binary into a clean Debian image. This reduces the size from ~2 GB to ~100 MB.



2. Multi-stage build: optimizing image size

Multi-stage build is a key technique for Rust. It allows separating the build environment (with compiler, dependencies) from the runtime environment.



2.1. Advanced Dockerfile with caching

To speed up repeated builds, we cache the dependencies:

FROM rust:1.77 AS builderWORKDIR /appCOPY Cargo.toml Cargo.lock ./RUN mkdir src && echo "fn main() {}" > src/main.rsRUN cargo build --release 2>/dev/null || trueCOPY src/ src/RUN cargo build --release && \    cp target/release/my_rust_app /app/rust_app

FROM gcr.io/distroless/cc-debian12COPY --from=builder /app/rust_app /app/rust_appEXPOSE 8080CMD ["/app/rust_app"]

Why distroless? The distroless image contains only glibc and basic libraries — without shell, apt, ls. This increases security and reduces the size to ~30 MB.



2.2. Minimal image on scratch

If your application does not use libc (for example, written with #![no_std]), you can use scratch:

FROM rust:1.77 AS builderWORKDIR /appCOPY . .RUN cargo build --release

FROM scratchCOPY --from=builder /app/target/release/my_rust_app /appEXPOSE 8080CMD ["/app"]

Such an image weighs less than 10 MB!



3. Docker Compose: multi-container application

Often a Rust application works in conjunction with PostgreSQL, Redis or Nginx. We use docker-compose.yml:



3.1. Example docker-compose.yml

version: '3.8'services:  app:    build: .    ports:      - "8080:8080"    environment:      - DATABASE_URL=postgres://user:pass@db:5432/mydb      - RUST_LOG=info    depends_on:      - db

db: image: postgres:16-alpine ```

Blogs

Book Recommendations