Docker et C++ : conteneurisation d'applications de A à Z
La conteneurisation est devenue une partie intégrante du développement logiciel moderne. Docker permet d'empaqueter les applications avec leurs dépendances, assurant reproductibilité et portabilité. Dans cet article, nous allons détailler comment conteneuriser des applications C++ avec Docker — des concepts de base aux techniques avancées d'optimisation.
1. Pourquoi conteneuriser des applications C++ ?
De nombreux développeurs pensent à tort que Docker n'est pertinent que pour les langages interprétés (Python, Node.js). Cependant, les applications C++ bénéficient également de la conteneurisation :
- Isolation des dépendances : les bibliothèques, compilateurs et utilitaires système n'entrent pas en conflit avec d'autres projets
- Reproductibilité des builds : un environnement identique à toutes les étapes (dev, CI/CD, production)
- Simplification du déploiement : l'image contient tout le nécessaire pour l'exécution
- Passage à l'échelle : facile de lancer plusieurs instances via Docker Compose ou Kubernetes
Examinons le processus avec un exemple pratique.
2. Dockerfile de base pour C++
Commençons par une application C++ simple. Créons un fichier main.cpp :
#include <iostream>
int main() { std::cout << "Hello, Dockerized C++!" << std::endl; return 0;}Écrivons maintenant un Dockerfile pour la compilation et l'exécution :
# Image de base avec compilateurFROM gcc:latest AS builder
WORKDIR /appCOPY main.cpp .
# CompilationRUN g++ -o hello main.cpp
# Image finale minimaleFROM alpine:latest
WORKDIR /appCOPY --from=builder /app/hello .
CMD ["./hello"]Construisons et exécutons :
docker build -t cpp-hello .docker run cpp-helloRésultat : Hello, Dockerized C++!
Explication :
- La première étape (builder) utilise l'image complète gcc pour la compilation
- La deuxième étape utilise Alpine Linux minimal pour l'image finale
- L'image finale contient uniquement le binaire compilé — cela économise de l'espace
3. Compilation avec CMake et dépendances
Les projets réels utilisent souvent CMake et des bibliothèques externes. Considérons un exemple avec la bibliothèque fmt.
Structure du projet :
myapp/├── CMakeLists.txt├── main.cpp└── DockerfileCMakeLists.txt :
cmake_minimum_required(VERSION 3.14)project(MyApp)
find_package(fmt REQUIRED)
add_executable(myapp main.cpp)target_link_libraries(myapp fmt::fmt)main.cpp :
#include <fmt/core.h>
int main() { fmt::println("Hello from {}!", "C++ with Docker"); return 0;}Dockerfile avec build multi-stage :
# Étape 1 : Compilation des dépendances et de l'applicationFROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y \ build-essential cmake libfmt-dev \ && rm -rf /var/lib/apt/lists/*
WORKDIR /appCOPY CMakeLists.txt main.cpp ./RUN cmake -B build && cmake --build build
# Étape 2 : Image finaleFROM ubuntu:22.04
RUN apt-get update && apt-get install -y libfmt9 \ && rm -rf /var/lib/apt/lists/*
WORKDIR /appCOPY --from=builder /app/build/myapp .
CMD ["./myapp"]Points importants :
- Les dépendances de compilation (build-essential, cmake) ne se retrouvent pas dans l'image finale
- Les dépendances d'exécution (libfmt9) sont installées séparément
- La taille de l'image finale est considérablement réduite
4. Optimisation des images Docker pour C++
L'optimisation est particulièrement importante pour les applications C++, car les images avec compilateurs peuvent