Skip to content

Build Environment

Tim edited this page Aug 15, 2026 · 3 revisions

The NebulaOS build environment

This covers the unified build container (ghcr.io/coreflake1/nebulaos-build) — why it exists, what's actually in it, what deliberately isn't, and what has to pass before a new version of it becomes the one everyone builds against.

What's actually in it

Every host-level build tool the 0006 build stages invoke — gcc/g++, make, cmake, python3, git, the autotools family, dtc, mksquashfs, e2fsprogs, and so on — baked into the image at build time instead of apt-get install'd mid-build. It also bundles GuppyScreen's exact Bootlin mips32el-musl cross-toolchain, kept on a non-default PATH entry (GUPPYSCREEN_TOOLCHAIN_BIN) rather than the image's global PATH — its bundled autoreconf would otherwise shadow the system one a v4l2-ctl build needs, a real bug we found and fixed while putting this together.

See build-env/Dockerfile and build-env/versions.env in the repo for the exact, current, authoritative list.

What's deliberately NOT in it

  • Project source. NebulaOS-firmware, NebulaOS-kernel, NebulaOS-klipper, NebulaOS-guppyscreen, Buildroot, Moonraker, kernel source — all fetched fresh by 00-fetch-vendor-sources.sh at build time, pinned in manifests/dependencies.conf. Think of the image as the factory and the manifest as the material list.
  • The kernel/rootfs/native-app target compiler. mipsel-buildroot-linux-gnu-* is Buildroot's own self-bootstrapped toolchain, built from source during Stage 03 from the project's pinned Buildroot revision. Bundling a pre-built copy would defeat the whole point of pinning Buildroot — this image only supplies the host compiler Buildroot itself needs to build it.

Why pin by digest instead of a tag

A floating tag (:latest, :candidate) can get silently repointed at different content later — we actually closed exactly this gap once already for pellcorp/k1-bash-build, the container this one replaced. A digest is content-addressed: ghcr.io/coreflake1/nebulaos-build@sha256:... can only ever resolve to the exact bytes that produced that hash. manifests/dependencies.conf's BUILD_IMAGE_DIGEST is the one place this is recorded, and build.sh reads it directly — never a tag.

Why Buildroot still builds its own toolchain

Because that's what makes the kernel and rootfs reproducible from the pinned Buildroot source. Baking a pre-built mipsel-buildroot-linux-gnu-gcc into the build image would mean the actual compiler producing NebulaOS's kernel/rootfs/native-app binaries is no longer traceable to a pinned, auditable source. This image changes where the build runs, not what Buildroot itself produces.

Building the OS

Unchanged by any of this:

git clone https://github.com/coreflake1/NebulaOS-firmware.git
cd NebulaOS-firmware
./build.sh

build.sh pulls the pinned nebulaos-build image and runs the whole 0006 pipeline inside it directly — no nested pellcorp/k1-bash-build/guppydev containers, no /var/run/docker.sock requirement, no per-stage apt-get. Just Docker or Podman on the host.

Rebuilding the build image itself

docker build -t nebulaos-build:local build-env/

To publish a new candidate (never automatically canonical): push to a branch touching build-env/**, or run .github/workflows/build-environment.yml via workflow_dispatch. It publishes to ghcr.io/coreflake1/nebulaos-build under a dated/short-SHA tag and prints the resulting digest. Promotion to canonical is always a separate, deliberate, human step.

How a new build image gets promoted

Only after all of:

  1. The candidate image is built from tracked build-env/Dockerfile content and pushed to GHCR.
  2. Its digest is known and recorded.
  3. A fresh clone is rebuilt using exactly the source refs already accepted as the reference — same KERNEL_PIN/KLIPPER_PIN/GUPPYSCREEN_PIN, not newer main, not newer anything.
  4. That rebuild's output is strictly compared against the accepted reference artifacts (hashes, build-manifest.txt fields, 06-verify.sh's content checks).
  5. Every difference found is understood and explicitly classified — either expected/deliberate, or a real regression to fix before promoting.

Only then does a human update BUILD_IMAGE_DIGEST in manifests/dependencies.conf, in its own reviewed commit. Never automated, never silent.

Two migrations, only one of them done

  • Migration A (done): replace the two nested external containers with one NebulaOS-owned image — a change in container ownership/structure only. GuppyScreen's compiler is preserved byte-for-byte.
  • Migration B (investigated, not executed): converge GuppyScreen from its Bootlin musl toolchain onto Buildroot's own mipsel-buildroot-linux-gnu-* toolchain. That's a real product/ABI change (musl vs. glibc), not just a packaging change, and was deliberately kept out of Migration A's scope.

Being honest about the reproducibility limits

  • apt-get install packages aren't individually version-pinned. Ubuntu's apt repos serve whatever package versions are current when the image is built, not when the Dockerfile is written — so rebuilding build-env/Dockerfile today vs. a year from now can resolve different gcc/make/etc. point releases. The Dockerfile itself isn't perfectly bit-reproducible on rebuild. What is reproducible: the published, digest-pinned image is immutable once built — anyone pulling ghcr.io/coreflake1/nebulaos-build@sha256:... gets the exact same bytes forever. The pin is on the resulting image, not a promise that rebuilding the Dockerfile reproduces it identically. (pellcorp/k1-bash-build, the container this replaced, had this exact same property and limitation — not a regression, just now made explicit.)
  • The Bootlin GuppyScreen toolchain and the Ubuntu base image are both pinned (URL + SHA256, and base image digest respectively) — those two layers genuinely are exactly reproducible.

The old containers are actually gone

As of the Final Closure mission (2026-08-15): this image is canonical, manifests/ dependencies.conf's old PELLCORP_K1_BASH_BUILD_IMAGE pin is removed, and no script in this repo references pellcorp/k1-bash-build or ghcr.io/coreflake1/guppydev as a live dependency any longer. NebulaOS-guppyscreen's own CI has since migrated too — it now builds inside this same image rather than the standalone guppydev container it used to depend on.

Related pages

Also tracked as docs/NEBULAOS_BUILD_ENVIRONMENT.md in the repo.