|
cpp-toolchain documentation
Up-to-date C++ toolchain images for the complete development cycle - GNU and LLVM side by side
|
Up-to-date C++ toolchain images for the complete development cycle - GNU and LLVM side by side, from a minimal runtime to a full dev container.
Built as a single multi-stage Dockerfile, published to Docker Hub and GHCR by GitHub Actions.
Every Ubuntu release pins a GCC and a Clang version - 24.04 ships GCC 13 and Clang 18.
Getting past that means wiring up the toolchain repositories yourself, on every machine and in every CI job.
Keeping it current then means watching upstream for releases and bumping versions by hand - time spent on the toolchain rather than on the code.
These images do it once and carry both toolchains, so using GNU or LLVM - or moving between them later - is a matter of which command you run rather than which image you pull.
graph LR
runtime --> build
build --> analysis["static-analysis"]
build --> documentation
analysis --> dev
documentation --> dev
Each stage is published as its own image, so you pull only what you need - prefix any version with the stage name: ghcr.io/guillaumedua/cpp-toolchain:<stage>-latest
The -cross images carry per-target cross toolchains (~+200 MB installed per target), so reach for them only when you cross-compile - see Cross-compilation. runtime has no toolchain, so it is published once, without a cross variant.
What each version means - latest, pre-release v<major>.<minor>-rc.<n>, pinned v<major>.<minor> - is detailed in Tags & versioning.
The stages form a diamond: static-analysis and documentation both build on build; dev inherits static-analysis and re-adds the documentation tools.
| Category | runtime | build | static-analysis | documentation | dev |
|---|---|---|---|---|---|
| C++ runtime libraries - GNU (libc6, libgcc-s1, libstdc++6) | ✅ | ✅ | ✅ | ✅ | ✅ |
| C++ runtime libraries - LLVM (libc++1, libc++abi1) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Compilers: GNU-G++, LLVM-Clang++ | ✅ | ✅ | ✅ | ✅ | |
| Cross-compilation: per-target GNU toolchains via g++-<triplet> (opt-in) | ✅ | ✅ | ✅ | ✅ | |
| Multilib: secondary ABIs -m32 / -mx32 | ✅ | ✅ | ✅ | ✅ | |
| Build systems: CMake, make/Unix-makefile, ninja, ccache (+ opt-in Bazel, Build2) | ✅ | ✅ | ✅ | ✅ | |
| Dependency management: vcpkg, conan (python3) | ✅ | ✅ | ✅ | ✅ | |
| Versioning: git | ✅ | ✅ | ✅ | ✅ | |
| Coverage (GNU): gcov, gcov-tool | ✅ | ✅ | ✅ | ✅ | |
| Coverage (LLVM): llvm-cov, llvm-profdata | ✅ | ✅ | ✅ | ||
| Static analysis: clang-tidy, clang-format, clangd, scan-build, cppcheck, iwyu (+ lldb) | ✅ | ✅ | |||
| Documentation: doxygen, graphviz - and coverage reports: lcov / genhtml | ✅ | ✅ | |||
| Dynamic analysis / debug: valgrind, gdb | ✅ | ||||
| Versioning extra: subversion | ✅ | ||||
| Editors: emacs, nano, vim | ✅ | ||||
| Shells: bash, zsh | ✅ | ||||
| Misc: jq, ripgrep, docker-compose | ✅ |
build installs Clang minimalistically: only clang/clang++ answer to an unversioned name there, though the upstream installer's default set also leaves lld-<N>, lldb-<N> and clangd-<N> behind. The full LLVM tooling (clang-tidy, clang-format, clangd, lldb, scan-build, ...) is installed and registered in static-analysis, and inherited by dev.
A tag is <stage>[-cross]-<version>: the stage picks what is in the image, the optional cross picks the cross-arch flavor, and the version picks how fresh it is.
| Version | Published by | Meaning |
|---|---|---|
| v<major>.<minor> (e.g. build-v1.0) | major: a GitHub release, cut by hand from main minor: promoted by hand from a release candidate | A specific release, pinned and immutable; the version matches the release tag exactly |
| latest (e.g. build-latest) | the newest release, major or minor | Newest release - what you want unless you know otherwise |
| v<major>.<minor>-rc.<n> (e.g. build-v1.2-rc.1) | the twice-monthly rc schedule (cadence), from main | A release candidate for the next minor: ahead of latest, so upstream breakage surfaces before it reaches a release. Never aliased to latest |
The three channels differ in who decides, not in what they contain:
Every version in the image is pinned in the Dockerfile and updated by Renovate, so a scheduled run publishes nothing when nothing changed - no release is cut just because a date arrived.
dev is the Dockerfile's default target, so it also answers to the unprefixed versions - cpp-toolchain:latest is the same digest as cpp-toolchain:dev-latest, and likewise for v1.0 / v1.2-rc.1 (and cross-latest = dev-cross-latest). Every other stage must be named explicitly.
Every release note lists the versions that release pins - compilers, build systems, dependency managers, documentation tooling - beside what the images resolved them to, what moved since the previous release, and the pull requests merged in between. A pin is what the image requests, which is not always what it carries. GCC and Clang pin a major and install from rolling apt sources (ppa:ubuntu-toolchain-r/test and apt.llvm.org), so two builds of the same commit weeks apart can carry different patch releases of the same compiler major. ubuntu:24.04 is a rolling tag that resolves to whichever point release is current, and the Ubuntu archive behind it is pinned by UBUNTU_SNAPSHOT; the rest pins an upstream version. The standard libraries carry no pin at all, so they get a table of their own, with the ABI levels a binary is linked against.
Available from the build stage onwards. Both toolchains are installed side by side - the pinned version of each by default:
| Toolchain | Command | Versioned command | Also registered |
|---|---|---|---|
| GNU | gcc / g++ | gcc-<N> / g++-<N> | gcov, gcov-tool |
| LLVM | clang / clang++ | clang-<N> / clang++-<N> | clang-tidy, clangd, lldb, ... in static-analysis / dev |
Unversioned commands are update-alternatives symlinks; the latest-stable version always has the highest priority. Switching the default, or installing several versions at once, is Choosing a compiler version.
| Compiler | Default standard library | Alternative |
|---|---|---|
| g++ | libstdc++ | - |
| clang++ | libstdc++ (GCC's - the Linux default) | -stdlib=libc++ |
libc++ (libc++-<N>-dev, libc++abi-<N>-dev, libunwind-<N>-dev) is installed for the host architecture, so the LLVM toolchain is fully usable without GCC.
The runtime image carries the matching shared libraries (libc++1, libc++abi1) beside libstdc++6, so it runs everything build can produce - g++, clang++, and clang++ -stdlib=libc++ alike. That all three still run there is asserted by the validation gate, not assumed.
Everything below is also published as a browsable site at https://guillaumedua.github.io/cpp-toolchain.
| Document | Content |
|---|---|
| docs/IMAGES.md | Using the images: dev container, GitHub Actions, GitLab CI, Compose, one-off runs, remote SSH, custom builds |
| scripts/README.md | Without Docker: which scripts are standalone, and how to fetch and run one |
| docs/CROSS-COMPILATION.md | Cross-architecture compilation: published targets, what links and what does not, multilib |
| docs/COVERAGE.md | Code coverage: GNU gcov/lcov and LLVM llvm-cov/llvm-profdata |
| docs/IMAGES_VALIDATION.md | Images validation gate: what proves an image still fills its purpose, and how to run it |
| scripts/install/README.md | Installation scripts reference: cmake.sh, gcc.sh, llvm.sh, binutils.sh |
| HOW_TO_CONTRIBUTE.md | Contribution workflow |
Every version is pinned in the Dockerfile - base image (by digest), GCC, Clang/LLVM, CMake, vcpkg, Conan, Doxygen, build2, oh-my-zsh (by commit) and powerlevel10k - and each pin is tracked by Renovate. The actions the workflows run are pinned to commit digests and tracked the same way. Nothing resolves to "whatever is newest" at build time.
That has two consequences worth knowing:
Issues and pull requests are welcome - see HOW_TO_CONTRIBUTE.md for the workflow.
MIT - see LICENSE.