build: ship the debug variants from scratch with busybox
Dockerfile.debug's runtime stage is FROM scratch, so all six variants now are. kaniko unpacks a build's base image over its own root without clearing it first, so what the runtime ships is what a customer's RUN steps see, and the debug pair was the last vendor userland there.
The shell is compiled in the UBI9 builder instead. busybox 1.36.1 is the newest release busybox.net marks stable; 1.37.0 and 1.38.0 are listed unstable, so a Renovate bump is a decision CONTRIBUTING spells out. BUSYBOX_URL is an ordered list, canonical URL first and Buildroot's mirror second, because busybox.net's port 443 timed out from a GitLab SaaS runner and a workstation on 2026-09-08; the build takes the first that answers, verifies the digest either way, and an airgap consumer sets one internal URL. glibc-static comes from ubi-9-codeready-builder-rpms, needing no subscription.
Two edits to defconfig. CONFIG_STATIC=y, because a scratch runtime holds no loader. CONFIG_TC=n, because networking/tc.c:319 does not compile against UBI9's kernel-headers-5.14.0-687.44.1.el9_8, which no longer defines struct tc_cbq_wrropt. The build asserts the binary has no dynamic section, so one linked against libc fails there. KCONFIG_NOTIMESTAMP=1 keeps a build time out of the banner.
The layout is upstream's: /busybox with every applet, /bin/sh a symlink into it, VOLUME /busybox, PATH=/usr/local/bin:/kaniko:/busybox. Two departures, both measured. Upstream's exec-form RUN steps also commit empty /dev, /proc and /sys into a scratch image, so /bin/sh is made in the builder and the runtime stage stays COPY-only. The flat /busybox is built from the applet list, because make install spreads the applets over four directories.
job-image-smoke and job-image-fips-smoke are new: the existing debug jobs run through busybox sh but are written to test the executor, so a shell fault would read as an executor fault. nonubi-build-smoke gains both debug images, three variants against two bases, and a check that no built layer carries /busybox.
Proof, all local with podman on linux/amd64:
- Both variants build. busybox is 2,512,856 bytes, 0
NEEDED, 401 applets. shandgrepresolve at/busyboxandexecutor versionprintsv1.25.19.- The image holds the three
/kanikopaths,/busyboxand/bin/sh, withVolumes{"/busybox"},base.namescratch, and the two new labels. - The
nonubicontrol, verbatim: both debug images buildalpine:3.24andbusybox:stable-musland report the base's own/usr/bin. The publishedv1.25.19-debugfails both, at 279 entries against alpine's 142 and 195 against 1. - The layer assertion finds no built layer carrying
/busyboxor a/bin/shpointing into it. The published image passes it too, so it guards the future. trivyfinds no operating system and one result, the Go binary. It reports nothing for busybox, which no package manager installed; the pin, Renovate and the image label track it.- The FIPS image builds alpine over TLS under
fips140=onandfips140=only. hadolint,shellcheck,check-versions.sh,glab ci lintand the Renovate validator pass;valeholds at the three pre-existing errors, one warning fewer.- Dropping
CONFIG_STATICtakesNEEDEDto 3 and fails the build.
A consumer sees six variants on scratch. The debug pair keeps its shell and loses bash and microdnf with the UBI9-minimal base, so it cannot install a package at job time, as upstream's debug image cannot. UBI_MINIMAL_IMAGE is gone, and the rpm findings against that package set stop being detected after the rebuild.
Expect six images to rebuild.