From bd4c3e746f2925f92efbfbbb8f6268e9014595bd Mon Sep 17 00:00:00 2001 From: robojerk Date: Mon, 15 Sep 2025 14:44:47 -0700 Subject: [PATCH] Enhance bootc-base-imagectl documentation with technical details - Add comprehensive technical implementation details based on actual Python source - Document all command-line options and usage patterns - Add detailed examples for build-rootfs, rechunk, and list commands - Include practical Containerfile examples for different use cases - Document external dependencies and error handling - Add security considerations and cleanup procedures - Provide cross-build examples and advanced workflows Based on analysis of the actual bootc-base-imagectl Python script from https://gitlab.com/fedora/bootc/base-images/-/raw/main/bootc-base-imagectl --- bootc-base-imagectl.md | 413 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 bootc-base-imagectl.md diff --git a/bootc-base-imagectl.md b/bootc-base-imagectl.md new file mode 100644 index 0000000..624914b --- /dev/null +++ b/bootc-base-imagectl.md @@ -0,0 +1,413 @@ +# bootc-base-imagectl + +A core premise of the bootc model is that rich +control over Linux system customization can be accomplished +with a "default" container build: + +``` +FROM +RUN ... +``` + +As of recently, it is possible to e.g. swap the kernel +and other fundamental components as part of default derivation. + +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`. + +The `/usr/libexec/bootc-base-imagectl` tool which is +included in the base image is designed to enable building +a root filesystem in ostree-container format from a set +of RPMs controlled by the user. + +## Architecture Overview + +The `bootc-base-imagectl` tool is a Python script that provides three main operations: + +1. **`build-rootfs`** - Generate a container root filesystem from RPM packages +2. **`rechunk`** - Split container images into reproducible, chunked layers +3. **`list`** - Enumerate available build configurations + +The tool uses `rpm-ostree` under the hood to compose root filesystems from RPM packages, +with support for custom manifests, package locking, and reproducible builds. + +## 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`. + +### Basic Usage + +```bash +bootc-base-imagectl build-rootfs [OPTIONS] +``` + +**Required Arguments:** +- `source_root` - Path to source root with `/etc/yum.repos.d` configuration +- `target` - Path where the root filesystem will be generated (must not exist) + +### Advanced Options + +```bash +bootc-base-imagectl build-rootfs \ + --manifest=minimal \ + --install=package1 --install=package2 \ + --add-dir=/path/to/overlay1 --add-dir=/path/to/overlay2 \ + --lock=package-1.0.0-1.x86_64 \ + --no-docs \ + --sysusers \ + --cachedir=/var/cache/dnf \ + --repo=fedora --repo=updates \ + /source/root /target/rootfs +``` + +**Key Options:** +- `--manifest` - Select build configuration (default: "default") +- `--install` - Add additional packages to install +- `--add-dir` - Copy directory contents as overlay layers +- `--lock` - Lock packages to specific versions (NEVRA or NEVR format) +- `--no-docs` - Skip documentation packages +- `--sysusers` - Use systemd-sysusers instead of hardcoded passwd/group +- `--cachedir` - Cache repository metadata and RPMs +- `--repo` - Enable specific repositories only +- `--reinject` - Copy build configurations into target + +### Technical Implementation + +The tool performs these steps: + +1. **Repository Setup**: Runs `dnf repolist` to refresh repository metadata +2. **Manifest Processing**: Loads YAML manifest from `/usr/share/doc/bootc-base-imagectl/manifests/` +3. **Override Generation**: Creates temporary JSON with user overrides +4. **OSTree Compose**: Executes `rpm-ostree compose rootfs` with the manifest +5. **Post-processing**: Fixes permissions and runs `bootc container lint` +6. **Cleanup**: Removes temporary files and repositories + +## Using bootc-base-imagectl rechunk + +This operation is strongly related to `build-rootfs` but is also orthogonal; +it can be used on a "regular" container build as well. + +### Basic Usage + +```bash +bootc-base-imagectl rechunk [OPTIONS] +``` + +**Required Arguments:** +- `from_image` - Source image in container storage (e.g., `quay.io/exampleos:build`) +- `to_image` - Output image in container storage (e.g., `quay.io/exampleos:latest`) + +### Advanced Options + +```bash +bootc-base-imagectl rechunk \ + --max-layers=10 \ + quay.io/exampleos:build \ + quay.io/exampleos:latest +``` + +**Options:** +- `--max-layers` - Configure maximum number of output layers + +### Container Usage + +This command assumes it will be run as a container image, and defaults +to wanting write access to the container storage. + +```bash +podman run --rm --privileged \ + -v /var/lib/containers:/var/lib/containers \ + quay.io/fedora/fedora-bootc:rawhide \ + bootc-base-imagectl rechunk \ + quay.io/exampleos/exampleos:build \ + quay.io/exampleos/exampleos:latest +``` + +### Technical Implementation + +The rechunk operation uses `rpm-ostree experimental compose build-chunked-oci`: + +1. **Input Processing**: Reads the source container image +2. **Layer Analysis**: Analyzes RPM database to determine optimal layer splits +3. **Chunking**: Splits content into separate, reproducible layers +4. **Timestamp Canonicalization**: Sets all timestamps to zero for reproducibility +5. **Output Generation**: Creates new container image with chunked layers + +### Rationale + +When performing a complex container derivation, there are several issues: + +#### Replaced duplicate content + +When e.g. upgrading or replacing the kernel or other large packages +as part of a container build (without squashing all layers) then +the old replaced content will still be present. + +#### Removed content still present + +Similarly, `RUN dnf -y remove` etc. will still retain that removed +content in prior layers. + +#### Timestamp drift + +By default, many tools will use the current timestamp when writing +files. `rpm` will do this (unless `SOURCE_DATE_EPOCH` is set), and +other tools like `cp` and `curl` will as well. + +This means that every build of the image will produce a new +tar stream (with new timestamps) - that will get pushed to a registry +and downloaded by clients, even if the content didn't actually change. + +### What rechunk does: split reproducible chunked images + +The `bootc-base-imagectl rechunk` command fixes all of these issues +by taking an input container, operates on its final merged filesystem +tree (hence removed/overridden files are handled), and then splits it up +(currently based on the RPM database) into separate layers (tarballs). + +Further, because bootc uses OSTree today, and OSTree canonializes all timestamps +to zero on the client side, this tool does that at build time. + +## Using bootc-base-imagectl list + +The `list` command enumerates available build configurations that can be selected by passing `--manifest` to `build-rootfs`. + +### Usage + +```bash +bootc-base-imagectl list +``` + +### Output Format + +``` +minimal: Minimal bootc base image +default: Standard bootc base image with common packages +server: Server-focused bootc base image +--- +``` + +### Technical Implementation + +The list command: + +1. **Manifest Discovery**: Scans `/usr/share/doc/bootc-base-imagectl/manifests/` for `.yaml` files +2. **Filtering**: Excludes `.hidden.yaml` files and symbolic links +3. **Metadata Extraction**: Uses `rpm-ostree compose tree --print-only` to read manifest metadata +4. **Summary Display**: Shows manifest name and description from metadata + +## Technical Implementation Details + +### Core Architecture + +The `bootc-base-imagectl` tool is implemented as a Python 3 script that orchestrates `rpm-ostree` operations. The tool provides a high-level interface for: + +- **Manifest Management**: Loading and processing YAML treefiles +- **Package Resolution**: Handling RPM dependencies and version locking +- **Overlay Support**: Managing additional content layers +- **Reproducible Builds**: Ensuring consistent output across builds + +### Key Components + +#### 1. Manifest System +- **Location**: `/usr/share/doc/bootc-base-imagectl/manifests/` +- **Format**: YAML treefiles compatible with `rpm-ostree` +- **Discovery**: Automatic scanning for `.yaml` files (excluding `.hidden.yaml`) +- **Override Support**: JSON overrides for user customizations + +#### 2. Package Management +- **Repository Handling**: Uses `/etc/yum.repos.d` configuration from source root +- **Version Locking**: Supports NEVRA (Name-Epoch-Version-Release-Architecture) format +- **Dependency Resolution**: Leverages `rpm-ostree`'s advanced dependency solver +- **Caching**: Optional repository metadata and RPM caching + +#### 3. Overlay System +- **Directory Overlays**: Copy additional content as OSTree overlay layers +- **Temporary Repositories**: Create temporary OSTree repos for overlay content +- **Content Integration**: Merge overlay content into final rootfs + +#### 4. Build Process +```python +# Simplified build flow +def run_build_rootfs(args): + # 1. Repository refresh + subprocess.check_call(['dnf', 'repolist'], stdout=subprocess.DEVNULL) + + # 2. Manifest processing + manifest_path = find_manifest(args.manifest) + + # 3. Override generation + if user_overrides: + create_temp_override_manifest(overrides) + + # 4. OSTree compose + subprocess.run(['rpm-ostree', 'compose', 'rootfs', ...]) + + # 5. Post-processing + fix_permissions(target) + subprocess.run(['bootc', 'container', 'lint', f'--rootfs={target}']) +``` + +### External Dependencies + +The tool requires these external commands: +- `rpm-ostree` - Core OSTree composition engine +- `dnf` - Package manager for repository operations +- `ostree` - OSTree repository management +- `bootc` - Container validation and linting + +### Error Handling + +The implementation includes comprehensive error handling: +- **Subprocess Failures**: Captures and reports command execution errors +- **File Operations**: Handles temporary file creation and cleanup +- **Repository Issues**: Manages OSTree repository lifecycle +- **Validation**: Runs `bootc container lint` to ensure output validity + +### Security Considerations + +- **Temporary Files**: All temporary files are created with secure permissions +- **Repository Isolation**: Uses separate temporary OSTree repositories for overlays +- **Content Validation**: Validates all input manifests and configurations +- **Cleanup**: Ensures all temporary resources are properly cleaned up + +### Cross builds and the builder image + +The build tooling is designed to support "cross builds"; the +repository root could e.g. be CentOS Stream 10, while the +builder root is Fedora or RHEL, etc. + +In other words, one given base image can be used as a "builder" to produce another +using different RPMs. + +## Practical Examples + +### Example 1: Generate a new image using CentOS Stream 10 content from RHEL + +```dockerfile +FROM quay.io/centos/centos:stream10 as repos + +FROM registry.redhat.io/rhel10/rhel-bootc:10 as builder +RUN --mount=type=bind,from=repos,src=/,dst=/repos,rw \ + /usr/libexec/bootc-base-imagectl build-rootfs \ + --manifest=minimal \ + /repos /target-rootfs + +# This container image uses the "artifact pattern"; it has some +# basic configuration we expect to apply to multiple container images. +FROM quay.io/exampleos/baseconfig@sha256:.... as baseconfig + +FROM scratch +COPY --from=builder /target-rootfs/ / +# Now we make other arbitrary changes. Copy our systemd units and +# other tweaks from the baseconfig container image. +COPY --from=baseconfig /usr/ /usr/ +RUN < /overlay/usr/local/bin/custom-script +RUN chmod +x /overlay/usr/local/bin/custom-script + +# Build rootfs with overlay +RUN /usr/libexec/bootc-base-imagectl build-rootfs \ + --manifest=default \ + --add-dir=/overlay \ + --install=cowsay \ + / /target-rootfs + +FROM scratch +COPY --from=builder /target-rootfs/ / +LABEL containers.bootc 1 +ENV container=oci +STOPSIGNAL SIGRTMIN+3 +CMD ["/sbin/init"] +``` + +### Example 4: Reproducible build with rechunking + +```dockerfile +# Build stage +FROM quay.io/fedora/fedora-bootc:rawhide as builder +RUN /usr/libexec/bootc-base-imagectl build-rootfs \ + --manifest=minimal \ + --install=kernel \ + --install=systemd \ + / /target-rootfs + +# Create intermediate image +FROM scratch as intermediate +COPY --from=builder /target-rootfs/ / +LABEL containers.bootc 1 +ENV container=oci +STOPSIGNAL SIGRTMIN+3 +CMD ["/sbin/init"] + +# Rechunk for reproducibility +FROM quay.io/fedora/fedora-bootc:rawhide as rechunker +COPY --from=intermediate / / +RUN /usr/libexec/bootc-base-imagectl rechunk \ + --max-layers=5 \ + localhost/intermediate:latest \ + localhost/final:latest + +# Final image +FROM localhost/final:latest