A custom Fedora Atomic image based on Bluefin DX, optimized for development on a Framework Intel laptop.
- Base:
ghcr.io/ublue-os/bluefin-dx:stable— GNOME + dev tooling baseline (Fedora 44) - Shell: fish (default), with starship prompt
- Terminal: Alacritty, Zellij
- Editors: Emacs, Neovim
- Dev tools: gcc/make/gdb (Development Tools group), pandoc, aspell, fd, bat, eza, zoxide, fzf, jq, yq, httpie, zellij
- Browsers: Firefox, Nyxt
- Apps: 1Password
- Fonts: JetBrains Mono Nerd Font, Cascadia Code, Inter — with tuned subpixel rendering
- Framework extras: thermald, fprintd (fingerprint reader), powertop
The recommended path uses a custom installer ISO built from your exact
OCI image via
bootc-image-builder. You
boot from it and the Anaconda installer puts your image directly onto
disk — no internet required on the target machine after that.
Go to github.com/iwillig/dev-linux → Packages → dev-linux → Package settings → Change visibility → Public.
This is required for bootc-image-builder and bootc to pull the image without credentials.
Tag a release to trigger the CI build:
just release v0.1.0CI will build the OCI image, run bootc-image-builder to produce a custom Anaconda ISO, and attach it to the GitHub Release. Once the run finishes (~15 min), download it:
just download-release # saves to vm/Or download manually from the Releases page.
# Find your USB device
just usb-list
# Write the ISO (replace /dev/disk4 with your USB device)
sudo dd if=vm/dev-linux-v0.1.0.iso of=/dev/disk4 bs=4m status=progressOn macOS use
just usb-write /dev/disk4— it handles unmounting and uses the raw device automatically.
- Plug USB into Framework, power on, press F12 for boot menu
- Select the USB drive
- Follow the Anaconda installer — partition as you like, set username/password
- Reboot — you're running your custom image
Whenever you add or change packages, push to main, wait for CI to finish, then on the Framework:
sudo bootc update
sudo rebootbootc update pulls only the changed layers from GHCR (fast after the
first pull), stages the new image alongside the running one, and
activates it on next boot. Your data in /home is untouched.
To check whether an update is available without applying it:
sudo bootc statusTo roll back to the previous image if something goes wrong:
sudo bootc rollback
sudo rebootIf you already have Fedora Silverblue installed, you can rebase to this image directly without a custom ISO:
sudo bootc switch ghcr.io/iwillig/dev-linux:latest
sudo rebootAfter the initial switch, future updates work the same way:
sudo bootc update
sudo rebootRequires podman and just. Works on both Linux (native) and macOS.
just build # build amd64 image locally via podman
just shell # open bash inside the built image
just test # smoke-test: verify key commands and fonts
just check-fonts # list installed fonts
just check-packages # list installed packagesSince you're already running dev-linux, the fastest feedback loop is to build locally and switch the running system to your changes:
just local-switch # build → export → sudo bootc switch (staged, not yet active)
sudo reboot # activate the new imageIf something breaks after rebooting:
sudo bootc rollback
sudo rebootbootc keeps the previous image around, so rollback is instant.
Requires podman (brew install podman) and just.
Test the image in an isolated VM before installing on bare metal. Downloads a stock Fedora Silverblue ISO, installs it into a QEMU disk, then you rebase to your custom image. On Linux the VM uses KVM hardware acceleration; on macOS it falls back to TCG software emulation.
just vm-download-iso # one-time: download Fedora 44 Silverblue ISO (~2.5 GB)
just vm-create # one-time: create 60 GB disk (+ init OVMF_VARS on Linux)
just vm-install # one-time: boot installer, follow Anaconda
just vm-run # start the VM
just vm-ssh # SSH into the running VM
# Inside the VM, switch to your image:
sudo bootc switch ghcr.io/iwillig/dev-linux:latest
sudo rebootTo test a locally-built image without pushing to GHCR:
just vm-snapshot # save a rollback point
just vm-load-local # push local image into VM via SSH
# Inside VM:
sudo bootc switch --transport containers-storage localhost/dev-linux:local
sudo rebootSkip the Silverblue install step entirely by downloading the pre-built qcow2 from a release:
just download-release # downloads and decompresses the qcow2 into vm/
just vm-run # boot straight into your imagejust release v0.2.0This tags the commit and pushes the tag. GitHub Actions then:
- Builds the OCI image and pushes it to
ghcr.io/iwillig/dev-linux:latest - Runs
bootc-image-builderto producedev-linux-v0.2.0.isoanddev-linux-v0.2.0.qcow2.zst - Attaches both to a GitHub Release
You can also trigger the disk image build manually from the Actions tab (useful for testing the ISO without tagging).
The laptop panel (eDP-1) auto-disables whenever the ViewSonic XG3220
external monitor is connected, so the external monitor is the only active
display rather than extending/mirroring — handled by kanshi
(/etc/kanshi/config), matched by monitor make/model/serial rather than
connector name so it keeps working regardless of which port/dock it's
plugged into.
- sway: automatic via kanshi. Manual override:
mod+shift+i(laptop only) /mod+shift+o(external only), orkanshictl switch laptop|externalfrom a terminal. - GNOME: kanshi requires the wlr-output-management protocol, which
mutter doesn't implement, so switching there is manual — use Settings →
Displays, or
gnome-monitor-config list/setfrom a terminal.
To support a different external monitor, update the output "..." match
string in /etc/kanshi/config — get the exact make/model/serial via
swaymsg -t get_outputs.
Sharing a screen or window in Zoom silently fails to start. The portal logs the error:
journalctl --user -u xdg-desktop-portal-wlr
[ERROR] - wlroots: No supported targets specified
This is an upstream bug in xdg-desktop-portal-wlr 0.8.1/0.8.2 (the version
shipped in Fedora 44):
emersion/xdg-desktop-portal-wlr#379.
SelectSources defaults its requested type-mask to 0 instead of MONITOR
when the caller omits the types option, so the type intersection is always
empty and the call fails — regardless of whether you pick a monitor or a
window in the share dialog. It is not specific to Zoom; any client that omits
types (per the portal spec, which allows this) hits it.
As of 2026-06-20, the latest upstream release (0.8.3) does not include a fix. A one-line patch is posted on the issue and has been community-confirmed to work, but isn't merged yet. Revisit once upstream merges a fix or Fedora picks up a patched build — until then, no workaround is applied in this image.