Einleitung: Warum ein Rust-Entwickler Docker braucht
Rust ist bekannt für seine Leistung und Speichersicherheit, aber selbst die effizienteste Binärdatei benötigt die richtige Umgebung zum Ausführen. Docker löst dieses Problem, indem es die Anwendung zusammen mit allen Abhängigkeiten in einen isolierten Container packt. In diesem Artikel werden wir untersuchen, wie man Rust-Anwendungen mit Docker containerisiert, unter Verwendung moderner Techniken, einschließlich Multi-Stage-Builds und Minimierung der Image-Größe.
Die Containerisierung von Rust-Projekten ist besonders vorteilhaft, da Rust in eine statische Binärdatei kompiliert wird – dies ermöglicht die Erstellung minimaler Images (alpine oder sogar scratch), die schneller und sicherer laufen als vergleichbare Anwendungen in Python oder Node.js.
1. Grundlagen: Dockerfile für eine einfache Rust-Anwendung
Beginnen wir mit einem einfachen Webserver in Rust unter Verwendung des Frameworks actix-web. Erstellen wir ein grundlegendes Dockerfile.
1.1. Projektstruktur
my_rust_app/├── Cargo.toml├── Cargo.lock└── src/ └── main.rs1.2. Beispielcode (main.rs)
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]async fn hello() -> impl Responder { HttpResponse::Ok().body("Hallo aus dem 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. Einfaches 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"]Erklärung: Die erste Stufe (builder) kompiliert die Anwendung, die zweite Stufe kopiert nur die Binärdatei in ein sauberes Debian-Image. Dies reduziert die Größe von ~2 GB auf ~100 MB.
2. Multi-Stage-Build: Optimierung der Image-Größe
Multi-Stage-Build ist eine Schlüsseltechnik für Rust. Sie ermöglicht es, die Build-Umgebung (mit Compiler, Abhängigkeiten) von der Laufzeitumgebung zu trennen.
2.1. Fortschrittliches Dockerfile mit Caching
Um wiederholte Builds zu beschleunigen, cachen wir die Abhängigkeiten:
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"]Warum distroless? Das distroless-Image enthält nur glibc und grundlegende Bibliotheken – ohne shell, apt, ls. Dies erhöht die Sicherheit und reduziert die Größe auf ~30 MB.
2.2. Minimales Image auf scratch-Basis
Wenn Ihre Anwendung libc nicht verwendet (z. B. mit #![no_std] geschrieben), kann scratch verwendet werden:
FROM rust:1.77 AS builderWORKDIR /appCOPY . .RUN cargo build --release
FROM scratchCOPY --from=builder /app/target/release/my_rust_app /appEXPOSE 8080CMD ["/app"]Ein solches Image wiegt weniger als 10 MB!
3. Docker Compose: Multi-Container-Anwendung
Oft arbeitet eine Rust-Anwendung zusammen mit PostgreSQL, Redis oder Nginx. Wir verwenden docker-compose.yml:
3.1. Beispiel 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