Building a Custom Bluefin Image#
Overview#
finpilot is the recommended template for building custom Bluefin images. Rather than forking Bluefin and patching it, finpilot lets you assemble your own Bluefin by consuming the same centralized OCI containers that official variants — Bluefin, Aurora, Bluefin LTS, and dakotaraptor — are built from . This means you inherit shared maintenance improvements automatically without rebasing against a fork.
The core constraint is simple: all modifications happen at build time. The OS is delivered as a complete, signed container image. There is no runtime package installation — everything you want in the image must be incorporated during the build via the Containerfile and build scripts .
Getting Started#
Fork the Template#
- Go to github.com/projectbluefin/finpilot and click "Use this template" to create a new repository.
- Rename the project by replacing every instance of
finpilotwith your repository name in these six files :ContainerfileJustfileREADME.mdartifacthub-repo.ymlcustom/ujust/README.md.github/workflows/clean.yml
- Enable GitHub Actions in the Actions tab of your new repository.
Repository Layout#
| Path | Purpose |
|---|---|
Containerfile | Multi-stage build definition. Defines the base image, imports shared OCI layers, and runs build scripts. |
Justfile | Local build automation: build container, create VM images, run tests. |
build/ | Numbered shell scripts executed sequentially during the image build. |
build/10-build.sh | Main build script. Copies Brewfiles, ujust commands, and Flatpak configs into the image; installs packages; configures services. |
custom/brew/ | Homebrew Brewfiles (default.Brewfile, development.Brewfile, fonts.Brewfile) baked into the image. |
custom/flatpaks/ | Flatpak preinstall configuration — apps to install on first boot. |
custom/ujust/ | User-facing just commands bundled into the image as part of the ujust system. |
Note: Flatpaks are not embedded in the image. They are declared in
custom/flatpaks/and downloaded on first boot after network setup.
Choosing a Base Image#
The Containerfile defaults to ghcr.io/ublue-os/silverblue-main:latest (GNOME desktop included). Alternative bases are commented in the file :
# Base Image - GNOME included (default)
FROM ghcr.io/ublue-os/silverblue-main:latest@sha256:...
# Fedora base without a desktop environment
# FROM ghcr.io/ublue-os/base-main:latest
# CentOS Stream 10 base
# FROM quay.io/centos-bootc/centos-bootc:stream10
Uncomment the alternative that suits your use case.
Building Locally#
All local build and test operations are driven by the Justfile .
| Command | Description |
|---|---|
just build | Build the container image with Podman. |
just build-qcow2 | Convert the container image into a QCOW2 VM disk image via Bootc Image Builder. |
just build-raw | Convert to a raw disk image. |
just build-iso | Build an ISO installer. CI/CD-only — expects the image to already exist in a registry. Use just rebuild-iso for local builds instead. |
just run-vm-qcow2 | Launch the QCOW2 image in a QEMU-based VM (4 CPUs, 8 GB RAM). Opens a browser-based console automatically. |
Typical local dev loop:
just build # Build the container
just build-qcow2 # Convert to a VM disk image
just run-vm-qcow2 # Boot and test in a VM
rebuild-*variants (e.g.,just rebuild-qcow2) chainjust buildand the conversion step together in one command.
CI/CD with GitHub Actions#
The included .github/workflows/build.yml handles the full automated pipeline :
- Triggers: pull requests, pushes to
main, daily schedule (10:05 UTC), and manual dispatch. - PR builds validate the image without pushing to the registry.
- Merges to
mainbuild and push a:stabletag toghcr.io/YOUR_USERNAME/YOUR_REPO. - Optional: image signing with Cosign and SBOM generation with Syft are already wired into the workflow — enable them by adding the required secrets to your repository.
Automatic Dependency Updates with Renovate#
The included .github/renovate.json5 keeps your image's upstream dependencies current automatically :
- Tracks container image digests in both
ContainerfileandJustfile. - Auto-merges digest-only updates (safe, reproducibility-preserving changes).
- Tracks and updates pinned GitHub Actions versions.
- Requires manual review for rechunk-related updates.
No configuration changes are needed — Renovate runs automatically once your repository is connected to the Renovate app.
Customization Examples#
Build scripts in build/ execute in ascending numerical order during the image build . 10-build.sh runs first. Add your own scripts with higher numbers to extend the system without modifying the base script.
Two example scripts are included to illustrate common patterns:
Adding Third-Party RPM Repositories#
build/20-onepassword.sh.example demonstrates adding external RPM repositories (Google Chrome, 1Password) and cleaning up the repo file after installation so it doesn't persist in the image .
Replacing the Desktop Environment#
build/30-cosmic-desktop.sh.example shows how to remove GNOME and install the COSMIC desktop from System76's COPR instead . It uses the copr_install_isolated() helper from build/copr-helpers.sh , which enables a COPR, installs from it, then disables it — preventing the COPR repo from being baked into the final image.
Enabling an Example Script#
# Remove the .example extension and make it executable
mv build/20-onepassword.sh.example build/20-onepassword.sh
chmod +x build/20-onepassword.sh
The build system picks it up automatically on the next just build.
Script Template#
#!/usr/bin/env bash
set -eoux pipefail
# Your build-time modifications here
dnf5 install -y your-package
Use dnf5 (not dnf or yum) for package management. Scripts run as root with the build context available at /ctx.
Deploying to Your Machine#
Once your image is built and pushed to ghcr.io, switch to it from any running Bluefin (or compatible bootc) system :
sudo bootc switch ghcr.io/YOUR_USERNAME/YOUR_REPO:stable
sudo systemctl reboot
Your home directory, Flatpak applications, and configurations are preserved across the switch. The previous deployment is kept as a rollback target — if something goes wrong, run:
bootc rollback
Tip: Keep your repository public so your machine can pull the image without authentication. For private registries, configure credentials via
podman loginbefore switching.
References#
| Resource | Link |
|---|---|
| finpilot template | github.com/projectbluefin/finpilot |
| Contributor's Guide | docs.projectbluefin.io/contributing |
| bootc documentation | bootc-dev.github.io/bootc |
| Modernizing custom images (blog) | docs.projectbluefin.io/blog/modernizing-custom-images |
| Universal Blue base images | github.com/ublue-os/main |
| projectbluefin/common (shared OCI layer) | github.com/projectbluefin/common |