DevOps for C++ — reproducible deployment
A C++ program in production breaks for reasons you cannot reproduce locally: a different glibc, a missing libssl, an unexpected locale, an unfamiliar CPU model. Containers solve that pain — the application and its dependencies ship together, and the host system only influences things through the kernel.
Topic map
- Containers vs virtual machines — namespaces, cgroups, kernel sharing.
- Docker — images, layers, registry, multi-stage builds.
- C++ deployment — static vs dynamic linking, image size optimization.
- Orchestration — a short overview of Kubernetes.
Containers vs virtual machines
A virtual machine emulates a full machine (or paravirtualizes it). Inside it runs its own OS kernel, its own userspace, its own disk image. Strong isolation, slow startup (seconds), large size (gigabytes).
A container is an isolated process on the host kernel. The kernel provides the isolation via:
- Namespaces —
pid,net,mnt,uts,ipc,user,cgroup— each sees its own set of PIDs, network interfaces, mounts. - Cgroups — bound CPU, RAM, IO, network usage.
- Seccomp / capabilities / AppArmor — restrict system calls and privileges.
A container shares the host kernel → a Linux container on Windows runs through a VM (WSL2 or the Docker Desktop VM). Container startup is in milliseconds; size ranges from 5 MB (Alpine + static binary) to hundreds of MB.
⚠️ A container is weaker isolation than a VM — a container escape through a kernel hole gives root on the host. For multi-tenant code teams use either VMs, microVMs (Firecracker), or gVisor.
Docker
An image is an immutable set of layers (UnionFS). Each Dockerfile command creates a layer. Layers are cached and reused across images.
A container is a running instance of an image plus a writable top layer.
A registry stores images: Docker Hub, GitHub Container Registry, ECR. The latest tag is not a version — it is a human-readable pointer that silently changes.
Multi-stage builds
For C++, a critical technique. The build stage contains the compiler and dependencies (700 MB); the runtime stage holds only the binary and runtime libraries (10–50 MB).
FROM gcc:13 AS build
WORKDIR /src
COPY . .
RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j
FROM debian:bookworm-slim
COPY --from=build /src/build/myapp /usr/local/bin/
USER 1000:1000
ENTRYPOINT ["myapp"]
⚠️ Do not run containers as root. Set USER — otherwise a container escape gives root on the host.
C++ deployment
Static linking — all dependencies inside one binary (-static). The image becomes tiny (Alpine + static binary = 10 MB), but glibc does not link statically cleanly — use musl or dynamic linking instead.
Dynamic linking — the standard. You need a runtime image with a matching glibc (or musl). ldd binary lists dependencies; verify every .so is present in the final image.
Image size: use debian:slim, ubuntu:24.04, or distroless (runtime only, no shell). Alpine is the smallest but ships with musl ≠ glibc; some libraries behave differently.
Orchestration
Kubernetes manages tens or thousands of containers: schedules across nodes, restarts crashed pods, load-balances traffic. For a C++ service, the relevant pieces are:
- Resource limits —
cpu: 1000m,memory: 512Mimap to cgroup limits. - Liveness / readiness probes — Kubernetes restarts the container on liveness failure; routes no traffic on readiness failure.
- HPA (Horizontal Pod Autoscaler) — scales by CPU / memory / custom metric.
For one or two services, Kubernetes is overkill — docker compose or systemd units are often enough.
Common traps
| Mistake | Consequence |
|---|---|
| Running the container as root | Escape gives root on the host |
Using the latest tag in production | Incompatible version after the next docker pull |
Secrets in ENV inside the Dockerfile | Visible in docker inspect and layer history |
| Single-layer build with the full toolchain | 1 GB image instead of 50 MB |
Static glibc linking without musl | Strange bugs in getaddrinfo and dlopen |
| Ignoring the liveness probe | A hung process is never restarted |
COPY . /app without .dockerignore | .git, build/, and secrets end up in the image |
Building for a different architecture without --platform | "Works on my machine" — fails on the ARM server |
Interview relevance
DevOps is a common slice of a backend C++ interview. The interviewer checks:
- Do you understand the container-vs-VM distinction at the kernel level?
- Do you know what namespaces and cgroups are (not by heart, but the right direction)?
- Do you see the difference between static and dynamic linking and their effect on image size?
- Do you avoid the myths: "Docker is a lightweight VM", "containers give full isolation"?
Typical wrong answer: "A container is a lightweight VM with its own kernel." No — a container shares the host kernel, and that is both its strength (fast startup) and its weakness (weaker isolation).
Popular question directions:
- How does a container differ from a VM at the kernel level?
- What are namespaces and cgroups? What namespace kinds exist?
- Why do C++ projects need multi-stage builds?
- Why is
latesta bad production tag? - When do you reach for Kubernetes, and when is
docker composeenough?