Introduction : pourquoi un développeur Rust a-t-il besoin de Docker ?
Rust est réputé pour ses performances et sa sécurité mémoire, mais même le binaire le plus efficace nécessite un environnement approprié pour s'exécuter. Docker résout ce problème en empaquetant l'application avec toutes ses dépendances dans un conteneur isolé. Dans cet article, nous allons voir comment conteneuriser des applications Rust avec Docker, en utilisant des techniques modernes, notamment la construction multi-étapes et la minimisation de la taille de l'image.
La conteneurisation de projets Rust est particulièrement avantageuse car Rust compile en un binaire statique — cela permet de créer des images minimales (alpine ou même scratch), qui s'exécutent plus rapidement et de manière plus sécurisée que leurs équivalents en Python ou Node.js.
1. Les bases : Dockerfile pour une application Rust simple
Commençons par un serveur web simple en Rust utilisant le framework actix-web. Créons un Dockerfile de base.
1.1. Structure du projet
my_rust_app/├── Cargo.toml├── Cargo.lock└── src/ └── main.rs1.2. Exemple de code (main.rs)
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]async fn hello() -> impl Responder { HttpResponse::Ok().body("Bonjour depuis le conteneur Rust !")}
#[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. Dockerfile simple
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"]Explication : la première étape (builder) compile l'application, la deuxième étape copie uniquement le binaire dans une image Debian propre. Cela réduit la taille d'environ 2 Go à environ 100 Mo.
2. Construction multi-étapes : optimisation de la taille de l'image
La construction multi-étapes (multi-stage build) est une technique clé pour Rust. Elle permet de séparer l'environnement de construction (avec le compilateur, les dépendances) de l'environnement d'exécution.
2.1. Dockerfile avancé avec mise en cache
Pour accélérer les constructions répétées, mettons en cache les dépendances :
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"]Pourquoi distroless ? L'image distroless contient uniquement glibc et les bibliothèques de base — sans shell, apt, ls. Cela améliore la sécurité et réduit la taille à environ 30 Mo.
2.2. Image minimale sur scratch
Si votre application n'utilise pas libc (par exemple, écrite avec #![no_std]), vous pouvez utiliser 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"]Une telle image pèse moins de 10 Mo !
3. Docker Compose : application multi-conteneurs
Souvent, une application Rust fonctionne en tandem avec PostgreSQL, Redis ou Nginx. Utilisons docker-compose.yml :
3.1. Exemple de 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