A template for building custom bootc operating system images based on the lessons from Universal Blue and Bluefin. It is designed to be used manually, but is optimized to be bootstraped by GitHub Copilot. After set up you'll have your own custom Linux.
This template uses the multi-stage build architecture from @projectbluefin/distroless, combining resources from multiple OCI containers for modularity and maintainability. See the Architecture section below for details.
Unlike previous templates, you are not modifying Bluefin and making changes.: You are assembling your own Bluefin in the same exact way that Bluefin, Aurora, and Bluefin LTS are built. This is way more flexible and better for everyone since the image-agnostic and desktop things we love about Bluefin lives in @projectbluefin/common.
Instead, you create your own OS repository based on this template, allowing full customization while leveraging Bluefin's robust build system and shared components.
Be the one who moves, not the one who is moved.
Here are the changes from [Base Image Name]. This image is based on [Bluefin/Bazzite/Aurora/etc] and includes these customizations:
- System packages: tmux, micro, mosh - [brief explanation of why]
- CLI Tools (Homebrew): neovim, helix - [brief explanation]
- GUI Apps (Flatpak): Spotify, Thunderbird - [brief explanation]
- List anything removed from base image
- Any systemd services enabled/disabled
- Desktop environment changes
- Other notable modifications
Last updated: [date]
Replace the placeholders above with your actual customizations whenever you add or remove packages, apps, or configuration. This section is what tells users how your image differs from the base.
This template works best with phased prompts that let Copilot bootstrap your image in three stages.
Use this prompt first to get your fork building:
Bootstrap a new custom OS from @projectbluefin/finpilot. Name it after this repository. Read `.agents/skills/finpilot-onboarding.md` first, then:
1. Rename `finpilot` in the 7 required files
2. Enable GitHub Actions and set RENOVATE_TOKEN (repo + workflow scopes)
3. Configure branch protection for `main` with `validate` as a required status check
4. Enable auto-merge
5. Trigger the first green build on `main`
6. Add the "What Makes this Raptor Different" section to README.md (with placeholders)
Once the first build is green, use this prompt to add packages:
Read `.agents/skills/finpilot-packages.md` and `.agents/skills/finpilot-custom.md`, then:
1. Add one system package to the image in `build/10-build.sh`
2. Add one CLI tool to `custom/brew/default.Brewfile`
3. Add one GUI app to `custom/flatpaks/default.preinstall`
4. Add shortcuts in `custom/ujust/custom-apps.just` to install them
5. Update the README "What Makes this Raptor Different" section with the new entries
6. Run `just build && just build-qcow2 && just run-vm-qcow2` to verify locally
7. Open a PR and merge once `validate` passes
When you are ready for production, use this prompt to harden the setup:
Read `.agents/skills/finpilot-maintain.md` and `.agents/skills/finpilot-ci.md`, then:
1. Enable keyless image signing by uncommenting the step in `.github/workflows/build-image.yml`
2. Verify the cosign command works: cosign verify --certificate-identity-regexp="https://github.com/USER/REPO/.github/workflows/" --certificate-oidc-issuer="https://token.actions.githubusercontent.com" ghcr.io/USER/REPO:stable
3. Review the maintenance schedule in `finpilot-maintain.md`
- Automated builds via GitHub Actions on every commit
- Self-hosted Renovate for automated dependency updates
- Automatic cleanup of old images (90+ days) to keep it tidy
- Pull request workflow - test changes before merging to main
- PRs build and validate before merge
mainbranch builds:stableimages
- Validates your files on pull requests so you never break a build:
- Brewfile, Justfile, ShellCheck, Renovate config, and it'll even check to make sure the flatpak you add exists on FlatHub
- Production Grade Features
- Container signing with keyless OIDC
- See checklist below to enable these as they take some manual configuration
- Pre-configured Brewfiles for easy package installation and customization
- Includes curated collections: development tools, fonts, CLI utilities. Go nuts.
- Users install packages at runtime with
brew bundle, aliased to premadeujust commands - See custom/brew/README.md for details
- Ship your favorite flatpaks
- Automatically installed on first boot after user setup
- See custom/flatpaks/README.md for details
- User-friendly command shortcuts via
ujust - Pre-configured examples for app installation and system maintenance for you to customize
- See custom/ujust/README.md for details
- Modular numbered scripts (10-, 20-, 30-) run in order
- Example scripts included for third-party repositories and desktop replacement
- Helper functions for safe COPR usage
- See build/README.md for details
Click "Use this template" to create a new repository from this template.
Important: Change finpilot to your repository name in these 7 files:
Containerfile(# Name:comment andARG IMAGE_NAME):# Name: your-repo-nameJustfile(export IMAGE_NAME := env("IMAGE_NAME", ...)):your-repo-nameREADME.md(title):# your-repo-nameartifacthub-repo.yml(repositoryID):repositoryID: your-repo-namecustom/ujust/README.md(bootc switch example):localhost/your-repo-name:stable.github/workflows/clean.yml(packages):packages: your-repo-nameiso/iso.toml(bootc switch URL):ghcr.io/YOUR_USERNAME/your-repo-name:stable
- Go to the "Actions" tab in your repository
- Click "I understand my workflows, go ahead and enable them"
Your first build will start automatically!
Note: Image signing is disabled by default. Your images will build successfully without any signing keys. Once you're ready for production, see "Optional: Enable Image Signing" below.
Renovate automatically updates dependencies and GitHub Actions (including workflow files). This template uses a self-hosted Renovate runner via projectbluefin/actions.
One-time setup:
- Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click Generate new token (classic)
- Set a note like
renovate-finpilot - Select scopes:
repo(full control) andworkflow(update workflows) - Click Generate token and copy the value
- Go to your repository → Settings → Secrets and variables → Actions
- Add a new secret:
RENOVATE_TOKEN(paste the token value) - Enable Settings → General → Pull Requests → Allow auto-merge so Renovate can merge low-risk updates after checks pass
- Configure branch protection for
main(required for automerge to work):- Go to Settings → Branches → Add rule
- Set Branch name pattern to
main - Enable "Require a pull request before merging"
- Enable "Require status checks to pass before merging"
- Add
validateas a required status check - Enable "Require branches to be up to date before merging" (recommended)
Renovate will run every 6 hours and on config changes. It pins GitHub Actions to SHAs and updates tracked image digests automatically.
Choose your base image in Containerfile (the FROM line):
FROM quay.io/fedora-ostree-desktops/silverblue:44@sha256:...Finpilot layers on top of Fedora Silverblue, not Bluefin. Bluefin's desktop
configuration is provided by @projectbluefin/common earlier in the build.
Add your packages in build/10-build.sh:
dnf5 install -y package-nameCustomize your apps:
- Add Brewfiles in
custom/brew/(guide) - Add Flatpaks in
custom/flatpaks/(guide) - Add ujust commands in
custom/ujust/(guide)
All changes should be made via pull requests:
- Open a pull request on GitHub with the change you want.
- The PR will automatically trigger:
- Build validation
- Brewfile, Flatpak, Justfile, and shellcheck validation
- Test image build
- Once checks pass, merge the PR
- Merging triggers publishes a
:stableimage
Switch to your image:
sudo bootc switch ghcr.io/your-username/your-repo-name:stable
sudo systemctl rebootImage signing is disabled by default to let you start building immediately. However, signing is strongly recommended for production use.
- Verify image authenticity and integrity
- Prevent tampering and supply chain attacks
- Required for some enterprise/security-focused deployments
- Industry best practice for production images
This template uses keyless OIDC signing via Cosign and GitHub Actions. No manual key generation, cosign.key, or cosign.pub files are required.
- Edit
.github/workflows/build-image.yml - Find the "OPTIONAL: Sign and attest" section
- Uncomment the
Sign and publishstep (remove the#from the beginning of each line in that section) - Commit and push
Your next build will produce a signed image. The signature is created using GitHub's OIDC token via Fulcio.
Users can verify your images with:
cosign verify \
--certificate-identity-regexp="https://github.com/your-username/your-repo-name/.github/workflows/" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/your-username/your-repo-name:stableReady to take your custom OS to production? Enable these features for enhanced security, reliability, and performance:
-
Enable Image Signing (Recommended)
- Provides cryptographic verification of your images
- Prevents tampering and ensures authenticity
- Uses keyless OIDC signing via GitHub Actions — no keys or secrets required
- See "Optional: Enable Image Signing" section above for setup instructions
- Status: Disabled by default to allow immediate testing
-
Enable Image Rechunking (Recommended)
- Optimizes bootc image layers for better update performance
- Reduces update sizes by 5-10x when combined with package cadence data
- Improves download resumability with evenly sized layers
- To enable:
- Edit
.github/workflows/build-image.yml - Find the "OPTIONAL: Rechunking" section
- Uncomment the
bootc-build/chunkastep
- Edit
- For optimal results, also add
bootc-build/apply-pkg-intervalsand apkg-cadence.ymlworkflow - Status: Not enabled by default (optional optimization)
After building your bootc image, add a rechunk step before pushing to the registry. The template ships with a commented bootc-build/chunka step in .github/workflows/build-image.yml:
- name: Rechunk image
if: github.event_name != 'pull_request'
id: rechunk-image
uses: projectbluefin/actions/bootc-build/chunka@6231015b336556d2ff0adc1d1e59514bf19dcb42 # v1
with:
source-image: localhost/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}
max-layers: 128This uses chunkah to reorganize OCI layers without rpm-ostree. Renovate will keep the action updated once it is uncommented.
Parameters:
max-layers: Maximum number of layers for the rechunked image (128 is a typical bootc default)source-image: Local image reference to rechunk
For optimal OTA deltas, also add bootc-build/apply-pkg-intervals before the rechunk step and create a .github/workflows/pkg-cadence.yml workflow that calls projectbluefin/actions/.github/workflows/reusable-pkg-cadence.yml@v1. This groups packages by update cadence (weekly, monthly, quarterly, yearly) so a typical update only downloads layers that actually changed. Without it, chunkah still works but uses default layer grouping.
References:
Your workflow will:
- Sign all images using keyless OIDC signing
- Provide cryptographic proof of authenticity via SLSA build provenance attestation
Users can verify your images with:
cosign verify \
--certificate-identity-regexp="https://github.com/your-username/your-repo-name/.github/workflows/" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/your-username/your-repo-name:stable- Homebrew/Brewfiles - Runtime package management
- Flatpak Preinstall - GUI application setup
- ujust Commands - User convenience commands
- Build Scripts - Build-time customization
This template follows the multi-stage build architecture from @projectbluefin/distroless, as documented in the Bluefin Contributing Guide.
Stage 1: Context (ctx) - Combines resources from multiple sources:
- Local build scripts (
/build) - Local custom files (
/custom) - @projectbluefin/common - Desktop configuration shared with Aurora (includes branding/artwork content)
- @ublue-os/brew - Homebrew integration
Stage 2: Base Image - Default options:
quay.io/fedora-ostree-desktops/silverblue:44(Fedora-based GNOME desktop, default)quay.io/centos-bootc/centos-bootc:stream10(CentOS-based alternative)
- Modularity: Compose your image from reusable OCI containers
- Maintainability: Update shared components independently
- Reproducibility: Renovate automatically updates OCI tags to SHA digests
- Consistency: Share components across Bluefin, Aurora, and custom images
The template imports files from these OCI containers at build time:
COPY --from=ghcr.io/projectbluefin/common:latest /system_files /oci/common
COPY --from=ghcr.io/ublue-os/brew:latest /system_files /oci/brewYour build scripts can access these files at:
/ctx/oci/common/- Shared desktop configuration (branding/artwork content lives insidecommon)/ctx/oci/brew/- Homebrew integration files
Note: Renovate automatically updates :latest tags to SHA digests for reproducible builds.
Test your changes before pushing:
just build # Build container image
just build-qcow2 # Build VM disk image
just run-vm-qcow2 # Test in browser-based VMThis template provides security features for production use:
- Optional image signing with keyless OIDC cosign for cryptographic verification
- Automated security updates via Renovate
- Build provenance tracking
These security features are disabled by default to allow immediate testing. When you're ready for production, see the "Love Your Image? Let's Go to Production" section above to enable them.
Flatpaks are installed on first boot via flatpak-preinstall.service, not during bootc switch. Ensure:
- Internet is available on first boot
flatpak-preinstall.servicecompletes (systemctl status flatpak-preinstall.service)- Wait until the service finishes before checking for flatpaks
The adw-gtk3-dark runtime is not available on Flathub. These warnings are cosmetic and do not prevent other flatpaks from installing. To suppress, remove adw-gtk3-dark from your flatpak list in custom/flatpaks/.
Homebrew is installed at build time into the image. If you don't see brew, verify your Containerfile includes the Brew integration. Check custom/brew/README.md for setup instructions.