Docker and C++: Containerizing Applications from A to Z
Containerization has become an integral part of modern software development. Docker allows you to package applications together with their dependencies, ensuring reproducibility and portability. In this article, we will thoroughly examine how to containerize C++ applications using Docker — from basic concepts to advanced optimization techniques.
1. Why Containerize C++ Applications?
Many developers mistakenly believe that Docker is only relevant for interpreted languages (Python, Node.js). However, C++ applications also benefit from containerization:
- Dependency isolation: libraries, compilers, and system utilities do not conflict with other projects
- Build reproducibility: identical environment at all stages (dev, CI/CD, production)
- Simplified deployment: the image contains everything needed to run
- Scalability: easily run multiple instances via Docker Compose or Kubernetes
Let's examine the process with a practical example.
2. Basic Dockerfile for C++
Let's start with a simple C++ application. Create a file main.cpp:
#include <iostream>
int main() { std::cout << "Hello, Dockerized C++!" << std::endl; return 0;}Now let's write a Dockerfile for building and running:
# Base image with compilerFROM gcc:latest AS builder
WORKDIR /appCOPY main.cpp .
# CompilationRUN g++ -o hello main.cpp
# Final minimal imageFROM alpine:latest
WORKDIR /appCOPY --from=builder /app/hello .
CMD ["./hello"]Build and run:
docker build -t cpp-hello .docker run cpp-helloResult: Hello, Dockerized C++!
Explanation:
- The first stage (builder) uses the full gcc image for compilation
- The second stage uses minimal Alpine Linux for the final image
- The final image contains only the compiled binary — this saves space
3. Building with CMake and Dependencies
Real projects often use CMake and external libraries. Let's look at an example with the fmt library.
Project structure:
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 with multi-stage build:
# Stage 1: Build dependencies and 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
# Stage 2: Final 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"]Important points:
- Build dependencies (build-essential, cmake) do not end up in the final image
- Runtime dependencies (libfmt9) are installed separately
- The size of the final image is significantly smaller
4. Optimizing Docker Images for C++
Optimization is especially important for C++ applications, as images with compilers can