Docker und C++: Containerisierung von Anwendungen von A bis Z
Die Containerisierung ist zu einem wesentlichen Bestandteil der modernen Softwareentwicklung geworden. Docker ermöglicht es, Anwendungen zusammen mit ihren Abhängigkeiten zu verpacken und so Reproduzierbarkeit und Portabilität zu gewährleisten. In diesem Artikel werden wir im Detail besprechen, wie man C++-Anwendungen mit Docker containerisiert – von grundlegenden Konzepten bis hin zu fortgeschrittenen Optimierungstechniken.
1. Warum C++-Anwendungen containerisieren?
Viele Entwickler glauben fälschlicherweise, dass Docker nur für interpretierte Sprachen (Python, Node.js) relevant ist. Doch auch C++-Anwendungen profitieren von der Containerisierung:
- Isolation von Abhängigkeiten: Bibliotheken, Compiler und Systemdienstprogramme geraten nicht in Konflikt mit anderen Projekten
- Reproduzierbarkeit von Builds: identische Umgebung in allen Phasen (Dev, CI/CD, Production)
- Vereinfachung des Deployments: das Image enthält alles, was zum Ausführen benötigt wird
- Skalierung: einfaches Ausführen mehrerer Instanzen über Docker Compose oder Kubernetes
Betrachten wir den Prozess anhand eines praktischen Beispiels.
2. Basis-Dockerfile für C++
Beginnen wir mit einer einfachen C++-Anwendung. Erstellen wir die Datei main.cpp:
#include <iostream>
int main() { std::cout << "Hello, Dockerized C++!" << std::endl; return 0;}Jetzt schreiben wir ein Dockerfile zum Bauen und Ausführen:
# Basis-Image mit CompilerFROM gcc:latest AS builder
WORKDIR /appCOPY main.cpp .
# KompilierungRUN g++ -o hello main.cpp
# Finales minimales ImageFROM alpine:latest
WORKDIR /appCOPY --from=builder /app/hello .
CMD ["./hello"]Bauen und ausführen:
docker build -t cpp-hello .docker run cpp-helloErgebnis: Hello, Dockerized C++!
Erklärung:
- Die erste Stufe (builder) verwendet das vollständige gcc-Image für die Kompilierung
- Die zweite Stufe verwendet minimales Alpine Linux für das finale Image
- Das finale Image enthält nur die kompilierte Binärdatei – das spart Platz
3. Bauen mit CMake und Abhängigkeiten
Reale Projekte verwenden oft CMake und externe Bibliotheken. Betrachten wir ein Beispiel mit der Bibliothek fmt.
Projektstruktur:
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 mit Multi-Stage-Build:
# Stufe 1: Bauen der Abhängigkeiten und der AnwendungFROM 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
# Stufe 2: Finales ImageFROM 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"]Wichtige Punkte:
- Build-Abhängigkeiten (build-essential, cmake) gelangen nicht in das finale Image
- Laufzeitabhängigkeiten (libfmt9) werden separat installiert
- Die Größe des finalen Images ist deutlich kleiner
4. Optimierung von Docker-Images für C++
Die Optimierung ist besonders wichtig für C++-Anwendungen, da Images mit Compilern mög