Introduction: Why Containerize C++ Applications?
C++ development has traditionally been associated with the "it works on my machine" problem. Different versions of compilers, libraries, and system dependencies make deployment a headache. Docker solves this problem: you package your application with its entire environment into a lightweight container that runs identically on any system.
In this article, we will walk through the complete process of containerizing a C++ application — from a simple Dockerfile to a multi-stage build with image size optimization.
1. Basics: Dockerfile for a C++ Application
Let's start with the simplest example. Suppose we have a C++ program that prints "Hello, Docker!".
Source Code (hello.cpp)
#include <iostream>
int main() { std::cout << "Hello, Docker!" << std::endl; return 0;}First Dockerfile
# Use the GCC imageFROM gcc:13.2
# Set the working directoryWORKDIR /app
# Copy the source codeCOPY hello.cpp .
# CompileRUN g++ -o hello hello.cpp
# RunCMD ["./hello"]Let's build and run it:
docker build -t cpp-hello .docker run cpp-helloThe downside of this approach is that the image weighs about 1.2 GB due to the full GCC toolchain. This is unacceptable for production.
2. Multi-stage Build
The best practice is to separate the build and execution stages. In the first stage, we compile the application with the full toolchain; in the second, we copy only the finished binary into a clean, minimal image.
Optimized Dockerfile
# Stage 1: BuildFROM gcc:13.2 AS builder
WORKDIR /app
COPY hello.cpp .
RUN g++ -static -o hello hello.cpp
# Stage 2: ExecutionFROM alpine:3.19
WORKDIR /app
# Copy the statically compiled binaryCOPY --from=builder /app/hello .
CMD ["./hello"]The key point is the -static flag. It includes all dependencies in the binary, allowing it to run on a clean Alpine without additional libraries. The image size decreases from 1.2 GB to ~6 MB!
3. Real Project: CMake + Dependencies
Let's consider a more complex case: an application that uses the Boost library and is built with CMake.
Project Structure
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 works in the container!" << std::endl; return 0;}Dockerfile with Dependencies
# Stage 1: BuildFROM ubuntu:22.04 AS builder
# Install build dependenciesRUN apt-get update && apt-get install -y \ build-essential \ cmake \ libboost-system-dev \ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy source codeCOPY CMakeLists.txt .COPY src/ src/
# BuildRUN mkdir build && cd build && \ cmake .. -DCMAKE_BUILD_TYPE=Release && \ make -j$(nproc)
# Stage 2: ExecutionFROM ubuntu:22.04
# Install only runtime dependenciesRUN 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"]Note: in the second stage, we install only the runtime version of Boost (libboost-system1.74.0).