Containerizing C++ Applications with Docker: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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-hello


The 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└── Dockerfile


CMakeLists.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).

Blogs

Book Recommendations