Introdução: por que containerizar aplicações C++?
O desenvolvimento em C++ é tradicionalmente associado ao problema "funciona na minha máquina". Diferentes versões de compiladores, bibliotecas e dependências do sistema transformam a implantação em uma dor de cabeça. O Docker resolve esse problema: você empacota a aplicação com todo o ambiente em um contêiner leve que é executado da mesma forma em qualquer sistema.
Neste artigo, vamos analisar o processo completo de containerização de uma aplicação C++ — desde um Dockerfile simples até a construção em múltiplos estágios com otimização do tamanho da imagem.
1. Fundamentos: Dockerfile para aplicação C++
Vamos começar com o exemplo mais simples. Suponha que temos um programa em C++ que exibe "Hello, Docker!".
Código-fonte (hello.cpp)
#include <iostream>
int main() { std::cout << "Hello, Docker!" << std::endl; return 0;}Primeiro Dockerfile
# Usamos a imagem com GCCFROM gcc:13.2
# Definimos o diretório de trabalhoWORKDIR /app
# Copiamos os fontesCOPY hello.cpp .
# CompilamosRUN g++ -o hello hello.cpp
# ExecutamosCMD ["./hello"]Vamos construir e executar:
docker build -t cpp-hello .docker run cpp-helloA desvantagem dessa abordagem é que a imagem pesa cerca de 1,2 GB devido ao conjunto completo de ferramentas GCC. Para produção, isso é inaceitável.
2. Construção em múltiplos estágios (Multi-stage build)
A melhor prática é separar a compilação da execução. No primeiro estágio, compilamos a aplicação com o conjunto completo de ferramentas; no segundo, copiamos apenas o binário final para uma imagem mínima e limpa.
Dockerfile otimizado
# Estágio 1: compilaçãoFROM gcc:13.2 AS builder
WORKDIR /app
COPY hello.cpp .
RUN g++ -static -o hello hello.cpp
# Estágio 2: execuçãoFROM alpine:3.19
WORKDIR /app
# Copiamos o binário compilado estaticamenteCOPY --from=builder /app/hello .
CMD ["./hello"]O ponto-chave é a flag -static. Ela inclui todas as dependências no binário, permitindo executá-lo em um Alpine puro sem bibliotecas adicionais. O tamanho da imagem diminui de 1,2 GB para ~6 MB!
3. Projeto real: CMake + dependências
Vamos considerar um caso mais complexo: uma aplicação que usa a biblioteca Boost e é compilada com CMake.
Estrutura do projeto
myapp/├── CMakeLists.txt├── src/│ └── main.cpp└── DockerfileCMakeLists.txt
cmake_minimum_required(VERSION 3.20)project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Boost REQUIRED COMPONENTS system)
add_executable(myapp src/main.cpp)target_link_libraries(myapp Boost::system)main.cpp
#include <iostream>#include <boost/asio.hpp>
int main() { boost::asio::io_context io; std::cout << "Boost.Asio funciona no contêiner!" << std::endl; return 0;}Dockerfile com dependências
# Estágio 1: compilaçãoFROM ubuntu:22.04 AS builder
# Instalamos as dependências de compilaçãoRUN apt-get update && apt-get install -y \ build-essential \ cmake \ libboost-system-dev \ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copiamos os fontesCOPY CMakeLists.txt .COPY src/ src/
# CompilamosRUN mkdir build && cd build && \ cmake .. -DCMAKE_BUILD_TYPE=Release && \ make -j$(nproc)
# Estágio 2: execuçãoFROM ubuntu:22.04
# Instalamos apenas as dependências de runtimeRUN apt-get update && apt-get install -y \ libboost-system1.74.0 \ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/build/myapp .
CMD ["./myapp"]Observe: no segundo estágio, instalamos apenas a versão de runtime do Boost (libboost-system1.74.0).