Docker et C++ : conteneurisation d'applications
Dans le développement logiciel moderne, la conteneurisation est devenue le standard de facto pour le déploiement et la mise à l'échelle des applications. Docker permet d'empaqueter une application avec toutes ses dépendances, bibliothèques et configurations dans un conteneur isolé. Pour les développeurs C++, c'est particulièrement pertinent car les applications C++ nécessitent souvent des versions spécifiques de compilateurs, de bibliothèques système et des processus de construction complexes. Dans cet article, nous allons voir comment conteneuriser efficacement des applications C++ avec Docker.
Pourquoi Docker pour le C++ ?
Les applications C++ sont traditionnellement considérées comme complexes à déployer en raison des problèmes de portabilité des fichiers binaires entre différentes distributions Linux. Docker résout ce problème en fournissant un environnement d'exécution unifié. Les principaux avantages de l'utilisation de Docker pour les projets C++ :
- Reproductibilité des builds – des versions fixes de compilateurs et de bibliothèques garantissent que le code se compile de manière identique sur n'importe quelle machine.
- Isolation des dépendances – différents projets peuvent utiliser différentes versions d'une même bibliothèque sans conflits.
- Simplification du CI/CD – le conteneur peut être utilisé comme un artefact unique pour les tests et le déploiement.
- Réduction de la taille – les builds multi-stage permettent de créer des images minimales contenant uniquement le fichier exécutable.
Création d'un Dockerfile pour une application C++
Considérons un exemple de base. Nous avons une simple application C++ qui affiche "Hello, Docker!".
Code source
// main.cpp#include <iostream>
int main() { std::cout << "Hello, Docker!" << std::endl; return 0;}Dockerfile de base
Créons un Dockerfile simple qui utilise l'image gcc pour la compilation et l'exécution :
# Utilisons l'image officielle GCCFROM gcc:latest
# Définissons le répertoire de travailWORKDIR /app
# Copions le code sourceCOPY main.cpp .
# Compilons l'applicationRUN g++ -o hello main.cpp
# Exécutons l'applicationCMD ["./hello"]Construisons et exécutons le conteneur :
docker build -t cpp-hello .docker run cpp-helloSortie : Hello, Docker!
Build multi-stage : optimisation de la taille de l'image
Le problème de l'approche de base est que l'image finale contient le compilateur, les fichiers d'en-tête et d'autres outils qui ne sont pas nécessaires lors de l'exécution. Pour un environnement de production, c'est superflu. La solution est le build multi-stage, qui permet de séparer l'étape de compilation de l'étape d'exécution.
Dockerfile optimisé
# Étape de compilationFROM gcc:latest AS builder
WORKDIR /appCOPY main.cpp .RUN g++ -static -o hello main.cpp
# Étape finaleFROM alpine:latest
WORKDIR /appCOPY --from=builder /app/hello .
CMD ["./hello"]Dans cet exemple, nous utilisons la liaison statique (-static) pour que le fichier exécutable ne dépende pas des bibliothèques dynamiques. L'image finale ne pèse qu'environ 5 Mo au lieu de 1,2 Go pour l'image avec GCC.
Conteneurisation d'un projet avec CMake et dépendances
Les projets réels utilisent généralement des systèmes de build (CMake) et des dépendances externes. Considérons un exemple avec la bibliothèque nlohmann/json.
Structure du projet
project/├── CMakeLists.txt├── main.cpp├── external/│ └── json.hpp└── DockerfileCMakeLists.txt
cmake_minimum_required(VERSION 3.10)project(JsonApp)
set(CMAKE_CXX_STANDARD 17)
add_executable(app main.cpp)target_include_directories(app PRIVATE external)main.cpp
#include <iostream>#include "json.hpp"
using json = nlohmann::json;
int main() { json data = {{"name"