Nix CI Docker images
This directory builds the Docker images used by xrpld's Linux CI. Each image
bundles the exact same toolchain that the Nix development shell provides
(see docs/build/nix.md), so what runs in CI matches
what developers get locally from nix develop.
The toolchain (CMake, Ninja, Conan, GCC, Clang, clang-tidy, the
sanitizer/coverage tools, …) is defined in nix/packages.nix
and assembled for CI by nix/ci-env.nix. The Docker build
turns that Nix environment into an ordinary container image layered on top of a
conventional base image (Ubuntu, Debian, RHEL, or nixos/nix).
Images
The images are built by the build-nix-images.yml
workflow and pushed to ghcr.io/xrplf/xrpld/nix-<distro>. The <distro> is
selected through the BASE_IMAGE build argument; the base images are the
oldest supported version of each distribution we target:
| Image | BASE_IMAGE |
Notes |
|---|---|---|
nix-nixos |
nixos/nix:latest |
Build/lint only; binaries are not run (see below). |
nix-ubuntu |
ubuntu:20.04 |
Oldest supported Ubuntu (glibc 2.31). |
nix-debian |
debian:bookworm |
|
nix-rhel |
registry.access.redhat.com/ubi9/ubi:latest |
All images carry the full toolchain on PATH (via /nix/ci-env/bin) plus the
CA bundle shipped in the Nix environment, so HTTPS clients (git, curl, Conan)
work without ca-certificates being installed in the base image.
Build stages
Dockerfile is a multi-stage build:
builder— On anixos/nixbuilder, evaluate the flake and build the CI environment (nix/ci-env.nix). The resulting Nix store closure (the complete set of store paths the toolchain depends on) is copied into a staging directory.final— Start fromBASE_IMAGE, copy in the Nix store closure and theci-envsymlink tree, and wire upPATHand the CA bundle. It then:- installs the dynamic linker if the base image lacks one (see How libc is handled),
- runs
bin/check-tools.shto verify every expected tool is present and runnable. - compiles the C++ test programs in
test_files/cpp/sources/with bothg++andclang++, and sanitizers, and - compiles the Rust test programs in
test_files/rust/sources/withrustc, and builds thetest_files/rust/proc_macro/workspace withcargoto exercise proc-macro dylib loading.
tester— Start again from a cleanBASE_IMAGE(no Nix toolchain), install only the sanitizer runtime libraries (install-sanitizer-libs.sh), and run the binaries compiled infinal. This proves the binaries built with the Nix toolchain actually run on a vanilla base image. Onnixos/nixthis step is skipped (the binaries are patched for a conventional FHS loader).- Output — The final image is gated on the tester succeeding: it copies a
sentinel file out of
tester, so a failed test run fails the whole build.
How libc is handled
The goal is for binaries built in these images to run on the oldest supported base image (Ubuntu 20.04, glibc 2.31) and newer — without the developer's Nix toolchain being present at runtime. Two pieces make that work:
-
Compilers linked against an old glibc. The Nix CI environment does not use nixpkgs' current glibc. Instead it pins a 2020 nixpkgs snapshot whose primary glibc is 2.31 (matching Ubuntu 20.04), via the
nixpkgs-custom-glibcflake input. GCC, Clang, binutils and compiler-rt are all rebuilt/wrapped against this custom glibc (seenix/ci-env.nix). As a result the libraries they emit (libstdc++,libgcc_s, the sanitizer runtimes) reference only symbols available in glibc 2.31. -
An expected dynamic linker in the image. Binaries built in Nix environments reference a dynamic linker from Nix store paths, which won't be present in the base image. However,
loader-path.shreports the expected loader path for the current architecture, so we can patch the binaries to use the correct loader.
The build then verifies all of this end to end, and the C++ and Rust programs
go through the same pipeline: each is compiled in final, has its PT_INTERP
patched to the target loader, and is then run in the clean tester stage to
confirm it emits the expected diagnostic on a stock base image. The C++ programs
are in test_files/cpp/sources/ (a regular binary plus ASan/TSan/UBSan
variants); the Rust programs are in test_files/rust/sources/ (a hello binary
plus panic and overflow-check variants), plus the test_files/rust/proc_macro/
workspace — a crate whose compilation additionally loads a proc-macro dylib, and
whose resulting binary is patched and run like the others.
Files
| File | Purpose |
|---|---|
./Dockerfile |
Multi-stage build described above. |
./loader-path.sh |
Print the dynamic-linker (PT_INTERP) path for the current architecture. |
./test_files/cpp/ |
C++ sanitizer smoke test: sources + compile/run scripts. |
./test_files/rust/ |
Rust smoke test: rustc sources + a cargo proc-macro workspace + compile/run scripts. |
/bin/check-tools.sh |
Verify every expected tools are present and runnable. |
/bin/install-sanitizer-libs.sh |
Install libasan/libtsan/libubsan runtimes on the supported base images. |