When debugging a bug in a node component (bootc, CRI-O, kubelet, kernel, etc.), you need to replace the package or binary with a custom build and verify the fix. There are two approaches:
- Runtime update — Build a custom bootc OCI image and use
bootc switchto deploy it on a running node. Faster, no disk rebuild needed. - Disk image rebuild — Rebuild the entire qcow2 disk image. Required when the fix must be present from first boot (kernel, bootc itself, initrd).
When to use which:
| Situation | Approach |
|---|---|
| Patching a userspace component (CRI-O, kubelet, a CLI tool) | Runtime update |
| Patching bootc itself, the kernel, or initrd | Disk image rebuild |
| Need the fix before the first boot completes | Disk image rebuild |
| Testing composefs-specific behavior or fs-verity | Disk image rebuild (composefs variant) |
- Podman with socket running (
systemctl --user start podman.socket) /dev/kvmand/dev/fuseaccessible- bink binary built (see README)
- A running bink cluster, or you will create one
Before making changes, inspect what is currently deployed. SSH into a node:
bink node ssh node1 --cluster-name <name>Then run:
# Check the ostree deployment
ostree admin status
# Show ostree commit details
ostree show --repo=/sysroot/ostree/repo <checksum>
# Check the origin file (ostree backend)
sudo cat /sysroot/ostree/deploy/default/deploy/*.origin
# Check the origin file (composefs backend)
sudo cat /sysroot/state/deploy/*.origin
# Full bootc status
sudo bootc status --json | jq
# Determine the deployment backend (ostree or composefs)
sudo bootc status --json | jq '.status.booted.store'
# Check specific package versions
rpm -qa | grep -E 'bootc|cri-o|kubeadm'The simpler path when the patch does not need to be in the boot image. Build a custom bootc OCI image, push it to bink's local registry, and switch the node to it.
The bootc repository has a Dockerfile with ARG base=quay.io/centos-bootc/centos-bootc:stream10. Override this to use the bink node image as the base, producing a new bootable container image with bootc compiled from any git commit:
git clone https://github.com/bootc-dev/bootc.git
cd bootc
git checkout <commit-or-branch>
podman build \
--build-arg base=ghcr.io/bootc-dev/bink/node:v1.36-fedora-44 \
-t localhost/custom-bootc-node:latest \
.This compiles bootc from source and installs it into an image derived from the bink node image, preserving all the existing packages (kubernetes, CRI-O, etc.).
For simpler cases (installing a different package version), write a minimal Containerfile:
FROM ghcr.io/bootc-dev/bink/node:v1.36-fedora-44
RUN dnf -y install <your-package>Build it:
podman build -t localhost/custom-node:latest -f Containerfile.custom .Bink runs a local OCI registry on port 5000. From the host, push to localhost:5000. Inside the VMs, the registry is reachable at registry.cluster.local:5000.
podman push --tls-verify=false \
localhost/custom-bootc-node:latest \
localhost:5000/custom-bootc-node:latestSSH into the node and switch:
bink node ssh node1 --cluster-name <name>
sudo bootc switch \
registry.cluster.local:5000/custom-bootc-node:latestTo switch by digest:
sudo bootc switch \
registry.cluster.local:5000/custom-bootc-node@sha256:<digest>Reboot to apply:
sudo rebootbink node ssh node1 --cluster-name <name>
ostree admin status
sudo bootc status --json | jq '.status.booted.image'
rpm -q bootc
# Determine the deployment backend
sudo bootc status --json | jq '.status.booted.store'
# Check the origin file (ostree backend)
sudo cat /sysroot/ostree/deploy/default/deploy/*.origin
# Check the origin file (composefs backend)
sudo cat /sysroot/state/deploy/*.originInstead of switching after boot, set the tracked image reference at cluster creation:
bink cluster start \
--cluster-name test \
--target-imgref registry.cluster.local:5000/custom-bootc-node:latestThis rewrites the ostree origin file during cloud-init so the node tracks your custom image from the start. Running bootc upgrade will pull updates from your custom image reference.
Required when the fix must be present from first boot (kernel, bootc, initrd changes).
The node disk image is built in two stages from node-images/fedora/:
Containerfile -> bootc OCI image (bootc-base-imagectl build-rootfs)
Containerfile.disk -> qcow2 disk image (bcvk to-disk)
- Stage 1 (
Containerfile): Builds a bootc OCI image fromquay.io/fedora/fedora-bootc:44. Usesbootc-base-imagectl build-rootfswith--installflags for packages (kubernetes, CRI-O, etc.). Validated withbootc container lint. - Stage 2 (
Containerfile.disk): Converts the bootc OCI image to a qcow2 disk usingbcvk to-disk. The final container image contains only/disk.qcow2and/images.txt.
N.B. If you need to run
podman builddirectly instead of using the Makefile targets, you must pass the required privilege flags:podman build \ --cap-add=all \ --security-opt=label=disable \ --device /dev/fuse \ --build-arg KUBE_MINOR=1.36 \ -t localhost/custom-node:latest \ -f Containerfile \ .
Modify node-images/fedora/Containerfile to add a COPR repo before the build-rootfs command:
RUN dnf -y install 'dnf5-command(copr)' && \
dnf -y copr enable <owner>/<project> fedora-44-x86_64The COPR architecture string must match the base image (e.g., fedora-44-x86_64 for Fedora 44).
Build and test:
cd node-images/fedora
make build-bootc-image BOOTC_IMAGE=localhost/custom-node:latest
make build-disk-image \
BOOTC_IMAGE=localhost/custom-node:latest \
NODE_IMAGE=localhost/custom-node:disk
cd ../..
bink cluster start \
--node-image localhost/custom-node:disk \
--cluster-name custom-testCopy the RPM into the build context and install it into the target rootfs after build-rootfs completes:
COPY my-package.rpm /tmp/my-package.rpm
# After the build-rootfs RUN instruction:
RUN dnf --installroot=/target-rootfs install -y /tmp/my-package.rpmThe --installroot=/target-rootfs flag is required because build-rootfs assembles the filesystem at /target-rootfs, not in the builder's own root.
Build and test with the same make commands as Scenario A.
Use a multi-stage build to compile the binary and copy it into the node image:
FROM fedora:44 AS custom-build
RUN dnf -y install git golang make
COPY my-source/ /src
WORKDIR /src
RUN make build
FROM ghcr.io/bootc-dev/bink/node:v1.36-fedora-44
COPY --from=custom-build /src/my-binary /usr/bin/my-binaryBuild and test with the same make commands as Scenario A.
Composefs is an alternative to the default ostree deployment backend. It uses EROFS for filesystem metadata and overlayfs for content-addressed file mounts, with optional fs-verity integrity checking.
Build the disk image with the composefs backend:
cd node-images/fedora
make build-bootc-image BOOTC_IMAGE=localhost/custom-node:latest
make build-disk-image-composefs \
BOOTC_IMAGE=localhost/custom-node:latest \
NODE_IMAGE_COMPOSEFS=localhost/custom-node:disk-composefs
cd ../..
bink cluster start \
--node-image localhost/custom-node:disk-composefs \
--cluster-name composefs-testmake build-disk-image-composefs wraps make build-disk-image with BCVK_EXTRA_ARGS="--composefs-backend". Override the image name via NODE_IMAGE_COMPOSEFS (not NODE_IMAGE).
Verify the node is using the composefs backend:
bink node ssh node1 --cluster-name composefs-test
# Should return "composefs"
sudo bootc status --json | jq '.status.booted.store'
# Composefs-specific metadata (verity status, boot digest)
sudo bootc status --json | jq '.status.booted.composefs'
# Origin file (composefs path)
sudo cat /sysroot/state/deploy/*.originRun the composefs-specific integration tests:
make test-integration-composefsVariables in node-images/fedora/Makefile:
| Variable | Default | Description |
|---|---|---|
KUBE_MINOR |
1.36 |
Kubernetes minor version |
FEDORA_VERSION |
44 |
Fedora base version |
DISK_SIZE |
10G |
VM disk size |
BUILD_MEMORY |
4G |
Memory for bcvk build |
BOOTC_IMAGE |
ghcr.io/bootc-dev/bink/node:v1.36-fedora-44 |
Bootc OCI image name |
NODE_IMAGE |
ghcr.io/bootc-dev/bink/node:v1.36-fedora-44-disk |
Disk image name |
NODE_IMAGE_COMPOSEFS |
ghcr.io/bootc-dev/bink/node:v1.36-fedora-44-disk-composefs |
Composefs disk image name |
BCVK_EXTRA_ARGS |
(none) | Extra flags for bcvk to-disk |