Chapter 7. Creating bootc images from scratch
With bootc images from scratch, you can have control over the underlying image content, and tailor your system environment to your requirements.
You can use the bootc-base-imagectl
command to create a bootc image from scratch by using an existing bootc base image as a build environment, providing greater control over the content included in the build process. This process takes the user RPMs as input, so you need to rebuild the image if the RPMs change.
The custom base derives from the base container, and does not automatically consume changes to the default base image unless you make them part of a container pipeline.
You can use the bootc-base-imagectl
rechunk
subcommand on any bootc container image.
If you want to perform kernel management, you do not need to create a bootc image from scratch. See Managing kernel arguments in bootc systems.
7.1. Using pinned content to build images
To ensure the base image version contains a set of packages at exactly specific versions, for example, defined by a lockfile, or an rpm-md
or yum repository
, you can use several tools to manage snapshots of rpm-md
or yum repository
repositories.
With the bootc image from scratch
feature, you can configure and override package information in source RPM repositories, while referencing mirrored, pinned, or snapshotted repository content. Consequently, you gain control over package versions and their dependencies.
For example, you might want to gain control over package versions and their dependencies in the following situations:
- You need to use a specific package version because of strict certification and compliance requirements.
- You need to use specific software versions to support critical dependencies.
Prerequisites
- A standard bootc base image.
Procedure
The following example creates a bootc image from scratch with pinned content:
Begin with a standard bootc base image that serves as a "builder" for our custom image. Configure and override source RPM repositories, if necessary. The following step is required when 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 by 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. Create a new, empty image from scratch. Copy the root file system built in the previous step into this image. 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. Set pipefail to display failures within the heredoc and avoid false-positive successful builds. Install necessary packages, run scripts, etc. Remove leftover build artifacts from installing packages in the final built image. Define required labels for this bootc image to be recognized as such. Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd. Run the bootc linter to avoid encountering certain bugs and maintain content quality. Place this command last in your Containerfile.
# Begin with a standard bootc base image that serves as a "builder" for our custom image. FROM registry.redhat.io/rhel10/rhel-bootc:latest # Configure and override source RPM repositories, if necessary. The following 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 # 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 by 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 NetworkManager emacs # Remove leftover build artifacts from installing packages in the final built image. dnf clean all rm /var/{log,cache,lib}/* -rf 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"] # Run the bootc linter to avoid encountering certain bugs and maintain content quality. Place this command last in your Containerfile. RUN bootc container lint
Copy to Clipboard Copied!
Verification
Save and build your image.
podman build -t quay.io/<namespace>/<image>:<tag> . --cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse
$ podman build -t quay.io/<namespace>/<image>:<tag> . --cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse
Copy to Clipboard Copied! Build <_image_> image by using the
Containerfile
in the current directory:podman build -t quay.io/<namespace>/<image>:<tag> .
$ podman build -t quay.io/<namespace>/<image>:<tag> .
Copy to Clipboard Copied!
7.2. Building a base image up from minimal
Previously, you could build just a standard image by using image mode for RHEL. The standard image is roughly a headless server-oriented installation, although you can use it for desktops as well, and includes many opinionated packages for networking, CLI tool, among others.
You now have the option to generate from the standard image a new minimal image which only starts from bootc, kernel, and dnf. This image can then be extended further in a multi-stage build. At the current time the minimal image is not shipped pre-built in the registry.
The base images include the /usr/libexec/bootc-base-imagectl
tool that enables you to generate a custom base image. By using the tool, you can build a root file system that is based on the RPM packages that you selected in the base image.
Prerequisites
- A standard bootc base image.
Procedure
The following example creates a custom minimal base image:
Begin with a standard bootc base image that is reused as a "builder" for the custom image. 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 by 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. Create a new, empty image from scratch. Copy the root file system built in the previous step into this image. 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. Set pipefail to display failures within the heredoc and avoid false-positive successful builds. 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. Remove package caches Clean up all logs and caches Run the bootc linter to perform build-time verification. Keep this as the last command in your build instructions. Close the shell command. Define required labels for this bootc image to be recognized as such. Optional labels that only apply when running this image as a container. These keep the default entry point running under systemd.
# Begin with a standard bootc base image that is reused as a "builder" for the custom image. FROM registry.redhat.io/rhel10/rhel-bootc:latest # 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 by 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. dnf -y install NetworkManager openssh-server # Remove package caches dnf clean all # Clean up all logs and caches rm /var/{log,cache,lib}/* -rf # Run the bootc linter to perform build-time verification. Keep this as the last command in your build instructions. bootc container lint # Close the shell command. 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"]
Copy to Clipboard Copied!
7.3. Building required privileges
Generating a root filesystem from scratch requires the inner build process to use some nested containerization (such as mount namespacing) that are not enabled by default by many container build tools.
Prerequisites
-
In this example using
podman
, thecontainer-tools
meta-package is installed.
Procedure
Generate a new root file system, providing these arguments at a minimum to
podman build
:--cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse
--cap-add=all --security-opt=label=type:container_runtime_t --device /dev/fuse
Copy to Clipboard Copied!
7.4. Generating your bootc images from scratch
Create bootc images from scratch from a custom RHEL bootc default base container image to get a small root content set.
Prerequisites
-
The
container-tools
metapackage is installed.
Procedure
Create a
Containerfile
. The following is an example:The following example reuses the default base image as a "builder" image. Optionally, you can use the commented instructions to configure or override the RPM repositories in /etc/yum.repos.d to, for example, refer to pinned versions RUN rm -rf /etc/yum.repos.d/* COPY mycustom.repo /etc/yum.repos.d Create a new, empty image from scratch. Copy the root file system built in the previous step into this image. You can make arbitrary changes such as copying the systemd units and other tweaks from the baseconfig container image. This example uses the heredocs syntax, to improve and make it easy to add complex instructions, and install critical components Install networking support and SSH which are not in minimal This label is required These labels are optional but useful if you want to keep the default of running under systemd when run as a container image.
# The following example reuses the default base image as a "builder" image. Optionally, you can use the commented instructions to configure or override the RPM repositories in /etc/yum.repos.d to, for example, refer to pinned versions FROM registry.redhat.io/rhel10/rhel-bootc:latest # RUN rm -rf /etc/yum.repos.d/* # COPY mycustom.repo /etc/yum.repos.d 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/ / # You can make arbitrary changes such as copying the systemd units and other tweaks from the baseconfig container image. This example uses the heredocs syntax, to improve and make it easy to add complex instructions, and install critical components RUN <<EORUN set -xeuo pipefail # Install networking support and SSH which are not in minimal dnf -y install NetworkManager openssh-server dnf clean all rm /var/{log,cache,lib}/* -rf bootc container lint EORUN # This label is required LABEL containers.bootc 1 LABEL ostree.bootable 1 # These labels are optional but useful if you want to keep the default of running under systemd when run as a container image. STOPSIGNAL SIGRTMIN+3 CMD ["/sbin/init"]
Copy to Clipboard Copied!
Next steps
-
After creating your
Containerfile
, you get an image with a single tar file large layer. Every change, such as pushing to the registry, pulling for clients, results in copying the single large tar file, and increases the container image size. You can optimize the container image that you created for a smaller version.
7.5. Optimizing container images to a smaller version
You can use the bootc-base-imagectl rechunk
subcommand to optimize an input container image into a new image with the same filesystem tree, but split into content-addressed reproducible layers, with precomputed SELinux labeling.
This provides better network efficiency (for both pushes and pulls) since layers that did not change across an image build can be reused without causing a transfer.
The rechunk
operation works on an image produced by the default mode of creating new images FROM <rhel-bootc>
, but is especially useful in combination with the scratch builds that output only a single large tar
layer. Without rechunk
every change to the input, for example a kernel update, will result in a new layer including the entire contents of the bootc image. This new layer must then be pushed, stored by registries, and pulled by clients.
The bootc-base-imagectl
is shipped as part of the bootc images and is intended to be run inside a container, but requires mapping the host containers-storage into the container to execute.
Prerequisites
- You have a previously-built base image.
Procedure
Run the following command to rechunk your base image.
sudo podman run --rm --privileged -v /var/lib/containers:/var/lib/containers \ registry.redhat.io/rhel10/rhel-bootc:latest \ /usr/libexec/bootc-base-imagectl rechunk \ quay.io/exampleos/rhel-bootc:single \ quay.io/exampleos/rhel-bootc:chunked
$ sudo podman run --rm --privileged -v /var/lib/containers:/var/lib/containers \ registry.redhat.io/rhel10/rhel-bootc:latest \ /usr/libexec/bootc-base-imagectl rechunk \ quay.io/exampleos/rhel-bootc:single \ quay.io/exampleos/rhel-bootc:chunked
Copy to Clipboard Copied!