containers/runa-toolchain.Containerfile
# nordisk RUNA TOOLCHAIN — the image a runa PROGRAM STEP runs in.
#
# `runs_on: Container` is the default place of every step that runs a program
# (CargoBuild, Nextest, CargoTest, Bench, Coverage, Trace, Profile, Lint, Xtask, DeployPages, Sbom,
# SecurityScan, HolgerPackage — `runa_exec::step_runs_a_program`). The forge boots this image
# through draupnir (`BootSpec::container` → `ContainerBoot`, podman's REST API),
# bind-mounts the constellation root at its own path, and runs the step's argv in
# the step's checkout. The host contributes an engine socket and the checkout.
#
# podman build -t nordisk-runa-toolchain:nightly-2026-10-05 \
# -f containers/runa-toolchain.Containerfile containers/
#
# The tag is `runa_exec::DEFAULT_RUNA_IMAGE`; `DWARVES_RUNA_IMAGE` overrides it.
# `runa_exec::tests::the_default_image_is_this_recipe` holds the tag, the ARG
# below and the package list together.
#
# NOT `nordisk-cargo-toolchain`: that name is build-thing's image (stable
# 1.97.1, the musl/wasm/uefi targets) and recipes PIN it by digest
# (edda-build-thing `recipe.rs`). A second image under its tag would make that
# pin name two things.
#
# The same BASE and ARCHIVE pins as edda's `cargo-toolchain.Containerfile`
# (debian:trixie by digest, apt from the snapshot.debian.org instant it was built
# from), so the constellation still has one base.
FROM debian:trixie@sha256:d63a99144861e4e460196ed93d07777490cbeab53ca660c434f2a589a6c50ea3
ENV DEBIAN_FRONTEND=noninteractive
ENV SOURCE_DATE_EPOCH=1783900800
# THE TOOLCHAIN: the nightly the Mímir consumer reads the workspace with
# (nornir `crates/mimir-driver/rust-toolchain.toml`:
# rustc 1.101.0-nightly (282215592 2026-10-04)), so a pipeline compiles with the
# compiler the code room was read with. Bump both ON PURPOSE, in one go.
ARG RUST_TOOLCHAIN=nightly-2026-10-05
RUN set -eu; \
printf '%s\n' \
'Types: deb' \
'URIs: http://snapshot.debian.org/archive/debian/20260713T000000Z' \
'Suites: trixie trixie-updates' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.pgp' \
'' \
'Types: deb' \
'URIs: http://snapshot.debian.org/archive/debian-security/20260713T000000Z' \
'Suites: trixie-security' \
'Components: main' \
'Signed-By: /usr/share/keyrings/debian-archive-keyring.pgp' \
> /etc/apt/sources.list.d/debian.sources; \
printf 'Acquire::Check-Valid-Until "false";\n' > /etc/apt/apt.conf.d/10no-check-valid-until; \
apt-get update && apt-get install -y --no-install-recommends \
`# COMPILE — rustup (Debian's, signed archive) fetches the pinned nightly;` \
`# ca-certificates makes that fetch (and crates.io) verifiable.` \
rustup ca-certificates \
`# LINK — the planner's ONE linker set: every repo links with` \
`# -C linker=clang -C link-arg=--ld-path=/usr/bin/ld.mold (dwarves' own` \
`# .cargo/config.toml says exactly that). clang drives, mold links.` \
clang mold \
`# LINK / build scripts — cc-rs falls back to cc; the C runtime to link to.` \
gcc g++ libc6-dev \
`# FETCH — cargo runs git for git dependencies; xtasks read the tree's git.` \
git \
`# CODEGEN — tonic-build shells out to protoc (every gRPC crate's build.rs).` \
protobuf-compiler \
`# -sys crates — pkg-config finds system libraries; cmake builds the C++ of` \
`# openzl-sys-rs (znippy-common's path dependency).` \
pkg-config cmake \
`# openssl-sys (edda, knut … through native-tls / git2): its build script` \
`# asks pkg-config for libssl; the headers and the .pc come from here.` \
libssl-dev \
`# graphblas-sys (nornir's graph engine, in dwarves' default build): links` \
`# -lgraphblas and checks GraphBLAS.h is 7.x. Without it every test binary` \
`# that pulls nornir dies in mold, "library not found: graphblas".` \
libgraphblas-dev \
`# OVMF — tunnr's firmware tests resolve the stock UEFI pair (OVMF_CODE /` \
`# OVMF_VARS) before they judge a named one; without it they fail here.` \
ovmf \
`# TRACE — runa's Trace step builds with -Z instrument-xray and links with` \
`# clang -fxray-instrument: clang pulls compiler-rt's XRay runtime` \
`# (libclang_rt.xray*.a), which Debian ships apart from clang.` \
libclang-rt-19-dev \
`# PROFILE — runa's Profile step samples with perf record -g (no rebuild).` \
`# It runs only where the box allows perf_event_open (perf_event_paranoid` \
`# <= 2, or CAP_PERFMON): the image carries the tool, never the permission.` \
linux-perf \
&& rm -rf /var/lib/apt/lists/* /var/log/apt /var/log/dpkg.log /var/log/alternatives.log \
&& rm -f /var/cache/ldconfig/aux-cache && rm -rf /root/.cache
# rustup and cargo under /usr/local: the image behaves the same for whatever uid
# the engine runs it as (rootless: 0:0 = the forge's user; rootful: the forge's
# own uid). The forge points CARGO_HOME at a cache of its own per run.
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
# NO toolchain override here: the nightly is rustup's DEFAULT (below), so a
# checkout that pins its own (`rust-toolchain.toml`: gunnar-ui 1.96.0, tunnr
# 1.97.1) builds with its pin — the forge then points RUSTUP_HOME at a cache of
# its own and rustup fetches the pin there once (runa_backend::pinned_toolchain_file).
# `minimal` + the components a runa pipeline names: rustfmt (Lint(Fmt),
# the xtask fmt-check) and clippy (Lint(Clippy)) — and rust-src: the Mímir
# consumer's container road dumps the toolchain's own std/core/alloc into the
# ring once per toolchain (`cargo mimir-dump --std` compiles the sysroot's
# sources); without it that dump fails on the container road.
RUN rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal --component rustfmt,clippy,rust-src --no-self-update \
&& rustup default "${RUST_TOOLCHAIN}" \
`# Debian's rustup keeps its proxies in /usr/bin: CARGO_HOME starts empty` \
&& mkdir -p /usr/local/cargo && chmod -R a+rX /usr/local/rustup /usr/local/cargo \
&& rustc --version && cargo --version && cargo fmt --version && cargo clippy --version
# TEST — runa's `Nextest` step runs `cargo nextest run`; nextest is not a
# rustup component, so it is built here, version-pinned and `--locked`.
ARG NEXTEST_VERSION=0.9.148
RUN CARGO_BUILD_JOBS=8 cargo install --locked cargo-nextest --version "${NEXTEST_VERSION}" --root /usr/local/cargo \
&& rm -rf /usr/local/cargo/registry /usr/local/cargo/git \
&& chmod -R a+rX /usr/local/cargo && cargo nextest --version
# COVERAGE — runa's `Coverage` step runs `cargo llvm-cov` (LLVM source-based
# coverage): the toolchain's `llvm-tools` component and cargo-llvm-cov, built here
# version-pinned and `--locked` like nextest.
ARG LLVM_COV_VERSION=0.9.1
RUN rustup component add llvm-tools-preview --toolchain "${RUST_TOOLCHAIN}" \
&& CARGO_BUILD_JOBS=8 cargo install --locked cargo-llvm-cov --version "${LLVM_COV_VERSION}" --root /usr/local/cargo \
&& rm -rf /usr/local/cargo/registry /usr/local/cargo/git \
&& chmod -R a+rX /usr/local/cargo /usr/local/rustup && cargo llvm-cov --version
# THE IMAGE ASSERTS XRAY: a function built with every function instrumented and
# linked with the XRay runtime must log its calls (runa's Trace step).
RUN set -eu; \
printf '#[inline(never)] fn f(x: u64) -> u64 { x + 1 }\nfn main() { println!("{}", f(1)); }\n' > /tmp/xr.rs; \
rustc -Z instrument-xray=always -C linker=clang -C link-arg=-fxray-instrument -C link-arg=--ld-path=/usr/bin/ld.mold -o /tmp/xr /tmp/xr.rs; \
mkdir /tmp/xl; XRAY_OPTIONS="patch_premain=true xray_mode=xray-basic xray_logfile_base=/tmp/xl/" XRAY_BASIC_OPTIONS="func_duration_threshold_us=0" /tmp/xr; \
test -s "$(ls /tmp/xl/* | head -1)"; \
perf --version; \
rm -rf /tmp/xr /tmp/xr.rs /tmp/xl
# THE IMAGE ASSERTS ITS LINKER: a hello world linked exactly the way the
# constellation links (clang + mold) must run.
RUN set -eu; \
printf 'fn main() { println!("linked"); }\n' > /tmp/probe.rs; \
rustc -C linker=clang -C link-arg=--ld-path=/usr/bin/ld.mold -o /tmp/probe /tmp/probe.rs; \
test "$(/tmp/probe)" = linked; \
git --version; \
rm -f /tmp/probe /tmp/probe.rs
WORKDIR /work