This text was generated by an LLM. cgwalters has not reviewed it yet.
A generic bootc desktop and host image built
on Fedora Silverblue, published as ghcr.io/cgwalters-forge/atomic-linux.
It is a base to derive from, not a finished personal system: what is specific
to one machine or person (users, SSH keys, network and VPN setup, hardware
tuning, extra packages) belongs in a small downstream image that starts
FROM this one.
It started as a Universal Blue finpilot
image and keeps finpilot's multi-stage layout and examples. Status: early.
The image builds and passes bootc container lint, and CI boots each
build of it with just ephemeral-smoke and the k3s VM check. The KubeVirt test passed on the
predecessor image but has not been rerun here, nothing has run on hardware,
and images are not yet signed.
- Fedora Silverblue 44 (GNOME), digest-pinned, with Universal Blue's
Homebrew integration (
brew-setup, update and upgrade timers),ujustexample recipes, and example Brewfiles and Flatpak preinstall files.@projectbluefin/commonis in the build context but not overlaid. tmuxandgum(used by the ujust recipes);podman.socketenabled.sshd.serviceenabled, because bcvk's VM install waits for SSH. That is a real exposure; firewalld and SSH authentication still govern it.bootc installdefaults to a btrfs root.- Container short names are enforcing, with no search registries.
- firewalld enabled, and the
FedoraWorkstationzone without Fedora's default 1025-65535 tcp/udp opening (dhcpv6-client, samba-client and ssh stay). - k3s, checksum-pinned with k3s-selinux, installed but disabled. Its
configuration (SELinux, secrets encryption, Flannel VXLAN, Traefik and
ServiceLB off, NodePorts on loopback only, a
k3sfirewalld zone trusting only the pod and service CIDRs) and a preflight unit that loads the kernel modules and sysctls k3s needs only when k3s is enabled, so a desktop that never runs k3s is unaffected./usr/libexec/k3s-validateis a bounded acceptance check for test VMs.
A downstream repository needs little more than this Containerfile:
FROM ghcr.io/cgwalters-forge/atomic-linux@sha256:<digest>
ARG IMAGE_NAME=my-host
ARG IMAGE_VENDOR=me
ARG IMAGE_REGISTRY=registry.example.com/me
RUN /usr/libexec/atomic-linux/write-image-info
COPY system/ /
RUN dnf5 install -y <packages> && dnf5 clean all \
&& systemctl enable k3s.service
RUN bootc container lint --fatal-warningsPin the base by digest (take it from the build summary of the commit you
want, or skopeo inspect docker://ghcr.io/cgwalters-forge/atomic-linux:stable)
and bump it in its own pull request. Configuration hooks:
| Hook | For |
|---|---|
/usr/libexec/atomic-linux/write-image-info |
Record the derived image's name and update origin in image-info.json |
/etc/rancher/k3s/config.yaml.d/*.yaml |
k3s settings; use kubelet-arg+: to add kubelet reservations without replacing the base list |
/etc/default/k3s-preflight |
K3S_PREFLIGHT_REQUIRE_KVM=1 on KubeVirt hosts |
/etc/modules-load.d/, /etc/modprobe.d/ |
Hardware modules and options, e.g. kvm_intel nested=1 |
A derived image that installs packages should run dnf5 clean all (or reuse
this repository's build/clean-stage.sh pattern) so its own
bootc container lint passes.
bootc has two storage backends: ostree, the default here, and
composefs, which
bootc 1.17.0 declared stable. just build-composefs builds a
composefs-native variant as atomic-linux:composefs: build/15-bootc.sh
installs a newer bootc, ships /usr/lib/composefs/setup-root-conf.toml,
removes ostree's prepare-root.conf and adds bootc's 51bootc dracut module
to the initramfs. bootc install (and so bcvk) then picks the composefs
backend by itself, with GRUB and BLS entries; no --composefs-backend flag is
needed. The root is a read-only, fs-verity-checked EROFS image, and /etc and
/var are per-deployment and shared state directories under /sysroot/state.
It needs bootc 1.17 or newer, which Fedora 44 has only in updates-testing so
far; the build fails early with older bootc. source=copr takes upstream's
main branch from the rhcontainerbot/bootc COPR instead:
just build-composefs # bootc from Fedora updates-testing
just build-composefs composefs copr # bootc from upstream main
just composefs-vm-test # install, upgrade, switch, rollbackcomposefs-vm-test installs the build in a disposable libvirt VM on a btrfs
root and checks that the composefs backend is booted, that an /etc and a
/var edit survive bootc upgrade, and that bootc switch and
bootc rollback boot the expected images, with SELinux enforcing and the k3s
preflight unit working. CI builds and lints the variant on every pull
request and push to main, boots it with bcvk ephemeral and runs
composefs-vm-test, but does not publish it, and these jobs do not gate
stable.
Known gaps, as of bootc 1.17.1 (October 2026):
- There is no in-place move from ostree; install fresh. Do not
bootc switchan ostree install to the composefs build. - rpm-ostree, and so GNOME Software's OS updates, do not work; use
bootc upgrade(ujust update-and-reboot).bootc-fetch-apply-updates.timeris conditioned on/run/ostree-bootedand never runs (bootc#2578). - With no separate
/bootpartition, systemd's GPT auto-generator mounts the ESP on/bootwhile the BLS entries and GRUB configuration live in/sysroot/boot, sobootloader-update.service(bootupd) fails on every boot and the system shows as degraded. Upgrades and rollback are unaffected. - Upstream tests ext4 and XFS; btrfs, this image's default, is expected to work and passed the VM test here.
- No boot counting, logically bound images or
bootc edit; see bootc's known issues.
A derived image inherits the backend. If it changes bootc, it must also regenerate the initramfs, which carries bootc's mount code.
just build # podman build into containers-storage as atomic-linux:stable
just lint # shellcheck over the scripts
just hadolint # Containerfile lint, in a container
just fmt-check # Justfile formattingWith bcvk and rootless libvirt
(qemu:///session):
just ephemeral-smoke # stateless boot: identity, network, target
just vm-create && just vm-test # persistent VM on a btrfs root
just k3s-vm-create && just k3s-vm-test # enables k3s in the VM, runs k3s-validate
just kubevirt-test # nested KVM: disposable VM, KubeVirt L2 guestkubevirt-test needs nested virtualization on the machine running it and
removes the VM it creates; KEEP_FAILED_VM=1 keeps a failed one.
.github/workflows/build.yml builds every pull
request and boots the result in two VM jobs on a GitHub-hosted runner with
KVM and a pinned bcvk: vm-smoke runs just ephemeral-smoke, and vm-k3s
runs just k3s-vm-create and just k3s-vm-test. Both test the build job's
saved image, checked by image ID, not a rebuild. KubeVirt (just kubevirt-test) needs nested KVM inside the test VM and is not run in CI.
A push to main pushes the image to ghcr.io/cgwalters-forge/atomic-linux
as git-<sha> only. Once both VM jobs pass, the promote job retags that
digest as the version label (44.YYYYMMDD) and stable, after checking
that its config is the image the VM jobs booted; nothing is rebuilt. A
failed VM test leaves stable where it was, and promote skips a build
that is no longer the head of main. The workflow's
GITHUB_TOKEN (packages: write) is the only credential, and the job
summaries print the digest. Dependabot proposes digest bumps for the FROM
lines and action pins.
Not yet done: signing. The plan is a cosign key pair, not keyless signing, because containers/image (and so bootc) cannot match keyless signatures to a workflow identity. See the TODO at the end of the workflow.
Apache-2.0, see LICENSE. Derived from the Universal Blue finpilot template; see NOTICE for attribution.
Generated-by: https://github.com/cgwalters/#llms