Building and testing a C++ application in Docker
Düzenlendi · Kapsam: Multi-stage Dockerfiles, BuildKit cache mounts, CMake/Ninja, CTest and a Distroless runtime.
The two biggest problems when building C++ projects in Docker containers are:
- Build tools (GCC/Clang, CMake, Ninja, vcpkg/Conan) can inflate the final image to several gigabytes and increase its attack surface.
- A single-line source change can invalidate the layer cache and cause libraries and dependencies to be rebuilt from scratch for hours.
A modern solution combines multi-stage builds, BuildKit cache mounts (--mount=type=cache) and CTest integration into an isolated pipeline.
1. Multi-stage architecture
The pipeline has four logical stages:
[ 1. Derleme Ortamı ] ──► Derleyici araçlar (GCC/Clang, CMake, Ninja, Ccache)
│
[ 2. Bağımlılık Katmanı ] ──► vcpkg.json ile harici kütüphaneler (vcpkg cache)
│
[ 3. Kod Derleme ] ──► Kaynak kod derlemesi (Ccache önbelleği ile saniyeler içinde)
│
[ 4. Test Koşucusu ] ──► CTest ile birim testler (Başarısız olursa imaj üretimi durur)
│
[ 5. Minimal Çalışma ] ──► Yalnızca ikili dosya (Distroless / Scratch - ~25 MB)
2. A production-oriented multi-stage Dockerfile
Create Dockerfile in the project root:
# syntax=docker/dockerfile:1.4
# ==============================================================================
# AŞAMA 1: Temel Derleme Ortamı
# ==============================================================================
FROM debian:bookworm-slim AS temel-araclar
ENV DEBIAN_FRONTEND=noninteractive \
VCPKG_ROOT=/opt/vcpkg \
VCPKG_FORCE_SYSTEM_BINARIES=1
# APT paketlerini BuildKit önbelleği ile kur (tekrar indirilmez)
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
ninja-build \
git \
curl \
zip \
unzip \
tar \
pkg-config \
ca-certificates \
ccache
# vcpkg aracını kur
RUN git clone --depth 1 https://github.com/microsoft/vcpkg.git /opt/vcpkg && \
/opt/vcpkg/bootstrap-vcpkg.sh -disableMetrics
# ==============================================================================
# AŞAMA 2: Bağımlılıkların Önbelleklenmesi
# ==============================================================================
FROM temel-araclar AS bagimlilik-derleyici
WORKDIR /proje
# Yalnızca bağımlılık listesini kopyala (Kaynak kod değiştiğinde bu katman bozulmaz)
COPY vcpkg.json ./
# vcpkg ikili önbelleği ile bağımlılıkları derle
RUN --mount=type=cache,target=/root/.cache/vcpkg \
${VCPKG_ROOT}/vcpkg install --triplet x64-linux
# ==============================================================================
# AŞAMA 3: Projeyi Derleme
# ==============================================================================
FROM bagimlilik-derleyici AS derleyici
WORKDIR /proje
# Kaynak kodları kopyala
COPY CMakeLists.txt CMakePresets.json ./
COPY src/ ./src/
COPY tests/ ./tests/
# Ccache ve vcpkg önbelleklerini bağlayarak derle
ENV CCACHE_DIR=/root/.cache/ccache
RUN --mount=type=cache,target=/root/.cache/vcpkg \
--mount=type=cache,target=/root/.cache/ccache \
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
-DBUILD_TESTING=ON && \
cmake --build build -j
# ==============================================================================
# AŞAMA 4: Testleri Koşma (CI/CD Kapısı)
# ==============================================================================
FROM derleyici AS test-kosucu
WORKDIR /proje
# Testlerden herhangi biri başarısız olursa Docker build hata verir ve durur
RUN ctest --test-dir build --output-on-failure
# ==============================================================================
# AŞAMA 5: Minimal Üretim İmajı (Distroless)
# ==============================================================================
# Google Distroless: Kabuk (sh), paket yöneticisi içermez; yalnızca glibc ve libstdc++ içerir
FROM gcr.io/distroless/cc-debian12:nonroot AS calisma-zamani
WORKDIR /app
COPY --from=derleyici /proje/build/bin/uygulama /app/uygulama
USER nonroot:nonroot
ENTRYPOINT ["/app/uygulama"]
3. Example CMakeLists.txt and test definition
CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(DockerCppProje LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Çıktı ikili dizini
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Harici kütüphane
find_package(fmt CONFIG REQUIRED)
# Ana uygulama
add_executable(uygulama src/main.cpp)
target_link_libraries(uygulama PRIVATE fmt::fmt)
# Testler
include(CTest)
if(BUILD_TESTING)
enable_testing()
add_executable(birim_test tests/test_main.cpp)
target_link_libraries(birim_test PRIVATE fmt::fmt)
add_test(NAME TemelTest COMMAND birim_test)
endif()
Example test file, tests/test_main.cpp:
#include <iostream>
#include <cassert>
int topla(int a, int b) { return a + b; }
int main() {
assert(topla(2, 3) == 5);
std::cout << "Tüm testler başarıyla geçti.\n";
return 0;
}
4. Building, testing and selecting a target
Start a build using Docker BuildKit’s advanced caching features:
DOCKER_BUILDKIT=1 docker build -t cpp-uygulama:latest .
Selecting target stages in CI/CD
You can split CI into stages instead of running every step at once:
- Run only the tests:
docker build --target test-kosucu -t test-raporu .If a test fails, the build stops immediately and prints its error logs to the console.
- Produce only the final production image:
docker build --target calisma-zamani -t cpp-uygulama:prod .
5. Comparing image sizes
The difference between a traditional single-stage image and a multi-stage Distroless image:
Geleneksel GCC + CMake İmajı: ~1.85 GB
Multi-Stage Distroless İmajı: ~28.4 MB
The final image contains no unnecessary compilers, header files (.h/.hpp), package manager or shell. This keeps startup quick and reduces the attack surface.
Sources and verification environment
- Docker multi-stage builds guide
- Google Container Tools — Distroless images
- BuildKit cache mounts documentation
These steps were tested and verified using Docker Engine 26.1 and BuildKit on a Debian 12 base with GCC 12, CMake 3.25 and Ninja.