Editions
Based on Fedora 44 COSMIC + Hyprland plus two headless appliances Image-based Fedora — transactional updates, identical on every machine, one-command rollback.Build it yourself
Based on Fedora 44 Clone the repo and run four commands. Every image is a chain of bootc layers, each an OCI image built from its parent.How an image is assembled
Each layer is an OCI image built FROM its parent, so a shared layer is built once and reused by everything below it. The appliance branch skips the desktop entirely. The gold leaves are the published images; intermediates are build infrastructure.
Four commands — plain bash and podman
- See the catalog.
./build.sh listEvery image with its DE, description and pin reason.
- Generate Containerfiles.
./build.sh generate allChain-aware Containerfiles under
generated/. - Build the images.
./build.sh build all # --push to publishEach shared layer built once, in dependency order.
- Make an ISO.
./build.sh iso carino-gamingbootc-image-builder (needs
sudo) →output/IMAGE/install.iso.
Classic (non-atomic) images — experimental
./build.sh blueprint IMAGE flattens an image's whole layer chain into an osbuild blueprint for the traditional Workstation backend. It is an export, not a build path — nothing here depsolves or builds it, so you hand it to composer-cli yourself:
./build.sh blueprint carino-general composer-cli blueprints push generated/carino-general/blueprint.toml composer-cli compose start carino-general image-installer
A blueprint is a lossy copy, never an equivalent. COPR packages need the repo configured host-side or the depsolve fails; Flatpaks, static files and post-install scripts are dropped; and a service whose unit ships only in the image's own file tree is omitted from enabled, because osbuild runs systemctl enable during the build and a missing unit fails it. Every omission is listed in the TOML header and warned about on the terminal.
Why only COSMIC and Hyprland?
Because every desktop shipped is a desktop that has to be verified against every purpose, and the combinations nobody tests are the ones that break. One session, built once, means the integration work — tearing control, per-output VRR, no file indexer, no automounter — is done once and inherited by all six desktop images.
Both sessions come from the same layer and the same greeter: pick COSMIC at login for pointer-driven use, or Hyprland when you want tiling. Purposes still declare which session they are pinned to, so adding a second desktop later stays a one-line change rather than a rewrite.
The telephony image has no desktop — why?
A PBX is an appliance, not a workstation: you reach it through its web UI and over SSH, and nobody sits in front of it. So carino-pbx pins the headless session, which branches off base and never touches desktop-common — no Wayland, no PipeWire, no portals, no Flatpak, no browser. Fewer packages to patch on the one machine in the building that answers the phones.
One caveat stated plainly: FreePBX itself is not part of the image. It writes to /var, and a bootc image copies /var into the machine once at install and never again — so baking it in would freeze it at the version you first installed from. The image ships Asterisk, MariaDB, Apache and the PHP 8.2 runtime FreePBX requires as RPMs that do update transactionally, and FreePBX installs itself on first boot, then updates with fwconsole ma upgradeall. bootc rollback covers the OS underneath it; it does not roll back a FreePBX module upgrade.
An offline appliance — why is that an image?
carino-offline is the intranet keystone: the one box a LAN with no working WAN still gets names, time, trust, packages and content from. It pins the same headless session carino-pbx does, because it is "an appliance that has to answer on the LAN's own ports before anything else on the network is up: DNS, NTP and HTTP are its interfaces and SSH is its console". The plan this box is one piece of is written up at offline.carino.systems, and that page points back here.
It is an image rather than a Carino Setup package list because five of its decisions are system integration a script run after installation cannot own. Port 53 has to be taken from systemd-resolved, which is installed and preset-enabled on this base, so it is masked rather than disabled — and /etc/resolv.conf, a symlink into resolved's run directory, has to be re-owned in the same breath or the box has no resolver at all. An internal CA needs an anchor directory a site root actually lands in, with update-ca-trust extract run where it takes effect. dnf has to be pointable at a LAN mirror without deadlocking the box's own tooling on a hostname that may not resolve. And chrony has to keep serving time with nothing upstream reachable, which takes one directive whose absence is silent for weeks. Nothing in this image has been booted yet — the package set was resolved against Fedora 44 and those system facts were read inside the built headless layer, but no ISO has been installed.
Why atomic, and what do updates look like?
The value of a purpose-built system is the integration — kernel args, service masking, session configuration — and on a mutable system that integration drifts. With bootc the whole OS is one versioned image: every install is identical, updates are transactional, and a bad one is a bootc rollback away.
bootc upgrade # staged, applied on reboot bootc switch <ref> # move between purposes bootc rollback # back to the previous deployment
Add your own purpose
Create one file, config/purposes/<name>.conf — plain bash, sourced by the build system. Four fields are required, the rest are optional:
PURPOSE="mything" # must match the filename DE="cosmic-hyprland" # or "headless" for an appliance PIN_REASON="…" # one line: why this session DESCRIPTION="…" # one line: what it is for PACKAGES="pkg @group" COPRS="owner/project" KARGS="…" REPO_RPMS="https://…/x-release.rpm" # non-COPR third-party repos SERVICES_ENABLE="a.service" SERVICES_MASK="b.service" FLATPAKS="org.app.Id" POST_SCRIPT="post.sh"
The catalog is derived by scanning config/purposes/*.conf, so ./build.sh list picks it up immediately. Everything that is "just a package list" for an already-installed system lives in Carino Setup instead.