# Image composition How Alloy is built and delivered. bootc + Containerfile, source on `git.sr.ht`, CI on `builds.sr.ht`, container image on `quay.io`. Companion to [STACK.md](STACK.md). ## Landscape Silverblue and its atomic siblings don't have a mutable root filesystem. The OS is an immutable commit, a tree of files that swaps atomically at boot. Users can't `dnf install` into a running system; they either layer packages locally (rebuilding their image) or pull a pre-built image. For a distro builder, "image composition" is the question of how you produce that immutable commit and how users receive it. ## Options considered ### rpm-ostree compose (traditional) Classical Silverblue build. Treefile in YAML declares packages, kernel arguments, systemd presets. `rpm-ostree compose tree` produces an ostree commit, pushed to an ostree repository. Users pull via `rpm-ostree upgrade`. **Cost:** you host an ostree repository (real infrastructure: nginx serving `/var/ostree` or equivalent). Legacy-shaped; Fedora itself is migrating away. ### bootc + Containerfile (modern, where Fedora is going) The OS *is* an OCI container image. Distributed via any OCI registry. Users pull with `bootc switch ` and reboot. Build is `podman build` from a Containerfile, the same as building any Docker image. **Cost:** pre-1.0, occasional breaking changes as bootc matures. Alloy accepts drift maintenance the same way it does for nushell config and userChrome.css. ### ublue-downstream (rejected) Universal Blue's convention is exactly the bootc + Containerfile pattern above, using `FROM ghcr.io/ublue-os/main`. Battle-tested. But being downstream of ublue means inheriting ublue's opinions and coupling to their release cadence, which [STACK.md's Base section](STACK.md#base) explicitly rejects. **The nuance:** Alloy uses the ublue-style *convention* (Containerfile, OCI registry, CI-driven build) *without* using ublue's base image. `FROM quay.io/fedora/fedora-bootc:43`, not `FROM ghcr.io/ublue-os/main`. That's what "alongside ublue, not downstream" resolves to in practice. ### "Install Silverblue and run our script" (non-option) Zero build infrastructure. But users get a mutated Silverblue with an Alloy veneer, not shipped as "Alloy." Manifesto commitment breaks. Rejected. ## Pick: bootc + Containerfile The tool choice. Rationale summarized: aligns with where Fedora is going; build tooling is `podman build`; distribution is any OCI registry (free); Containerfile is a single-file OS specification. ## Delivery stack ### Source: `git.sr.ht/~maxmj/alloy` Primary remote. Alloy is source-hosted on sr.ht for the same reasons the rest of Make Creative's public work is: independence from big-tech-monopoly infrastructure at the layer where users install software. `srht` is a canonical remote for Alloy, not a backup. ### CI: `builds.sr.ht` (disabled during local-iteration phase) sr.ht ships Fedora build VMs with `dnf`, `podman`, `buildah`, `skopeo` preinstalled. When ready, the build file at `.builds/alloy-image.yml` triggers on every push. **Current state:** the build file lives at `builds.disabled/alloy-image.yml`, parked outside the `.builds/` path sr.ht auto-runs, so no CI resources are consumed while the Containerfile is being iterated locally. Rename the directory back to `.builds/` and push to re-enable. ### Registry: `quay.io` (deferred until distribution starts) Red Hat's container registry is the intended destination once Alloy has an image worth distributing. Free for public images, ecosystem-coherent with the `quay.io/fedora/fedora-bootc:43` base. **Current state:** no registry provisioned. `.builds/alloy-image.yml` runs a **build + lint smoke test** only, validating the Containerfile on every push, without publishing anywhere. When the image is worth distributing, the CI file grows back `sign` and `push` tasks and `quay.io/alloy/alloy` gets provisioned with a robot account. Rejected alternatives (for when distribution starts): ghcr.io (source-registry ecosystem mismatch), Docker Hub (pull rate limits, no advantage), self-hosted (real infra commitment, not worth v0 operational load; revisit v2+). ### Signing: cosign (deferred alongside distribution) Once a registry is publishing images, cosign signs them as a CI task. Private key stored as an sr.ht secret; public key published in the Alloy repo. Users verify via `bootc switch --signature-source=`. Nothing to set up until distribution starts. ## Layer structure The Containerfile at the repo root (see `Containerfile`) sketches: 1. **Base:** `FROM quay.io/fedora/fedora-bootc:43`. 2. **Third-party repos:** Tailscale, any COPRs Alloy depends on for packages not in Fedora main. 3. **Package additions:** the full Alloy stack from [STACK.md](STACK.md): compositor, bar, launcher, notifications, terminal, editor, shell, viewers, utilities, continuity daemons, fonts, themes. 4. **Package removals:** stock Silverblue desktop pieces Alloy replaces (gnome-shell, gdm; the latter gated on the greeter pick). 5. **Config tree:** the tree at `etc/skel/.config/*` and `etc/skel/.mozilla/*` (new-user defaults, including the Firefox first-launch profile seed), `etc/*` (system-wide, including `etc/firefox/policies/policies.json`), and `usr/lib64/firefox/*` (Firefox autoconfig + `mozilla.cfg` default prefs) in the repo maps 1:1 into the image. 6. **Systemd presets:** which services are enabled by default (syncthing off by default, gammastep off until enrolled, alloy-hinged conditionally on FW12, etc.). 7. **Branding:** os-release, plymouth splash. 8. **Validation:** `bootc container lint` runs at build. The base browser (Firefox) ships as an RPM baked into the image: one code path, no first-boot delay, and enterprise policies (`/etc/firefox/policies/policies.json`) take effect immediately. The `flatpak` client is included so users can pull ungoogled-chromium and other Flathub-only apps on demand post-install; no Flatpaks are provisioned at build or first-boot time. ## Update cadence The sr.ht build triggers on: - **Push to `main`**: Alloy commits. - **Weekly cron**: picks up upstream Fedora base image updates. sr.ht doesn't have webhook receivers for external triggers, so cron is the pragmatic answer. Users receive updates via `bootc upgrade`: no push notifications, no forced restart. Alloy's update UX is the standard bootc UX. ## Fedora version tracking Pinned explicitly, currently `:43`. Bumps to `:44` and beyond are a deliberate act: Alloy tests the new base, updates the Containerfile, ships. Not automatic. This is the "correctness over deadline" pattern applied to base tracking. Note that `build/alloy-0.0.yaml` remains bootc-image-builder's fedora-42 package set, because bib ships no fedora-43 definition. That is deliberate and documented in the file's own header; it is not drift. ## Open questions - [ ] Verify which Alloy packages are in Fedora main vs. need COPRs. Candidates that may need COPRs: `satty`, `wl-screenrec` (depending on Fedora version). Audit at v0 packaging time. - [ ] `bootc-image-builder` for ISO generation. First-time-user path is `bootc install` from a live environment; the ISO is what makes that a smooth experience. Verify the ISO builder handles Alloy's specific package set. - [x] **Containerfile stage policy: multi-stage only where a non-Fedora Rust binary needs to ship.** Currently that's one stage (`rust-build` for wl-screenrec); everything else comes from RPM in the runtime stage. Do not add stages preemptively. - [x] sr.ht account: **`~maxmj`** for v0. LLC-owned `~makecreative` remains the long-term target; migration if/when the project grows. - [x] **Publish `:latest`, `:`, and `:-YYYYMMDD` tags.** Currently `:latest`, `:43`, `:43-YYYYMMDD`. `:latest` for casual users, the bare version for version-pinned users, the dated tag for reproducibility. Wired up when distribution starts.