Introdução: Por que Docker e Rust são a dupla perfeita
Rust é uma linguagem de programação de sistemas moderna, conhecida por seu desempenho e segurança de memória. No entanto, para entregar uma aplicação Rust ao usuário final ou à nuvem, é necessário resolver o problema do ambiente. É aqui que o Docker entra em ação. A conteinerização permite empacotar uma aplicação Rust junto com todas as suas dependências em um contêiner leve e isolado. Neste artigo, vamos explorar como criar uma imagem Docker eficiente e mínima para um projeto Rust, usando as melhores práticas.
1. Fundamentos do Docker para desenvolvedores Rust
Docker é uma plataforma para desenvolvimento, entrega e execução de aplicações em contêineres. O contêiner inclui a própria aplicação e tudo o que é necessário para seu funcionamento: bibliotecas, utilitários do sistema e configurações. Para Rust, isso é especialmente relevante, pois o binário compilado não depende de um runtime (ao contrário de Python ou Node.js), permitindo criar imagens muito pequenas.
Os principais componentes que você precisará:
- Dockerfile — arquivo de texto com instruções para construir a imagem.
- .dockerignore — arquivo que exclui arquivos desnecessários do contexto de build.
- Docker Compose (opcional) — para orquestrar múltiplos contêineres.
Antes de começar, certifique-se de ter instalado o Docker Desktop ou o Docker Engine.
2. Criando um Dockerfile para uma aplicação Rust
Vamos considerar um exemplo simples de servidor web em Rust usando o framework actix-web. Nosso arquivo principal main.rs é assim:
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder { "Hello, Docker and Rust!"}
#[actix_web::main]async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().route("/", web::get().to(index)) }) .bind("0.0.0.0:8080")? .run() .await}Agora vamos criar o Dockerfile. A técnica mais importante aqui é o multi-stage build. Ela permite primeiro compilar a aplicação em um ambiente com o conjunto completo de ferramentas Rust e, em seguida, copiar o binário pronto para uma imagem final mínima.
// ---- Primeiro estágio: build ----FROM rust:1.75 as builder
WORKDIR /app
// Copiamos os manifestos para cachear as dependênciasCOPY Cargo.toml Cargo.lock ./
// Criamos um main.rs vazio para compilar as dependênciasRUN mkdir src && echo "fn main() {}" > src/main.rsRUN cargo build --release 2>/dev/null || true
// Agora copiamos o código fonte realCOPY src ./src
// Compilamos o binário final (o cache das dependências já existe)RUN cargo build --release
// ---- Segundo estágio: imagem final ----FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
// Copiamos o binário do primeiro estágioCOPY --from=builder /app/target/release/my_rust_app .
EXPOSE 8080
CMD ["./my_rust_app"]Observe o truque com o main.rs vazio. Isso permite que o Docker armazene em cache as dependências compiladas (crates) e não as recompile toda vez que você alterar apenas o código fonte. Isso acelera significativamente as compilações subsequentes.
2.1. Otimizando o tamanho da imagem
A imagem final baseada em debian:bookworm-slim pesa cerca de 80-100 MB. No entanto, se você quiser uma imagem realmente mínima, pode usar scratch (imagem vazia) ou alpine. Para aplicações Rust compiladas estaticamente, esta é a opção ideal. Para isso, é necessário alterar o target de compilação:
// No primeiro estágio (builder):RUN rustup target add x86_64-