Docker and C++: Application Containerization: Advanced Level
\n\nContainerizing C++ applications using Docker is not just about moving a binary into an isolated environment. At an advanced level, it requires considering the specifics of compilation, dependency management, image size optimization, and debugging. In this article, we will break down best practices for creating efficient Docker images for C++ projects, using multi-stage builds, static and dynamic linking, as well as modern tools such as CMake and vcpkg.
\n\n1. Multi-stage Build
\nThe classic problem with Docker images for C++ is the huge size due to including the compiler, header files, and libraries in the final image. Multi-stage builds solve this problem: the first stage (build stage) contains all the tools for compilation, while the second stage (runtime stage) contains only the minimal set of runtime dependencies.
\n\n# First stage: build\nFROM gcc:13 AS builder\nWORKDIR /app\nCOPY . .\nRUN apt-get update && apt-get install -y cmake libboost-all-dev && \\\n cmake -B build -DCMAKE_BUILD_TYPE=Release && \\\n cmake --build build --parallel $(nproc)\n\n# Second stage: runtime\nFROM alpine:3.19\nRUN apk add --no-cache libstdc++ libgcc\nCOPY --from=builder /app/build/my_app /usr/local/bin/my_app\nCMD ["my_app"]\n\nThis approach reduces the final image from 1.5 GB (GCC image) to ~30 MB. For a C++ application to work in Alpine, only libstdc++ and libgcc are sufficient.
2. Dependency Management: vcpkg and Conan
\nFor modern C++ projects with dozens of libraries, manually installing dependencies via apt-get becomes unmanageable. The best solution is to use package managers such as vcpkg or Conan directly in the Dockerfile.
FROM ubuntu:22.04 AS builder\nRUN apt-get update && apt-get install -y curl git cmake build-essential\nRUN git clone https://github.com/Microsoft/vcpkg.git /opt/vcpkg && \\\n /opt/vcpkg/bootstrap-vcpkg.sh && \\\n /opt/vcpkg/vcpkg install fmt spdlog nlohmann-json\n\nWORKDIR /app\nCOPY . .\nRUN cmake -B build -DCMAKE_TOOLCHAIN_FILE=/opt/vcpkg/scripts/buildsystems/vcpkg.cmake && \\\n cmake --build build\n\nImportant: to avoid rebuilding vcpkg and libraries with every source code change, copy vcpkg.json and CMakeLists.txt before copying the rest of the files. Docker caches layers, and dependencies will only be reinstalled when the manifest changes.
3. Image Optimization: Static Linking and Alpine
\nTo minimize the image size, you can use static linking. This is especially relevant for microservices and CLI utilities. However, remember: static linking can increase the binary size but eliminates the need to include dynamic libraries in the container.
\n\n# CMakeLists.txt\nset(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++")\ntarget_link_libraries(my_app PRIVATE fmt::fmt spdlog::spdlog)\n\nThe final image can be built on scratch (empty image):
FROM scratch\nCOPY --from=builder /app/build/my_app /\nENTRYPOINT ["/my_app"]\n\nSuch an image weighs only a few megabytes. However, note: static linking is not always possible (for example, when using OpenSSL or CUDA), and in such cases, it is better to use Alpine with dynamic libraries.
\n\n4. Debugging C++ Applications in a Container
\nDebugging in Docker is a separate challenge. Advanced methods include:
\n- \n
- Using debug images: build a separate image with debug symbols and
gdbserver. Connect to it from your IDE (VS Code, CLion) via remote debugging. \n - Core dumps: enable writing core files in the container and analyze them after a crash: \n
# Dockerfile for