Repository for my custom Universal Blue (ublue) container images. Images are based on Bluefin DX, Aurora DX, and Bazzite DX, with layered customizations. The project builds container images for GitHub Container Registry (GHCR) and can produce bootable disk images (QCOW2, raw, ISO) via Bootc Image Builder.
- Multi-image matrix: Builds several distro/variant combinations from a single repo (see
images.yaml). - Layered customizations: Global → shared (opt-out) → distro common → variant scripts in
build_files/. - CI/CD: GitHub Actions build and push images on push/PR to main; optional Cosign signing.
- Disk images: Optional local builds of QCOW2, raw, or anaconda ISO using Bootc Image Builder and
just.
├── build_files/ # Image customizations (run at build time)
│ ├── global.sh # Applied to all images
│ ├── shared/ # Shared layers (opt-out per distro in the wrapper)
│ │ └── gaming.sh # Steam/Gamescope stack; skipped on Bazzite
│ ├── build-wrapper.sh # Orchestrator: global → shared → distro common → variant
│ ├── build.sh # Shim for default distro/variant
│ ├── bluefin-dx/
│ │ ├── common.sh # Bluefin DX–specific
│ │ ├── macintel.sh
│ │ └── nvidia.sh
│ ├── aurora-dx/
│ │ ├── common.sh
│ │ └── nvidia.sh
│ └── bazzite-dx/
│ ├── common.sh
│ ├── nvidia.sh
│ └── nvidia-gnome.sh
├── disk_config/ # Bootc Image Builder configs
│ ├── disk.toml # QCOW2/raw disk layout
│ └── iso.toml # Anaconda ISO (IMAGE_REF replaced in CI)
├── scripts/ # CI and local tooling
│ ├── ci-matrix.sh # Build matrix from images.yaml + changed files
│ ├── ci-disk-matrix.sh # Matrix for disk image workflow
│ ├── list-images.sh # List distro/variant from images.yaml
│ ├── get-image-base.sh # Base image for a distro/variant
│ └── get-next-tag-number.sh # Next date tag for GHCR (user packages)
├── images.yaml # Distro/variant → base image mapping
├── Containerfile # Multi-stage build; uses build_files via build-wrapper
├── Justfile # Local build and disk image recipes
└── .github/workflows/
├── build.yml # Build and push container images
└── build-disk.yml # Build disk images (QCOW2, raw, anaconda-iso)
Defined in images.yaml. Every image receives global customizations first, then shared layers (unless the distro opts out), then distro common, then variant-specific (see build_files/).
- Cursor IDE: installed via official RPM with
tsflags=noscripts(avoiding Flatpak issues and install scriptlets that fail in container builds); URL/MIME handlers are registered by the final desktop/MIME DB refresh inbuild-wrapper.sh. - ChatGPT Desktop / Codex: installed via official OpenAI RPM with
tsflags=noscripts; the packagedchatgpt.repois left disabled so updates come from image rebuilds, not rpm-ostree/dnf layering; scheme handlers (e.g.codex://) are registered by the same final DB refresh. - OpenLogi: installed via official GitHub RPM with
tsflags=noscripts; the user unitopenlogi-agent.serviceis enabled globally so the HID++ agent starts at login. - RPM Fusion: free and nonfree repos enabled on every image (Bluefin/ublue do not ship them by default).
- Proton apps: Proton VPN (GNOME desktop + daemon), Proton Mail Desktop (beta), Proton Pass (official RPMs).
- Services:
podman.socketenabled.
- Gaming stack: Steam, Gamescope, GameMode, and MangoHud (
build_files/shared/gaming.sh). Bazzite already ships this stack, so the wrapper skips it there.
| Distro | Variant | Base image | Customizations |
|---|---|---|---|
| bluefin-dx | macintel | ghcr.io/ublue-os/bluefin-dx:stable |
Global + gaming + Broadcom WiFi: akmods, broadcom-wl (e.g. BCM4360); kernel-devel and wl kernel module built at image build time (RPM Fusion already enabled globally). |
| bluefin-dx | nvidia | ghcr.io/ublue-os/bluefin-dx-nvidia-open:stable |
Global + gaming (Nvidia stack comes from base). |
| aurora-dx | nvidia | ghcr.io/ublue-os/aurora-dx-nvidia-open:stable |
Global + gaming (Nvidia stack comes from base). |
| bazzite-dx | nvidia | ghcr.io/ublue-os/bazzite-dx-nvidia:stable |
Global only (KDE + Nvidia open kernel module + DX tooling + gaming stack come from base). |
| bazzite-dx | nvidia-gnome | ghcr.io/ublue-os/bazzite-dx-nvidia-gnome:stable |
Global only (GNOME + Nvidia open kernel module + DX tooling + gaming stack come from base). |
To add or change images, edit images.yaml and add or adjust scripts under build_files/<distro>/ (common.sh and <variant>.sh).
- Local container builds: Podman.
- Justfile commands: just.
- Scripts:
yq(forimages.yaml),jq,bash. - Disk images (optional): Root/sudo for Bootc Image Builder;
bootc-image-buildercontainer image (seeJustfile/BIB_IMAGE).
List available images:
just list-imagesBuild a single container image (e.g. Bluefin DX macintel):
just build bluefin-dx macintelBuild all images:
just build-allDefault image name and tag can be set via environment variables IMAGE_NAME and DEFAULT_TAG (see top of Justfile).
Disk image builds require root (Bootc Image Builder runs privileged). Use sudo:
sudo just build-qcow2 bluefin-dx macintelSimilar commands: build-raw, build-iso. Output goes under output/. To run a VM from a QCOW2 image:
just run-vm-qcow2 bluefin-dx macintelOr use systemd-vmspawn:
just spawn-vm bluefin-dx macintelConfigs: disk_config/disk.toml (QCOW2/raw), disk_config/iso.toml (anaconda ISO).
At build time the Containerfile runs build-wrapper.sh, which executes in order:
global.sh— applied to every image.build_files/shared/gaming.sh— applied to every image except Bazzite (DISTROmatchingbazzite-*).build_files/<distro>/common.sh— applied to all variants of that distro.build_files/<distro>/<variant>.sh— applied only to that (distro, variant).- Desktop/MIME DB refresh —
update-desktop-databaseandupdate-mime-database, so URL scheme handlers and MIME types from packages installed withtsflags=noscripts(and any future layered apps) are registered in the system caches.
Edit these scripts to add packages, repos, or other changes. The CI matrix only rebuilds images whose layers (or Containerfile / images.yaml) changed.
build.yml: On push/PR to default branch, computes which (distro, variant) pairs to build from changed files andimages.yaml, then builds and pushes to GHCR. On the default branch (non-PR), images are tagged with a date-based tag (YYYYMMDD.N) and signed with Cosign ifSIGNING_SECRETis set.build-disk.yml: Builds disk images (QCOW2, raw, anaconda-iso) per matrix fromscripts/ci-disk-matrix.sh, usingdisk_config/disk.tomlanddisk_config/iso.toml.
Containers are published under ghcr.io/${{ github.repository_owner }}/<package>; the next date tag is determined from the user’s GHCR packages (see scripts/get-next-tag-number.sh).
If images are signed with Cosign, you can verify them using the public key in the repo:
cosign verify --key cosign.pub ghcr.io/<owner>/<image>:<tag>| Command | Description |
|---|---|
just list-images |
List distro/variant from images.yaml |
just build <distro> <variant> |
Build one container image |
just build-all |
Build all images |
just build-qcow2/raw/iso <distro> <variant> |
Build container + disk image |
just run-vm-qcow2/raw/iso <distro> <variant> |
Run VM from disk image |
just spawn-vm <distro> <variant> |
Run with systemd-vmspawn |
just lint |
Run shellcheck on shell scripts |
just format |
Run shfmt on shell scripts |
just check / just fix |
Check/fix Justfile syntax |
just clean |
Remove build artifacts |
See LICENSE.