Preface
On the Universal Blue Discourse, I saw this very simple guide to creating your own bootc image that I wanted to share: Locally built, automatically updating custom bootc image - General - Universal Blue
Notably, this does not rely on something like Github Actions to build your image. So it’s more private, not reliant on the whims of Microsoft, and likely faster.
Warning
Warning: this is unofficial and you should only do this if you are aware of the risks.
Before doing anything, it’s a good idea to pin your existing version.
# if this fails, try 1 instead
sudo ostree admin pin 0
My version of the guide
To start, create a containerfile. Here’s an example of one based on mine.
# This is located at /etc/system-image - DO NOT INCLUDE THIS LINE
FROM quay.io/fedora/fedora-silverblue:44
RUN --mount=type=bind,source=scripts,target=/tmp/scripts \
--mount=type=cache,destination=/var/cache/libdnf5 \
--mount=type=cache,destination=/var/lib/dnf5 \
--mount=type=tmpfs,destination=/var/log \
<<EOF
# bash safety options
set -euox pipefail
# this containerfile mounts ./scripts to /tmp/scripts so that you don't
# have to put everything in this containerfile
bash /tmp/scripts/myscript.sh
# remove packages
dnf5 remove -y gnome-software
# enable repos
dnf5 copr enable -y scottames/ghostty
# install packages
dnf5 install -y fastfetch ghostty zsh zsh-autosuggestions zsh-syntax-highlighting
EOF
RUN bootc container lint
Now, we create a systemd service to build that containerfile into an image.
# This is located at /etc/containers/systemd/system.build - DO NOT INCLUDE THIS LINE
[Build]
Arch=amd64
ImageTag=localhost/system-image
Pull=newer
SetWorkingDirectory=/etc/system-image
PodmanArgs=--squash
[Service]
ExecStartPost=/usr/bin/bootc switch --quiet --transport=containers-storage localhost/system-image:latest
ExecStartPost=/usr/bin/bootc update --quiet
ExecStartPost=/usr/bin/podman image prune --force --filter=label=containers.bootc=1
Nice=0
We will also want a timer so that systemd will automatically build new images.
# This is located at /etc/systemd/system/system-build.timer - DO NOT INCLUDE THIS LINE
[Unit]
Description=Daily automatic builds for custom bootc image
[Timer]
OnCalendar=daily
OnBootSec=15min
[Install]
WantedBy=timers.target
Now, run the following command so systemd finds these files.
sudo systemctl daemon-reload
You can build and switch to the image by running
sudo systemctl start build system-build.service
You can watch the status of the build by running
sudo systemctl status system-build.service
And enable the timer for it by running
sudo systemctl enable --now system-build.timer
For my convenience, I store these 3 files in the same directory in my home. I then use this script to copy them to their proper places (works on a new installs and updates existing installs).
#!/bin/bash
# bash safety options
# -e exits on failure
# -u exits on unknown variables
# -o pipefail exits on failed pipe
set -euox pipefail
# Parse arguments
RUN_NOW=false
for arg in "$@"; do
case $arg in
--now)
RUN_NOW=true
shift
;;
esac
done
# regardless pwd when running, cd into this script's directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# move Containerfile to its destination
sudo mkdir -p /etc/system-image
sudo cp "$SCRIPT_DIR/Containerfile" /etc/system-image/
sudo cp -r "$SCRIPT_DIR/scripts" /etc/system-image/
# move system.build to its destination
sudo mkdir -p /etc/containers/systemd
sudo cp "$SCRIPT_DIR/system.build" /etc/containers/systemd/
# move system-build.timer to its destination
sudo mkdir -p /etc/systemd/system
sudo cp "$SCRIPT_DIR/system-build.timer" /etc/systemd/system/
# run daemon-reload so systemd creates system-build.service
sudo systemctl daemon-reload
# enable and start the timer schedule
sudo systemctl enable --now system-build.timer
# conditionally start the service and tail logs
if [ "$RUN_NOW" = true ]; then
# trigger the build service to run immediately
sudo systemctl start system-build.service --no-block
# display the logs (control + C to exit)
journalctl -u system-build.service -f
fi
The script has two modes:
./updatewhich just copies the updated files to their system locations and makes systemd aware of the changes. This waits until the next scheduled run of the build timer../update --nowwhich copies the files and builds the image immediately.