Showcase/Learning Project: Building a Pure Fedora 44 bootc + Hyprland + Nix Image

Hey everyone,

I started a new learning project today to dive deep into bootc and image-based deployments. My goal was to build a highly minimal, atomic Hyprland base where the OS core is completely unopinionated, and the entire userland is strictly declarative.

The CI pipeline finally went green today, and I’m preparing for bare-metal testing soon. I wanted to share the architecture here to get some community feedback or see if anyone else is building along these same lines.

Core Philosophy & Architecture:

  • The Base: Built on Quay . I deliberately avoided the desktop bases (Silverblue/Kinoite) to ensure a completely headless, bloat-free foundation before layering the Wayland stack.

  • The Compositor: Hyprland, pulled from the lionheartp COPR to ensure compatibility with Fedora 44’s latest libdisplay-info and aquamarine packages.

  • Strict Nix Userland: The image ships with the official nix and nix-daemon packages. All user applications are intended to be managed via Nix Flakes and Home Manager.

  • Zero Flatpaks: To avoid split-brain package management and duplicate permission sandboxing, flatpak and its session helpers are actively purged during the container build.

Technical Hurdles Solved:

1. Licensing & Hardware Acceleration (NVIDIA) To respect Fedora’s licensing, the base image is 100% open source and ships no proprietary blobs. However, to make it actually usable for NVIDIA users, the container enables the RPM Fusion release packages. This allows users to easily run rpm-ostree install akmod-nvidia on their local machines after the first boot, compiling the drivers locally and signing them with their own Secure Boot Machine Owner Keys (MOK).

2. Mutable SDDM Themes on an Immutable OS Standard SDDM installs themes to /usr/share/sddm/themes, which is locked down as read-only on an atomic OS. To allow users to style their login screen without having to rebuild the entire container image, the Containerfile provisions a /var/lib/sddm/themes directory. A custom sddm.conf is injected pointing the theme directory to this writable /var path, while explicitly setting DisplayServer=wayland to ensure a tear-free handoff to Hyprland.

3. The Atomic /nix Directory Hack Because bootc relies on an immutable, read-only root filesystem, standard Nix installations fail since they cannot write to /nix. To solve this, the Containerfile deletes the default directory and uses systemd tmpfiles.d to recreate it during the boot process:

Plaintext

L+ /nix - - - - /var/nix
d /var/nix 0755 root root -

This forces a symlink from the read-only root directly to the writable /var/nix partition before the user even reaches the SDDM login screen, allowing the Nix daemon to function flawlessly.

I’m still smoothing out the edges and getting ready for the first hardware rebase test. If anyone has experience mixing bootc with strict Nix environments, I’d love to hear your thoughts or any pitfalls I should watch out for!

feel free to move this and add/remove tags if it is wrong place or wrong tags

Cheers, Phatle

3 Likes

Hey everyone,

I wanted to share a major milestone update on my minimal Fedora bootc image project. Over the past week, I’ve been heavily restructuring the architecture to tackle what feels like the “Final Boss” of custom Atomic setups: NVIDIA drivers with Secure Boot fully enabled. Rather than relying on local layering or turning off Secure Boot, the image is now a fully self-sufficient, signed OS factory.

Here are the major architectural changes just pushed:

1. Automated MOK Signing in CI (Multi-Stage Build)

I transitioned the Containerfile to a multi-stage build via Woodpecker CI.

  • Stage 1: Injects my private Machine Owner Key (MOK) securely via CI secrets, compiles the akmod-nvidia drivers against the latest kernel, and uses sign-file to cryptographically stamp the .ko modules.

  • Stage 2: Injects the pre-compiled, signed binaries into the final bootc image and bakes the public .der certificate into /etc/pki/akmods/certs/.

The result? Pure bootc upgrades. I enroll the public key in my BIOS once, and every subsequent update pulls a pre-signed NVIDIA driver that boots instantly under Secure Boot without local akmods compilation.

2. Strict Immutable Compliance (tmpfiles.d)

To ensure the image strictly adheres to read-only root philosophies while still supporting a pure Nix userland, I ripped out the static file copying.

  • Nix: The /nix -> /var/nix symlinks and directory provisioning are now completely handled dynamically by systemd tmpfiles.d on boot.

  • SDDM: The writable theme bridge for SDDM (/var/lib/sddm/themes) is also managed via tmpfiles.d with proper 0750 permissions, allowing persistent theming on an immutable base.

3. A Clean /etc Directory

Moved all default configurations (like forcing SDDM to use Wayland) into the vendor path at /usr/share/sddm/sddm.conf.d/. This leaves the /etc directory completely clean and unopinionated for local, machine-specific overrides.


It has been a massive learning curve piecing together the CI-side signing logic, but having a custom OS image that natively passes Secure Boot validation out-of-the-box is incredibly satisfying.

If anyone is struggling with Secure Boot on custom bootc images or wants to see how the multi-stage Woodpecker pipeline is set up, I’ve fully documented the MOK trust-chain logic in the repo:

Repo: [ Marko Jokinen / fedora-hyprland-atomic · GitLab ]

Feedback, critiques on the Containerfile logic, or tips on further minimizing the base are always welcome! will start full bare metal testing wednesday to see everything is actually working as i wanted. builds are now finally working as should

2 Likes

I finally got the pipeline built and running smoothly on GitLab—no hacks, no issues! I spent hours debugging why my rebase kept failing, and the culprit was the old builder breaking the ostree hardlinks. The rebase is now 100% clean and working straight out of the box.

I’m running a fresh build right now to fix one last bug: LUKS drives weren’t remounting because systemd-remount-fs.service was missing from the end of the build file. Let’s see if this fixes the LUKS issue, and if so, the whole setup is golden.

Well switched to easier way to achieve my goals for now since my skills wasn’t yet there fully to solve boot issues and others.

I chooses to use for now ublue base-nvidia image as starter and there ublue build template to quick start things and then just do what I was doing before the wrong way aka hard way.

Now I have bootc hyprland nix build and I love it

1 Like

Nice work, thanks for sharing!

I also went the blue-build way: bluebuild-images/recipes/silverblue-cosmic-nix.yaml at main · alexandregv/bluebuild-images · GitHub

To handle the /nix problem with immutable /, I created a bind-mount from /var/nix/ to /nix/:

Works like a charm!

PS: I noticed your gitlab repository is pending deletion, I think it would be great to keep it, this can help someone or allow you to take a second chance on this projet later.

Yeah I deleting that one it was a messy… also i used trivy on this one to scan CVE etc and then i got news it have huge cubly chain attack so better to delete the full project straight and was not ublue way. Was trying to make it my own.

I have GitHub repo now for current one and migrate that to my own forgejo server soon. I will make update here with all the details after migrate is done and I write proper readme on that.

Lots of changes on it and I did same to my nix works like a charm. And no permission issues at all just today removed flatpak fully and cleaning more

I actually need to make it modules and receipt way too so all is setup straight don’t have that now

Thanks for sharing! It is nice to see how others are implementing their bootc images. I also created something similar, but using Guix instead of Nix.

https://codeberg.org/16levels/guix-silverblue

1 Like

Hi, thanks for sharing, but Nix won’t accept a symlinked /nix on my computer. How do you get Nix to work?

My solution is to create a Btrfs volume and mount it at /nix. It has worked fine so far.

Yes I have /nix subvolume made too I should update my partition list here. Only issue on my bootc image is that nix dosent yet see dgpu so no hardware acceleration or cuds in nix install haven’t found solution on it yet

Ah, I ran into the same issue recently. It seems all Nix apps try to find the driver in /run/opengl-driver/, which only exists on NixOS.

A workaround is to enable targets.genericLinux.enable = true;. Then, after switching, home-manager will show something like:

Activating checkExistingGpuDrivers
This non-NixOS system is not yet set up to use the GPU
with Nix packages. To set up GPU drivers, run
sudo /nix/store/0336984d983pkkrh38r9ld73ains7za5-non-nixos-gpu/bin/non-nixos-gpu-setup

Edit: relevant home-manager doc: home-manager/docs/manual/usage/gpu-non-nixos.md at master · nix-community/home-manager · GitHub

Running the command will create a systemd service that links the drivers in your home directory to the correct path.

The script content and systemd service content are as follows, for reference:

#!/nix/store/i27rhb3nr65rkrwz36bchkwmav6ggsmn-bash-5.3p9/bin/bash

set -e

# Install the systemd service file and ensure that the store path won't be

# garbage-collected as long as it's installed.

unit_path=/etc/systemd/system/non-nixos-gpu.service
ln -sf /nix/store/7khlgj3b25j95pnnk9wj5sa9734m6s9x-non-nixos-gpu/lib/systemd/system/non-nixos-gpu.service "$unit_path"
ln -sf "$unit_path" "/nix/var/nix"/gcroots/non-nixos-gpu.service

systemctl daemon-reload
systemctl enable non-nixos-gpu.service
systemctl restart non-nixos-gpu.service
[Unit]
Description=GPU driver setup for Nix on non-NixOS Linux systems

[Install]
WantedBy=multi-user.target

[Service]
Type=oneshot
ExecStart=ln -nsf /nix/store/26lyy9zm656frjh6m0fl988b1fml9blx-non-nixos-gpu /run/opengl-driver
RemainAfterExit=yes

But linking a user directory into /run/opengl-driver feels hacky. Imagine two users using the same workaround at the same time: the later user would overwrite the service and determine which driver gets linked.

I haven’t found a better solution yet.

P.S. For the /nix directory, I think it’s better to use a bind mount to bind it to /var/nix. This follows Fedora Atomic’s convention better and can be done directly in the Containerfile, avoiding extra steps after installation.

[Unit]
Description=Bind mount /var/nix to /nix

[Mount]
What=/var/nix
Where=/nix
Type=none
Options=bind

[Install]
WantedBy=local-fs.target

Edit: @alexandregv already mentioned it; I missed that comment earlier.

I’ve got an idea to use Filesystem in Userspace (FUSE) to create a FUSE mount point at /run/opengl-driver, which returns a different driver path depending on the user accessing the path.

The driver path that a user’s home-manager is referencing can be found with something like:

> nix-store -qR "$HOME/.local/state/home-manager/gcroots/current-home" | grep "non-nixos-gpu"

/nix/store/26lyy9zm656frjh6m0fl988b1fml9blx-non-nixos-gpu
/nix/store/7khlgj3b25j95pnnk9wj5sa9734m6s9x-non-nixos-gpu
/nix/store/w9bsaimh4f8b6w1n66nyajsm058rf0d9-non-nixos-gpu-fish-completions

I’m not sure if this will work. I’ll dig into it more when I have time.

1 Like

Nice findings there will test these later I am now on another project where I use same base, but no nix on user level and running diatrobox style setup. More isolated system will upload later the repo here when I get it updated and added all the configs etc

Basically I am building from fedora bootc as base and then take what I need like Nvidia akmods for ublue and diatrobox. All flatpaks as flat hub user level. Making all configs and dotfiles ready to everyone. Noctalia shell hyprland still. Running now 3 containers packages, development and cyber security packages is all available on host. Dev half but dev directory folder not so those stays there and fully isolated cyber security. Still lots of stuff to do and cleaning.

Will start nix build maybe on weekend to see if I can get more on it again

here is the latest one i have come long way on learning and i have time now since UNI on summer holiday and other school graduated already. This one is really fun one and been figuring out so much. it mostly builds and starts, anaconda iso installer works just now figuring all the packages i need to add and configs. This one can be run on mad64 and arm64 tho arm64 havent tested at all no arm devices at home. Might need to get one someday and slowly getting all set for working optimised settings

Please check the GitHub Advisories

Nothing there and it needs to be there and private key is git-crypted. This I made fully following ublue blue build guide

Ah, I see now. I missed it. Thanks for explaining!

1 Like