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)
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.
Image
Description
Use 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 imageFROMregistry.redhat.io/rhel9/rhel-bootc:9.3# Install packagesRUNdnf install -y httpd mod_ssl php php-mysqlnd && \
dnf clean all && \
systemctl enable httpd# Add configuration filesCOPYhttpd.conf /etc/httpd/conf/httpd.confCOPYssl.conf /etc/httpd/conf.d/ssl.conf# Add application filesCOPYwebapp/ /var/www/html/# Set SELinux file contexts (important for bootc)RUNsetfiles -F /etc/selinux/targeted/contexts/files/file_contexts /var/www/html# Labels for identificationLABELdescription="Custom RHEL web server"LABELversion="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
FROMregistry.redhat.io/rhel9/rhel-bootc:9.3# Combine RUN commands to reduce layersRUNdnf install -y \
httpd \
mod_ssl \
php \
php-mysqlnd \
&& dnf clean all \
&& systemctl enable httpd# Copy configs before application files (better caching)COPYetc/ /etc/# Set correct SELinux contextsRUNsetfiles -F /etc/selinux/targeted/contexts/files/file_contexts /etc# Application files last (most likely to change)COPYwebapp/ /var/www/html/RUNsetfiles -F /etc/selinux/targeted/contexts/files/file_contexts /var/www/html# Always include metadataLABELorg.opencontainers.image.version="1.0"LABELorg.opencontainers.image.description="Production web server"LABELorg.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 applicationFROMregistry.redhat.io/ubi9/go-toolset:latest AS builderWORKDIR/appCOPYsrc/ .RUNgo build -o /app/myservice# Stage 2: Create bootable imageFROMregistry.redhat.io/rhel9/rhel-bootc:9.3# Install runtime dependencies only (not build tools)RUNdnf install -y minimal-runtime-deps && dnf clean all# Copy built application from builder stageCOPY--from=builder /app/myservice /usr/local/bin/# Add systemd service fileCOPYmyservice.service /etc/systemd/system/RUNsystemctl enable myserviceLABELversion="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.