Build Guide
Everything about producing bundles. This section is for:
- Release engineers cutting
v*tags. - Contributors changing
bundle.yaml, the builder code, or the manifest schema. - Operators who need to build a site-specific bundle (internally mirrored
.debs, a custom aether-ops build, a pinned RKE2 version).
If you're installing an already-built bundle, you want Getting Started instead.
The moving parts
flowchart LR
spec["bundle.yaml<br/><i>human-edited</i>"]
lock["bundle.lock.json<br/><i>committed</i>"]
builder["cmd/build-bundle<br/><i>Go tool</i>"]
tarball["bundle.tar.zst<br/><i>+ manifest.json</i>"]
sidecar["bundle.tar.zst.sha256"]
spec --> builder
lock --> builder
builder --> tarball
builder --> sidecar
bundle.yaml— the spec. Every build starts here.bundle.lock.json— pinned.debversions and hashes, committed with the spec. The builder warns when the current resolution differs from the existing lockfile.cmd/build-bundle— the builder. Reads the spec, fetches + verifies artifacts, writes the lockfile, assembles the tarball, emitsmanifest.json.manifest.json— the contract between builder and launcher. Lives inside the tarball.
Typical workflows
I just want to build the current bundle
make bundle
Produces dist/bundle.tar.zst and dist/bundle.tar.zst.sha256. See
building locally.
I'm bumping RKE2 (or any upstream pin)
- Edit
specs/bundle.yaml— change the version. - Run
make bundle. The builder re-fetches, re-pins, and rewrites the lockfile. - Commit both
specs/bundle.yamlandspecs/bundle.lock.jsonin the same PR.
See versioning for which version numbers move when.
I'm cutting a release
Tag and push:
git tag v0.1.44
git push origin v0.1.44
The release workflow takes it from there: GoReleaser builds the launcher, the bundle builder is run in CI, SBOMs and vulnerability scans are generated, and every artifact is attached to the GitHub release.
I'm overriding template files for a deployment
You have two options, depending on whether the override should ship as part of the spec.
Reproducible (rebuild from spec). Add an
onramp.patches: block listing the
files to override and run make bundle as usual.
Ad-hoc (no rebuild). Patch an existing bundle directly with
patch-bundle:
make build-patch-bundle
./dist/patch-bundle \
--in dist/bundle.tar.zst \
--out dist/bundle-patched.tar.zst \
--replace ocudu/roles/uEsimulator/templates/ue_zmq.conf=./ue_zmq.conf
Both paths produce a bundle whose manifest reflects the patched bytes, so the launcher detects the change and re-extracts on existing hosts.
I'm publishing the download page
make release-site
Renders a static site (dist/release-site/) with the current launcher,
bundle, SHA256s, and a versioned releases index. See the
download site generator.
I'm adding a new .deb to the bundle
- Add an entry under
debs:inspecs/bundle.yaml. make bundle— the builder pulls the package and its transitive deps.- Commit both files.
I'm using an internally mirrored aether-ops build
Edit aether_ops: in specs/bundle.yaml to point at a local file:
aether_ops:
version: "v0.1.43-custom"
source: ./artifacts/aether-ops_0.1.43-custom_linux_amd64.tar.gz
Or a private URL:
aether_ops:
version: "v0.1.43"
source: "https://artifacts.example.internal/aether-ops/0.1.43/aether-ops_0.1.43_linux_amd64.tar.gz"
What the builder refuses to do
- Download anything at launcher runtime. All fetching happens on the build machine; the launcher has no HTTP client for artifacts.
- Hide lockfile drift. If the lockfile exists and upstream
.debresolution has drifted, the builder logs a warning before rewriting it. - Publish unverified artifacts. Every downloaded file is hashed and
checked against its authoritative source (Ubuntu Packages index, GitHub
release checksums,
get.helm.sh).