Custom Fedora/CentOS bootc base container images
A core premise of the bootc model is that rich control over Linux system customization can be accomplished with a "default" container build:
FROM <base image>
RUN ...
As of recently, it is possible to e.g. swap the kernel and other fundamental components as part of default derivation.
This page is documenting a change which is on track to land in the CentOS images; but is not yet enabled in the Fedora base images. |
Goals
Exact content version control
However, some use cases want even more control - for example, as an organization deploying a bootc system, I may want to ensure the base image version carries a set of packages at exactly specific versions (perhaps defined by a lockfile, or an rpm-md repository). There are many tools which manage snapshots of yum (rpm-md) repositories.
There are currently issues where it won’t quite work to e.g.
dnf -y upgrade selinux-policy-targeted
.
Building up from minimal
Today there is just one default image produced by the project, which is now known as "standard". It is roughly a headless server-oriented installation (although it can be used for desktops as well), and comes with a lot of opinionated packages for networking, CLI tooling, etc.
This project also has a "minimal" content set definition which is not currently shipped as a distinct container, but can be built from the standard image.
Using /usr/libexec/bootc-base-imagectl
The /usr/libexec/bootc-base-imagectl
tool which is
included in the base image is designed to enable building
a root filesystem from an exact set of provided input RPMs.
Understanding the base image content
Most, but not all content from the base image comes from RPMs.
There is some additional non-RPM content, as well as postprocessing
that operates on the filesystem root. At the current time the
implementation of the base image build uses rpm-ostree
,
but this is considered an implementation detail subject to change.
Using bootc-base-imagectl build-rootfs
The core operation is bootc-base-imagectl build-rootfs
.
This command takes just one required argument:
-
A path to the target root filesystem which will be generated as a directory. The target should not already exist (but its parent must exist).
Additionally, --manifest
can be provided to choose the input set of packages
and configuration. The two default images are:
-
standard
: The default image. -
minimal
: A quite small root content set, effectivelybootc
,systemd
,kernel
,dnf
plus their hard dependencies, as well as a small set of non-RPM content tweaks to e.g. enable systemd persistent journaling.
For more information on the available content sets, run bootc-base-imagectl list
.
The set of packages is currently not officially supported to configure directly.
The general intention is that you can start from either image, and then add, change
or remove content from it in a secondary build phase. Especially building up from
the minimal
image should cover many use cases that want to control the content
set to a high degree.
A key goal is that this avoids "forking" by default, ensuring that by default when we change the base image (adding new packages usually), custom builds will inherit that change by default.
A "source root" can also be provided to enable "cross builds". More on this below.
Build privileges required
We’re generating a new root filesystem, not modifying the existing container. This is most easily done by using container features (mount namespacing in particular) that is not enabled by default in many container build environments today.
In summary, you must provide at least these arguments to e.g. podman build
:
--cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse
It is a goal to reduce these required capabilities; see this tracker issue.
Note however ultimately the goal here is to deploy this container image as a base operating system on a physical or virtualized environment, where inherently more trust is required regardless.
Example: Generating a custom base image with exact version control
# Begin with a standard bootc base image that serves as a "builder" for our custom image.
FROM quay.io/centos-bootc/centos-bootc:stream10 as builder
# Configure and override source RPM repositories, if necessary. This step is required when referencing specific content views or target mirrored/snapshotted/pinned versions of content.
RUN rm -vf /etc/yum.repos.d
COPY mypinnedcontent.repo /etc/yum.repos.d
# Add additional repositories to apply customizations to the image. However, referencing a custom manifest in this step is not currently supported without forking the code.
# Build the root file system using the specified repositories and non-RPM content from the "builder" base image.
# If no repositories are defined, the default build will be used. You can modify the scope of packages in the base image by changing the manifest between the "standard" and "minimal" sets.
RUN /usr/libexec/bootc-base-imagectl build-rootfs --manifest=standard /target-rootfs
# Create a new, empty image from scratch.
FROM scratch
# Copy the root file system built in the previous step into this image.
COPY --from=builder /target-rootfs/ /
# Apply customizations to the image. This syntax uses "heredocs" https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/ to pass multi-line arguments in a more readable format.
RUN <<EORUN
# Set pipefail to display failures within the heredoc and avoid false-positive successful builds.
set -xeuo pipefail
# Install necessary packages, run scripts, etc.
dnf -y install emacs
# Remove leftover build artifacts from installing packages in the final built image.
dnf clean all
rm /var/{log,cache,lib}/* -rf
# Run the bootc linter to avoid encountering certain bugs and maintain content quality. Place this as the final command in your last run invoaction.
RUN bootc container lint
EORUN
# Define required labels for this bootc image to be recognized as such.
LABEL containers.bootc 1
LABEL ostree.bootable 1
# Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd.
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]
Example: Generating a custom minimal base image
# Begin with a standard bootc base image that is reused as a "builder" for the custom image.
FROM quay.io/centos-bootc/centos-bootc:stream10 as builder
# Configure and override source RPM repositories, if necessary. This step is not required when building up from minimal unless referencing specific content views or target mirrored/snapshotted/pinned versions of content.
# Add additional repositories to apply customizations to the image. However, referencing a custom manifest in this step is not currently supported without forking the code.
# Build the root file system using the specified repositories and non-RPM content from the "builder" base image.
# If no repositories are defined, the default build will be used. You can modify the scope of packages in the base image by changing the manifest between the "standard" and "minimal" sets.
RUN /usr/libexec/bootc-base-imagectl build-rootfs --manifest=minimal /target-rootfs
# Create a new, empty image from scratch.
FROM scratch
# Copy the root file system built in the previous step into this image.
COPY --from=builder /target-rootfs/ /
# Apply customizations to the image. This syntax uses "heredocs" https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/ to pass multi-line arguments in a more readable format.
RUN <<EORUN
# Set pipefail to display failures within the heredoc and avoid false-positive successful builds.
set -xeuo pipefail
# Install required packages for our custom bootc image.
# Note that using a minimal manifest means we need to add critical components specific to our use case and environment.
# For example, install networking and SSH - not every use case will want SSH, so it's not included in minimal
dnf -y install NetworkManager openssh-server
# Remove leftover build artifacts from installing packages from the final built image.
dnf clean all
rm /var/{log,cache,lib}/* -rf
# Run the bootc linter to avoid encountering certain bugs and maintain content quality. Place this as the final command in your last run invoaction.
RUN bootc container lint
EORUN
# Define required labels for this bootc image to be recognized as such.
LABEL containers.bootc 1
LABEL ostree.bootable 1
# Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd.
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]
Example: "Cross" builds
The simplest use case is building a new container image from the builder
container, where the OS major version of the builder container is the
same as the target system (e.g. using fedora-bootc:41
to generate a system
with Fedora 41 RPMs).
However, the tooling does support "cross" (operating system major) builds with the pattern of a separate "repos" container. This can be used to aid bootstrapping scenarios.
# This container defines the RPM configuration (releasever, yum repos, etc)
FROM quay.io/fedora/fedora-bootc:41 as repos
# Now the builder container can be a different OS major version.
FROM quay.io/centos-bootc/centos-bootc:stream10 as builder
RUN --mount=type=bind,from=repos,target=/repos,rw /usr/libexec/bootc-base-imagectl build-rootfs --manifest=minimal /repos /target-rootfs
FROM scratch
COPY --from=builder /target-rootfs/ /
# ... insert here arbitrary build steps, as above
LABEL containers.bootc 1
Optimizing container images
The output of the above will be an image with a single large layer (tarball), which means every change will result in copying (pushing to the registry, pulling for clients) the single large tarball.
There is support in rpm-ostree to take a large image like this and "rechunk" it. This process will optimize the ordering and grouping of installed packages into many layers in the final image, which provides better network efficiency since a number of layers may be reused without incurring a transfer.
The bootc-base-imagectl
tool provides a rechunk
subcommand to
interact with rpm-ostree
and perform this rechunking. It is
recommended to use the bootc-base-imagectl rechunk
command and not
rpm-ostree
directly, since the implementation details may change in
the future.
Example
Currently, bootc-base-imagectl
is shipped as part of the
centos-stream bootc images. It is convenient to use these
pre-existing images in order to rechunk custom base images. This can
be done by mapping the host containers-storage into the container and
running bootc-base-imagectl
within to do the rechunking operation.
A previously-built base image named
quay.io/exampleos/fedora-bootc:single
can be optmized with:
# podman run --rm --privileged -v /var/lib/containers:/var/lib/containers \
quay.io/centos-bootc/centos-bootc:stream10 \
/usr/libexec/bootc-base-imagectl rechunk \
quay.io/exampleos/fedora-bootc:single \
quay.io/exampleos/fedora-bootc:chunked
Want to help? Learn how to contribute to Fedora Docs ›