Docker and C++: Advanced Application Containerization

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

Docker and C++: Application Containerization at an Advanced Level



Containerizing C++ applications with Docker is not just about moving code into an isolated environment. At an advanced level, it is the art of balancing image size, build speed, security, and compatibility with system libraries. In this article, we will explore techniques that will help you create efficient, lightweight, and maintainable Docker images for C++ projects.



We assume you are already familiar with the basics of Docker (commands docker build, docker run) and the basic syntax of a Dockerfile. Now let's move on to real-world tasks: multi-stage builds, working with system dependencies, debugging inside a container, and integration with CI/CD.



1. Multi-Stage Build: Separating Compilation and Execution



The main problem with C++ in Docker is image size. A full set of tools (g++, cmake, make, dev packages) can weigh 1–2 GB. The solution is a multi-stage build.



Consider a classic example: a C++ application using the libcurl library.



# First stage: buildFROM ubuntu:22.04 AS builder

RUN apt-get update && apt-get install -y \ build-essential \ cmake \ libcurl4-openssl-dev \ && rm -rf /var/lib/apt/lists/*

WORKDIR /appCOPY . .

RUN cmake -B build -DCMAKE_BUILD_TYPE=Release \ && cmake --build build --parallel $(nproc)

# Second stage: executable imageFROM ubuntu:22.04-slim

RUN apt-get update && apt-get install -y \ libcurl4 \ && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/build/myapp /usr/local/bin/myapp

CMD ["myapp"]


Key points:

  • In the first stage (builder), we install all tools and dependencies for building.
  • In the second stage, only runtime dependencies (libcurl4 instead of libcurl4-openssl-dev).
  • The final image contains only the compiled binary and necessary libraries.
  • We use --parallel $(nproc) to speed up the build.


The image size decreases from ~1.2 GB to ~80 MB. This is critical for deployment and downloading images in CI/CD.



2. Optimizing Docker Caching and Layers



Each instruction in a Dockerfile creates a layer. An incorrect order of instructions causes the entire cache to be invalidated with the slightest code change.



Rule: First, copy what changes rarely (CMakeLists.txt, dependencies), then the code.



FROM ubuntu:22.04 AS builder

WORKDIR /app

# 1. System dependencies (change rarely)RUN apt-get update && apt-get install -y \ build-essential cmake libboost-all-dev \ && rm -rf /var/lib/apt/lists/*

# 2. Build configuration files (change infrequently)COPY CMakeLists.txt .

# 3. Source code (changes constantly)COPY src/ ./src/

# 4. BuildRUN cmake -B build && cmake --build build


Additional tips:

  • Use .dockerignore to exclude build/, .git/, *.o.
  • Group RUN apt-get install into a single command — this reduces the number of layers.
  • For C++ projects with many header files, consider using ccache inside the container.


3. Working with System Libraries and Static Linking



Sometimes dynamic libraries are missing or incompatible in the container. An advanced approach is static linking or using Alpine Linux.



Option A: Static Build (g++)

FROM alpine:3.18 AS builder

RUN apk add --no-cache g++ gcc musl-dev make cmake

WORKDIR /appCOPY . .

# Static linkingRUN g++ -static -O2 -o myapp main.cpp -lpthread

FROM scratchCO

Blogs

Book Recommendations