RED HAT ENTERPRISE LINUX

Image-Based Management

Building, Deploying, and Upgrading with bootc

College-Level Course Module | RHEL System Administration

Learning Objectives

1
Understand image-based deployment concepts

Bootable containers, OSTree, atomic updates, and immutable infrastructure

2
Create bootable container images

Build custom OS images using Containerfiles and podman

3
Deploy image-based systems

Install servers from bootable container images

4
Manage upgrades and rollbacks

Perform atomic system updates with easy rollback capability

Traditional vs Image-Based

Traditional (Package-Based)

Install base OS
Add packages with dnf
Configure individually
Update packages incrementally
Each system potentially unique
Rollback is complex

Image-Based (bootc)

Build complete OS image
Deploy identical copies
Configuration in image
Update entire image atomically
All systems identical
Instant rollback available

Build Image
Test Image
Push to Registry
Deploy to Servers
Key insight: Image-based treats the OS like a container - build once, deploy many, update atomically, roll back instantly.

What is bootc?

bootc (bootable containers) is technology that enables deploying and updating Linux systems using OCI container images. Build your OS as a container, deploy it as a server.

# bootc manages bootable container images on a running system
[root@server ~]# bootc status
Current booted image: registry.redhat.io/rhel9/rhel-bootc:9.3
    Image version: 9.3.0 (2024-01-15)
    Image digest: sha256:a1b2c3d4...
    
Staged for next boot: none
Rollback available: registry.redhat.io/rhel9/rhel-bootc:9.2
    Image version: 9.2.0 (2023-10-01)

# Key components:
# - OCI container images (standard format)
# - OSTree for atomic filesystem updates
# - bootc CLI for system management
# - Standard container tools (podman, buildah)
Benefits: Reproducible deployments, atomic updates, instant rollbacks, familiar container workflow, infrastructure as code.

OSTree Foundation

OSTree is a system for managing bootable filesystem trees. It enables atomic updates by deploying complete filesystem snapshots rather than updating files in place.

Current Boot: rhel-bootc:9.3
Staged: rhel-bootc:9.4 (pending reboot)
Rollback: rhel-bootc:9.2
OSTree Repository (stores all versions)
# OSTree stores filesystem trees efficiently
# - Content-addressed storage (deduplication)
# - Atomic switching between versions
# - Boot loader integration for rollback

# View OSTree deployments
[root@server ~]# rpm-ostree status
State: idle
Deployments:
● ostree-image-signed:docker://registry.redhat.io/rhel9/rhel-bootc:9.3
                   Digest: sha256:a1b2c3d4...
  ostree-image-signed:docker://registry.redhat.io/rhel9/rhel-bootc:9.2
                   Digest: sha256:e5f6g7h8...

RHEL Bootc Images

Red Hat provides base bootc images that serve as foundations for custom images. These include the kernel, systemd, and core OS components needed to boot.

ImageDescriptionUse Case
rhel-bootc Full RHEL bootable base image General purpose servers
rhel-bootc-minimal Minimal bootable image Appliances, single-purpose systems
# Pull RHEL bootc base image
[user@workstation ~]$ podman pull registry.redhat.io/rhel9/rhel-bootc:9.3

# Inspect base image
[user@workstation ~]$ podman inspect registry.redhat.io/rhel9/rhel-bootc:9.3

# Base images include:
# - Linux kernel and firmware
# - systemd and core services
# - SELinux policies
# - Basic system utilities
# - bootc and rpm-ostree tools
Starting point: Always start FROM a bootc base image. Regular container images (like UBI) lack the kernel and boot components needed for a bootable system.

Creating Custom Images

Build custom bootc images using standard Containerfiles. Add your packages, configurations, and customizations on top of the base bootc image.

# Containerfile for custom web server bootc image
FROM registry.redhat.io/rhel9/rhel-bootc:9.3

# Install packages
RUN dnf install -y httpd mod_ssl php php-mysqlnd && \
    dnf clean all && \
    systemctl enable httpd

# Add configuration files
COPY httpd.conf /etc/httpd/conf/httpd.conf
COPY ssl.conf /etc/httpd/conf.d/ssl.conf

# Add application files
COPY webapp/ /var/www/html/

# Set SELinux file contexts (important for bootc)
RUN setfiles -F /etc/selinux/targeted/contexts/files/file_contexts /var/www/html

# Labels for identification
LABEL description="Custom RHEL web server"
LABEL version="1.0"

Building Images

# Build the bootc image
[user@workstation ~]$ podman build -t mywebserver:1.0 -f Containerfile .
STEP 1/8: FROM registry.redhat.io/rhel9/rhel-bootc:9.3
STEP 2/8: RUN dnf install -y httpd mod_ssl php...
...
STEP 8/8: LABEL version="1.0"
Successfully tagged localhost/mywebserver:1.0

# Verify the image
[user@workstation ~]$ podman images
REPOSITORY                               TAG    IMAGE ID      SIZE
localhost/mywebserver                    1.0    a1b2c3d4e5f6  2.1 GB
registry.redhat.io/rhel9/rhel-bootc      9.3    f6e5d4c3b2a1  1.8 GB

# Test the image (limited - cannot fully boot in container)
[user@workstation ~]$ podman run --rm -it mywebserver:1.0 rpm -q httpd
httpd-2.4.57-5.el9.x86_64

# Tag for registry
[user@workstation ~]$ podman tag mywebserver:1.0 registry.example.com/mywebserver:1.0

# Push to registry
[user@workstation ~]$ podman push registry.example.com/mywebserver:1.0

Registry Workflow

Build Image
Test
Push to Registry
Deploy/Update
# Login to registry
[user@workstation ~]$ podman login registry.example.com
Username: admin
Password: 
Login Succeeded!

# Push image to registry
[user@workstation ~]$ podman push registry.example.com/rhel-webserver:1.0

# Systems pull updates from registry
[root@server ~]# bootc upgrade
Pulling manifest for registry.example.com/rhel-webserver:latest
Importing ostree commit...
Staging deployment...
Queued for next boot: registry.example.com/rhel-webserver:1.1

# Registries can be:
# - Red Hat Quay (quay.io or self-hosted)
# - Docker Hub (docker.io)
# - Private registries (Harbor, Artifactory)
# - registry.redhat.io (Red Hat images)

Installing from Images

Deploy bootc images to new systems using bootc-image-builder to create installable media, or use bootc install on existing systems.

# Method 1: Create installation ISO with bootc-image-builder
[user@workstation ~]$ sudo podman run --rm -it --privileged \
    --pull=newer \
    -v ./output:/output \
    registry.redhat.io/rhel9/bootc-image-builder:latest \
    --type iso \
    registry.example.com/mywebserver:1.0

Building ISO image...
Output: /output/bootiso/install.iso

# Boot the ISO on target hardware/VM and install

# Method 2: Convert existing system to bootc
[root@existing ~]# podman run --rm --privileged \
    -v /:/target \
    -v /var/lib/containers:/var/lib/containers \
    registry.example.com/mywebserver:1.0 \
    bootc install to-existing-root /target

# Reboot into new image-based system
[root@existing ~]# systemctl reboot

bootc Status

# Check current system status
[root@server ~]# bootc status
Current booted image: registry.example.com/rhel-webserver:1.0
    Image version: 1.0.0
    Image digest: sha256:a1b2c3d4e5f6...
    Timestamp: 2024-01-15T10:30:00Z
    
Staged for next boot: none

Rollback available: registry.example.com/rhel-webserver:0.9
    Image version: 0.9.0
    Image digest: sha256:e5f6g7h8i9j0...
    Timestamp: 2024-01-01T08:00:00Z

# Show detailed image information
[root@server ~]# bootc status --format=json | jq .

# Verify system is running expected image
[root@server ~]# bootc status --booted
registry.example.com/rhel-webserver:1.0

# Check if updates are available
[root@server ~]# bootc upgrade --check
Update available: registry.example.com/rhel-webserver:1.1

Upgrading Systems

Atomic updates: bootc downloads the new image, stages it, and activates on reboot. The running system is unchanged until reboot - no partial updates.

# Check for and apply updates
[root@server ~]# bootc upgrade
Pulling manifest for registry.example.com/rhel-webserver:latest
Layers already present: 45/52
Pulling new layers: 7
Importing to ostree...
Staging deployment...
Upgrade staged. Reboot to apply.

# Reboot to activate new image
[root@server ~]# systemctl reboot

# After reboot, verify new version
[root@server ~]# bootc status
Current booted image: registry.example.com/rhel-webserver:1.1
Rollback available: registry.example.com/rhel-webserver:1.0

# Switch to specific image (not just latest)
[root@server ~]# bootc switch registry.example.com/rhel-webserver:2.0
Safe updates: The running system continues operating normally while the update is staged. Only reboot activates the change.

Rolling Back

Instant rollback: If an upgrade causes problems, roll back to the previous working image immediately. No complex recovery procedures needed.

# Scenario: Upgrade caused problems
[root@server ~]# bootc status
Current booted image: registry.example.com/rhel-webserver:1.1
Rollback available: registry.example.com/rhel-webserver:1.0

# Rollback to previous image
[root@server ~]# bootc rollback
Rollback staged. Reboot to apply.

# Reboot to activate rollback
[root@server ~]# systemctl reboot

# Alternative: Select previous deployment at boot menu
# GRUB shows multiple deployments - select older one

# After rollback
[root@server ~]# bootc status
Current booted image: registry.example.com/rhel-webserver:1.0
Rollback available: registry.example.com/rhel-webserver:1.1
Recovery time: Rollback is just a reboot - no reinstallation, no package downgrading, no complex recovery. System returns to known-good state instantly.

Managing Local Changes

Image-based systems are intended to be immutable, but some local state must persist: configuration, data, logs. These live outside the image in designated locations.

# Persistent directories (survive upgrades):
/etc       - Configuration (merged with image /etc)
/var       - Variable data, logs, databases
/home      - User home directories

# Immutable directories (from image, read-only concept):
/usr       - System binaries and libraries
/lib       - Libraries (symlink to /usr/lib)
/bin       - Binaries (symlink to /usr/bin)

# Configuration changes in /etc persist
[root@server ~]# vi /etc/myapp/config.yaml
# This change survives upgrades

# To add packages locally (temporary, testing only):
[root@server ~]# rpm-ostree install vim-enhanced
Staging deployment... Reboot to apply.

# Better approach: Add packages to the image
# Edit Containerfile, rebuild, upgrade

Containerfile Best Practices

FROM registry.redhat.io/rhel9/rhel-bootc:9.3

# Combine RUN commands to reduce layers
RUN dnf install -y \
        httpd \
        mod_ssl \
        php \
        php-mysqlnd \
    && dnf clean all \
    && systemctl enable httpd

# Copy configs before application files (better caching)
COPY etc/ /etc/

# Set correct SELinux contexts
RUN setfiles -F /etc/selinux/targeted/contexts/files/file_contexts /etc

# Application files last (most likely to change)
COPY webapp/ /var/www/html/
RUN setfiles -F /etc/selinux/targeted/contexts/files/file_contexts /var/www/html

# Always include metadata
LABEL org.opencontainers.image.version="1.0"
LABEL org.opencontainers.image.description="Production web server"
LABEL org.opencontainers.image.created="2024-01-15"
Caching tip: Put things that change frequently (app code) last in the Containerfile. Earlier layers are cached, making rebuilds faster.

Multi-Stage Builds

# Stage 1: Build application
FROM registry.redhat.io/ubi9/go-toolset:latest AS builder
WORKDIR /app
COPY src/ .
RUN go build -o /app/myservice

# Stage 2: Create bootable image
FROM registry.redhat.io/rhel9/rhel-bootc:9.3

# Install runtime dependencies only (not build tools)
RUN dnf install -y minimal-runtime-deps && dnf clean all

# Copy built application from builder stage
COPY --from=builder /app/myservice /usr/local/bin/

# Add systemd service file
COPY myservice.service /etc/systemd/system/
RUN systemctl enable myservice

LABEL version="1.0"
Smaller images: Build tools are only in the builder stage, not the final image. The bootable image contains only what is needed to run.

CI/CD Integration

Integrate bootc image building into CI/CD pipelines for automated, tested deployments. Build on commit, test automatically, deploy to staging, then production.

# Example CI pipeline (GitLab CI / GitHub Actions concept)
stages:
  - build
  - test
  - push
  - deploy-staging
  - deploy-production

build-image:
  script:
    - podman build -t myserver:${CI_COMMIT_SHA} .
    
test-image:
  script:
    - podman run myserver:${CI_COMMIT_SHA} /tests/run-tests.sh
    
push-image:
  script:
    - podman push registry.example.com/myserver:${CI_COMMIT_SHA}
    - podman push registry.example.com/myserver:latest
    
deploy-staging:
  script:
    - ssh staging "bootc upgrade && systemctl reboot"
    
deploy-production:
  script:
    - ssh prod-servers "bootc upgrade && systemctl reboot"
  when: manual

Troubleshooting

# Check bootc service status
[root@server ~]# systemctl status bootc-fetch-apply-updates.timer

# View bootc logs
[root@server ~]# journalctl -u bootc-fetch-apply-updates

# Check OSTree deployments
[root@server ~]# rpm-ostree status
[root@server ~]# ostree admin status

# Verify image signature (if using signed images)
[root@server ~]# bootc status --format=json | jq '.booted.image.signature'

# Debug upgrade issues
[root@server ~]# bootc upgrade --verbose 2>&1 | tee upgrade.log

# Common issues:
# - Registry authentication: podman login registry.example.com
# - Network connectivity: curl registry.example.com/v2/
# - SELinux denials: ausearch -m avc -ts recent
# - Disk space: df -h /var/lib/containers

# Emergency: Boot previous deployment from GRUB menu

Security Considerations

Security Benefits

  • Immutable base reduces attack surface
  • Atomic updates prevent partial/broken states
  • Easy rollback from compromised images
  • Reproducible builds enable auditing
  • Image signing verifies authenticity
  • Consistent state across all systems

Security Practices

  • Use signed images in production
  • Scan images for vulnerabilities
  • Keep base images updated
  • Minimize installed packages
  • Apply SELinux contexts correctly
  • Secure registry access
# Sign images with cosign
[user@workstation ~]$ cosign sign registry.example.com/myserver:1.0

# Verify signature before deployment
[root@server ~]# cosign verify registry.example.com/myserver:1.0

# Scan for vulnerabilities
[user@workstation ~]$ podman image scan myserver:1.0

Best Practices

Do

  • Start FROM official bootc base images
  • Use specific version tags, not :latest
  • Set SELinux contexts for added files
  • Enable services with systemctl enable
  • Clean dnf cache to reduce size
  • Test images before production deployment
  • Use CI/CD for automated building
  • Keep rollback images available

Do Not

  • Use non-bootc base images (UBI, etc.)
  • Modify /usr on running systems
  • Skip SELinux context configuration
  • Install packages locally with rpm-ostree
  • Deploy untested images to production
  • Ignore image signing in production
  • Let systems drift from the image
  • Delete all rollback deployments
Philosophy: Treat the OS like a container - build it once, test it, deploy identical copies everywhere, update atomically, roll back instantly.

Key Takeaways

1

Image-Based: bootc deploys complete OS images atomically. Build with Containerfiles, deploy with bootc, update and rollback instantly.

2

Building: Start FROM bootc base images. Add packages, configs, enable services. Set SELinux contexts. Push to registry.

3

Deploying: Use bootc-image-builder for install media, or bootc install to-existing-root for conversions.

4

Managing: bootc status/upgrade/rollback. Atomic updates with one reboot. Instant rollback to previous image.

LAB EXERCISES

  • Build a custom bootc image with web server and application
  • Push image to registry and deploy to VM
  • Update the image and perform atomic upgrade
  • Practice rollback after simulated problematic upgrade
  • Examine OSTree deployments with rpm-ostree status
  • Create CI pipeline for automated image building

Image-based management represents the future of Linux system administration