1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 15:45:53 +01:00

Dockerfile: Use rpmbuild

We were bit before by just doing a `COPY` of our binaries overtop of
the base image because that doens't remove old files.

Replace the pre-build approach with rpmbuild, and then change to
do an rpm-based upgrade so that we fix that problem.

Note that we still preserve incremental rebuilds by overriding
some of the RPM build process.

Assisted-by: Claude Code (Sonnet 4.5)
Signed-off-by: Colin Walters <walters@verbum.org>
This commit is contained in:
Colin Walters
2025-11-16 11:48:43 -05:00
parent 509a2c3954
commit d68245d319
10 changed files with 258 additions and 103 deletions

View File

@@ -8,6 +8,9 @@
# Toplevel build bits
!Makefile
!Cargo.*
# License and doc files needed for RPM
!LICENSE-*
!README.md
# We do build manpages from markdown
!docs/
# We use the spec file

View File

@@ -10,34 +10,26 @@ ARG base=quay.io/centos-bootc/centos-bootc:stream10
FROM scratch as src
COPY . /src
# And this image only captures contrib/packaging separately
# to ensure we have more precise cache hits.
FROM scratch as packaging
COPY contrib/packaging /
FROM $base as base
# We could inject other content here
# Mark this as a test image (moved from --label build flag to fix layer caching)
LABEL bootc.testimage="1"
# This image installs build deps, pulls in our source code, and installs updated
# bootc binaries in /out. The intention is that the target rootfs is extracted from /out
# back into a final stage (without the build deps etc) below.
FROM base as build
FROM base as buildroot
# Flip this off to disable initramfs code
ARG initramfs=1
# This installs our package dependencies, and we want to cache it independently of the rest.
# Basically we don't want changing a .rs file to blow out the cache of packages. So we only
# copy files necessary
COPY contrib/packaging /tmp/packaging
RUN <<EORUN
set -xeuo pipefail
. /usr/lib/os-release
case $ID in
centos|rhel) dnf config-manager --set-enabled crb;;
fedora) dnf -y install dnf-utils 'dnf5-command(builddep)';;
esac
# Handle version skew, xref https://gitlab.com/redhat/centos-stream/containers/bootc/-/issues/1174
dnf -y distro-sync ostree{,-libs} systemd
# Install base build requirements
dnf -y builddep /tmp/packaging/bootc.spec
# And extra packages
grep -Ev -e '^#' /tmp/packaging/fedora-extra.txt | xargs dnf -y install
rm /tmp/packaging -rf
EORUN
# Version for RPM build (optional, computed from git in Justfile)
ARG pkgversion=
# This installs our buildroot, and we want to cache it independently of the rest.
# Basically we don't want changing a .rs file to blow out the cache of packages.
RUN --mount=type=bind,from=packaging,target=/run/packaging /run/packaging/install-buildroot
# Now copy the rest of the source
COPY --from=src /src /src
WORKDIR /src
@@ -45,15 +37,10 @@ WORKDIR /src
# We aren't using the full recommendations there, just the simple bits.
# First we download all of our Rust dependencies
RUN --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch
# Then on general principle all the stuff from the Makefile runs with no network
RUN --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome --network=none <<EORUN
set -xeuo pipefail
make
make install-all DESTDIR=/out
if test "${initramfs:-}" = 1; then
make install-initramfs-dracut DESTDIR=/out
fi
EORUN
FROM buildroot as build
# Build RPM directly from source, using cached target directory
RUN --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome --network=none RPM_VERSION=${pkgversion} /src/contrib/packaging/build-rpm
# This "build" includes our unit tests
FROM build as units
@@ -70,76 +57,14 @@ RUN --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothom
FROM base
# See the Justfile for possible variants
ARG variant
RUN <<EORUN
set -xeuo pipefail
case "${variant}" in
*-sdboot)
dnf -y install systemd-boot-unsigned
# And uninstall bootupd
rpm -e bootupd
rm /usr/lib/bootupd/updates -rf
dnf clean all
rm -rf /var/cache /var/lib/{dnf,rhsm} /var/log/*
;;
esac
EORUN
RUN --mount=type=bind,from=packaging,target=/run/packaging /run/packaging/configure-variant "${variant}"
# Support overriding the rootfs at build time conveniently
ARG rootfs=
RUN <<EORUN
set -xeuo pipefail
# Do we have an explicit build-time override? Then write it.
if test -n "$rootfs"; then
cat > /usr/lib/bootc/install/80-rootfs-override.toml <<EOF
[install.filesystem.root]
type = "$rootfs"
EOF
else
# Query the default rootfs
base_rootfs=$(bootc install print-configuration | jq -r '.filesystem.root.type // ""')
# No filesystem override set. If we're doing composefs, we need a FS that
# supports fsverity. If btrfs is available we'll pick that, otherwise ext4.
fs=
case "${variant}" in
composefs*)
btrfs=$(grep -qEe '^CONFIG_BTRFS_FS' /usr/lib/modules/*/config && echo btrfs || true)
fs=${btrfs:-ext4}
;;
*)
# No explicit filesystem set and we're not using composefs. Default to xfs
# with the rationale that we're trying to get filesystem coverage across
# all the cases in general.
if test -z "${base_rootfs}"; then
fs=xfs
fi
;;
esac
if test -n "$fs"; then
cat > /usr/lib/bootc/install/80-ext4-composefs.toml <<EOF
[install.filesystem.root]
type = "${fs}"
EOF
fi
fi
# Ensure we've flushed out prior state (i.e. files no longer shipped from the old version);
# and yes, we may need to go to building an RPM in this Dockerfile by default.
(set +x; rpm -ql bootc | while read line; do if test -f $line; then rm -v $line; fi; done)
EORUN
# Create a layer that is our new binaries
COPY --from=build /out/ /
# We have code in the initramfs so we always need to regenerate it
RUN --network=none <<EORUN
set -xeuo pipefail
if test -x /usr/lib/bootc/initramfs-setup; then
kver=$(cd /usr/lib/modules && echo *);
env DRACUT_NO_XATTR=1 dracut -vf /usr/lib/modules/$kver/initramfs.img $kver
fi
# Only in this containerfile, inject a file which signifies
# this comes from this development image. This can be used in
# tests to know we're doing upstream CI.
touch /usr/lib/.bootc-dev-stamp
# And test our own linting
## Workaround for https://github.com/bootc-dev/bootc/issues/1546
rm -rf /root/buildinfo
bootc container lint --fatal-warnings
EORUN
RUN --mount=type=bind,from=packaging,target=/run/packaging /run/packaging/configure-rootfs "${variant}" "${rootfs}"
# Install the RPM built in the build stage
# This replaces the manual file deletion hack and COPY, ensuring proper package management
# Use rpm -Uvh with --oldpackage to allow replacing with dev version
COPY --from=build /out/*.rpm /tmp/
RUN --mount=type=bind,from=packaging,target=/run/packaging --network=none /run/packaging/install-rpm-and-setup /tmp
# Finally, testour own linting
RUN bootc container lint --fatal-warnings

View File

@@ -36,6 +36,30 @@ build:
podman build {{base_buildargs}} -t localhost/bootc-bin {{buildargs}} .
./tests/build-sealed {{variant}} localhost/bootc-bin localhost/bootc
# Build packages (e.g. RPM) using a container buildroot
_packagecontainer:
#!/bin/bash
set -xeuo pipefail
# Compute version from git (matching xtask.rs gitrev logic)
if VERSION=$(git describe --tags --exact-match 2>/dev/null); then
VERSION="${VERSION#v}"
VERSION="${VERSION//-/.}"
else
COMMIT=$(git rev-parse HEAD | cut -c1-10)
COMMIT_TS=$(git show -s --format=%ct)
TIMESTAMP=$(date -u -d @${COMMIT_TS} +%Y%m%d%H%M)
VERSION="${TIMESTAMP}.g${COMMIT}"
fi
echo "Building RPM with version: ${VERSION}"
podman build {{base_buildargs}} {{buildargs}} --build-arg=pkgversion=${VERSION} -t localhost/bootc-pkg --target=build .
# Build a packages (e.g. RPM) into target/
# Any old packages will be removed.
package: _packagecontainer
mkdir -p target
rm -vf target/*.rpm
podman run --rm localhost/bootc-pkg tar -C /out/ -cf - . | tar -C target/ -xvf -
# This container image has additional testing content and utilities
build-integration-test-image: build
cd hack && podman build {{base_buildargs}} -t localhost/bootc-integration-bin -f Containerfile .

View File

@@ -1,4 +1,5 @@
%bcond_without check
%bcond_with tests
%if 0%{?rhel} >= 9 || 0%{?fedora} > 41
%bcond_without ostree_ext
%else
@@ -21,7 +22,8 @@
%endif
Name: bootc
Version: 1.1.5
# Ensure this local build overrides anything else.
Version: 99999.0.0
Release: 1%{?dist}
Summary: Bootable container system
@@ -84,17 +86,49 @@ Recommends: podman
%description -n system-reinstall-bootc
This package provides a utility to simplify reinstalling the current system to a given bootc image.
%if %{with tests}
%package tests
Summary: Integration tests for bootc
Requires: %{name} = %{version}-%{release}
%description tests
This package contains the integration test suite for bootc.
%endif
%global system_reinstall_bootc_install_podman_path %{_prefix}/lib/system-reinstall-bootc/install-podman
%if 0%{?container_build}
# Source is already at /src, no subdirectory
%global _buildsubdir .
%endif
%prep
%if ! 0%{?container_build}
%autosetup -p1 -a1
# Default -v vendor config doesn't support non-crates.io deps (i.e. git)
cp .cargo/vendor-config.toml .
%cargo_prep -N
cat vendor-config.toml >> .cargo/config.toml
rm vendor-config.toml
%else
# Container build: source already at _builddir (/src), nothing to extract
# RPM's %mkbuilddir creates a subdirectory; symlink it back to the source
cd ..
rm -rf %{name}-%{version}-build
ln -s . %{name}-%{version}-build
cd %{name}-%{version}-build
%endif
%build
export SYSTEM_REINSTALL_BOOTC_INSTALL_PODMAN_PATH=%{system_reinstall_bootc_install_podman_path}
%if 0%{?container_build}
# Container build: use cargo directly with cached dependencies
export CARGO_HOME=/var/roothome/.cargo
cargo build -j%{_smp_build_ncpus} --release %{?with_rhsm:--features rhsm} \
--bin=bootc --bin=system-reinstall-bootc \
%{?with_tests:--bin tests-integration}
make manpages
%else
# Build the main bootc binary
%if %new_cargo_macros
%cargo_build %{?with_rhsm:-f rhsm}
@@ -104,7 +138,6 @@ rm vendor-config.toml
# Build the system reinstallation CLI binary
%global cargo_args -p system-reinstall-bootc
export SYSTEM_REINSTALL_BOOTC_INSTALL_PODMAN_PATH=%{system_reinstall_bootc_install_podman_path}
%if %new_cargo_macros
# In cargo-rpm-macros, the cargo_build macro does flag processing,
# so we need to pass '--' to signify that cargo_args is not part
@@ -118,18 +151,24 @@ export SYSTEM_REINSTALL_BOOTC_INSTALL_PODMAN_PATH=%{system_reinstall_bootc_insta
%endif
make manpages
%endif
%if ! 0%{?container_build}
%cargo_vendor_manifest
# https://pagure.io/fedora-rust/rust-packaging/issue/33
sed -i -e '/https:\/\//d' cargo-vendor.txt
%cargo_license_summary
%{cargo_license} > LICENSE.dependencies
%endif
%install
%make_install INSTALL="install -p -c"
%if %{with ostree_ext}
make install-ostree-hooks DESTDIR=%{?buildroot}
%endif
%if %{with tests}
install -D -m 0755 target/release/tests-integration %{buildroot}%{_bindir}/bootc-integration-tests
%endif
mkdir -p %{buildroot}/%{dirname:%{system_reinstall_bootc_install_podman_path}}
cat >%{?buildroot}/%{system_reinstall_bootc_install_podman_path} <<EOF
#!/bin/bash
@@ -153,8 +192,10 @@ fi
%files -f bootcdoclist.txt
%license LICENSE-MIT
%license LICENSE-APACHE
%if ! 0%{?container_build}
%license LICENSE.dependencies
%license cargo-vendor.txt
%endif
%doc README.md
%{_bindir}/bootc
%{_prefix}/lib/bootc/
@@ -169,5 +210,10 @@ fi
%{_bindir}/system-reinstall-bootc
%{system_reinstall_bootc_install_podman_path}
%if %{with tests}
%files tests
%{_bindir}/bootc-integration-tests
%endif
%changelog
%autochangelog

44
contrib/packaging/build-rpm Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Build bootc RPM package from source
set -xeuo pipefail
# Version can be passed via RPM_VERSION env var (set by Dockerfile ARG)
# or defaults to the hardcoded value in the spec file
VERSION="${RPM_VERSION:-}"
# Determine output directory (defaults to /out)
OUTPUT_DIR="${1:-/out}"
SRC_DIR="${2:-/src}"
if [ -n "${VERSION}" ]; then
echo "Building RPM with version: ${VERSION}"
else
echo "Building RPM with version from spec file"
fi
# Create temporary rpmbuild directories
mkdir -p /tmp/rpmbuild/{RPMS,BUILDROOT,SPECS}
# If version is provided, create modified spec file; otherwise use original
if [ -n "${VERSION}" ]; then
sed "s/^Version:.*/Version: ${VERSION}/" \
"${SRC_DIR}/contrib/packaging/bootc.spec" > /tmp/rpmbuild/SPECS/bootc.spec
SPEC_FILE=/tmp/rpmbuild/SPECS/bootc.spec
else
SPEC_FILE="${SRC_DIR}/contrib/packaging/bootc.spec"
fi
# Build RPM
rpmbuild -bb \
--define "_topdir /tmp/rpmbuild" \
--define "_builddir ${SRC_DIR}" \
--define "container_build 1" \
--with tests \
--nocheck \
"${SPEC_FILE}"
# Copy built RPMs to output directory
ARCH=$(uname -m)
mkdir -p "${OUTPUT_DIR}"
cp /tmp/rpmbuild/RPMS/${ARCH}/*.rpm "${OUTPUT_DIR}/"
rm -rf /tmp/rpmbuild

View File

@@ -0,0 +1,46 @@
#!/bin/bash
# Configure rootfs type for bootc installation
set -xeuo pipefail
VARIANT="${1:-}"
ROOTFS="${2:-}"
# Support overriding the rootfs at build time
CONFIG_DIR="/usr/lib/bootc/install"
mkdir -p "${CONFIG_DIR}"
# Do we have an explicit build-time override? Then write it.
if [ -n "$ROOTFS" ]; then
cat > "${CONFIG_DIR}/80-rootfs-override.toml" <<EOF
[install.filesystem.root]
type = "$ROOTFS"
EOF
else
# Query the default rootfs
base_rootfs=$(bootc install print-configuration | jq -r '.filesystem.root.type // ""')
# No filesystem override set. If we're doing composefs, we need a FS that
# supports fsverity. If btrfs is available we'll pick that, otherwise ext4.
fs=
case "${VARIANT}" in
composefs*)
btrfs=$(grep -qEe '^CONFIG_BTRFS_FS' /usr/lib/modules/*/config && echo btrfs || true)
fs=${btrfs:-ext4}
;;
*)
# No explicit filesystem set and we're not using composefs. Default to xfs
# with the rationale that we're trying to get filesystem coverage across
# all the cases in general.
if [ -z "${base_rootfs}" ]; then
fs=xfs
fi
;;
esac
if [ -n "$fs" ]; then
cat > "${CONFIG_DIR}/80-ext4-composefs.toml" <<EOF
[install.filesystem.root]
type = "${fs}"
EOF
fi
fi

View File

@@ -0,0 +1,26 @@
#!/bin/bash
# Configure system for a specific bootc variant
set -xeuo pipefail
VARIANT="${1:-}"
if [ -z "$VARIANT" ]; then
# No variant specified, nothing to do
exit 0
fi
# Handle variant-specific configuration
case "${VARIANT}" in
*-sdboot)
# Install systemd-boot and remove bootupd
dnf -y install systemd-boot-unsigned
# Uninstall bootupd
rpm -e bootupd
rm -rf /usr/lib/bootupd/updates
# Clean up package manager caches
dnf clean all
rm -rf /var/cache /var/lib/{dnf,rhsm} /var/log/*
;;
# Future variants can be added here
# For Debian support, this could check package manager type and use apt instead
esac

View File

@@ -5,3 +5,5 @@ rustfmt
clippy
git-core
jq
# We now always build a package in the container build
rpm-build

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Install buildroot requirements for Fedora derivatives
set -xeuo pipefail
cd $(dirname $0)
. /usr/lib/os-release
case $ID in
centos|rhel) dnf config-manager --set-enabled crb;;
fedora) dnf -y install dnf-utils 'dnf5-command(builddep)';;
esac
# Handle version skew, xref https://gitlab.com/redhat/centos-stream/containers/bootc/-/issues/1174
dnf -y distro-sync ostree{,-libs} systemd
# Install base build requirements
dnf -y builddep bootc.spec
# And extra packages
grep -Ev -e '^#' fedora-extra.txt | xargs dnf -y install

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Install bootc RPM and perform post-installation setup
set -xeuo pipefail
RPM_DIR="${1:-/tmp}"
# Install the RPM package
# Use rpm -Uvh with --oldpackage to allow replacing with dev version
rpm -Uvh --oldpackage "${RPM_DIR}"/*.rpm
rm -f "${RPM_DIR}"/*.rpm
# Regenerate initramfs if we have initramfs-setup
if [ -x /usr/lib/bootc/initramfs-setup ]; then
kver=$(cd /usr/lib/modules && echo *)
env DRACUT_NO_XATTR=1 dracut -vf /usr/lib/modules/$kver/initramfs.img $kver
fi
# Only in this containerfile, inject a file which signifies
# this comes from this development image. This can be used in
# tests to know we're doing upstream CI.
touch /usr/lib/.bootc-dev-stamp
# Workaround for https://github.com/bootc-dev/bootc/issues/1546
rm -rf /root/buildinfo