bootc-docs/bootc-base-imagectl.md
robojerk bd4c3e746f
Some checks failed
Test bootc Documentation / test-base-image (push) Failing after 38s
Test bootc Documentation / test-documentation (push) Failing after 24s
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
2025-09-15 14:44:47 -07:00

413 lines
13 KiB
Markdown

# 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 <base image>
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] <source_root> <target>
```
**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] <from_image> <to_image>
```
**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 <<EORUN
set -xeuo pipefail
# Install critical components
dnf -y install linux-firmware NetworkManager cloud-init cowsay
dnf clean all
bootc container lint
EORUN
LABEL containers.bootc 1
ENV container=oci
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]
```
### Example 2: Custom package selection with version locking
```dockerfile
FROM quay.io/fedora/fedora-bootc:rawhide as builder
# Create custom repository configuration
RUN mkdir -p /custom-repos/etc/yum.repos.d
COPY custom-repos.repo /custom-repos/etc/yum.repos.d/
# Build rootfs with specific packages and versions
RUN /usr/libexec/bootc-base-imagectl build-rootfs \
--manifest=minimal \
--install=kernel \
--install=systemd \
--lock=kernel-6.5.0-1.fc39.x86_64 \
--lock=systemd-253-1.fc39.x86_64 \
--cachedir=/var/cache/dnf \
/custom-repos /target-rootfs
FROM scratch
COPY --from=builder /target-rootfs/ /
LABEL containers.bootc 1
ENV container=oci
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]
```
### Example 3: Adding custom overlay content
```dockerfile
FROM quay.io/fedora/fedora-bootc:rawhide as builder
# Prepare custom overlay content
RUN mkdir -p /overlay/usr/local/bin
RUN echo '#!/bin/bash\necho "Hello from custom overlay!"' > /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