Skip to main content
Redhat Developers  Logo
  • Products

    Platforms

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat AI
      Red Hat AI
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • View All Red Hat Products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat Developer Hub
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat OpenShift Local
    • Red Hat Developer Sandbox

      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Secure Development & Architectures

      • Security
      • Secure coding
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • Product Documentation
    • API Catalog
    • Legacy Documentation
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Customize RHEL CoreOS at scale: On-cluster image mode in OpenShift

September 23, 2025
Mark Russell
Related topics:
ContainersDevOpsLinuxKubernetes
Related products:
Red Hat Enterprise LinuxRed Hat OpenShift

Share:

    If you've ever needed to add a custom driver, deploy a critical hotfix, or install monitoring agents on your OpenShift nodes, you know the challenge: How do you customize Red Hat Enterprise Linux CoreOS without compromising its scalable design? Until now, this meant choosing between the reliability of stock Red Hat Enterprise Linux CoreOS or the flexibility of package mode Red Hat Enterprise Linux (RHEL) workers.

    Meet image mode on Red Hat OpenShift, bringing you Red Hat Enterprise Linux CoreOS the way that it should be.

    What is "on-cluster" image mode on OpenShift?

    Image mode is a cloud-native approach to operating system management that treats your OS exactly like a container image. You define your OS configuration as code, build it as a unified image, deploy it consistently across your entire fleet, and update it directly from an OCI container registry. It's now fully supported in OpenShift 4.19. And we didn't forget our Extended Update Support (EUS) users; it's also fully supported in OpenShift Container Platform 4.18.21 or later.

    With image mode now in OpenShift, you can:

    • Add non-containerized agents, drivers, or monitoring tools to Red Hat Enterprise Linux CoreOS.
    • Deploy hotfixes without waiting for the next OpenShift release.
    • Maintain customizations automatically through cluster upgrades.
    • Keep the single-click upgrade experience you love about OpenShift.

    Conveniently, the entire build process happens on your cluster with no external CI/CD required.

    The journey to image mode

    We started this journey in 2023 when we first shipped RHEL CoreOS as a container image. We introduced the "off-cluster layering" approach that lets platform engineers build custom Red Hat Enterprise Linux CoreOS images in their own environments (see Figure 1). That's fantastic for emergency scenarios, but it requires external build infrastructure and some manual integration.

    Off-cluster workflow diagram where the user provides a build environment and builds and pushes the image to a container registry. Then the administrator tells the Machine Config Operator to roll out the new image using a machine config.
    Figure 1: Off-cluster Red Hat Enterprise Linux CoreOS image builds.

    Taking inspiration from how OpenShift uses Kubernetes to manage Kubernetes, we asked: Why not use the same cloud-native tools built into the cluster for the OS itself? On-cluster layering is that natural evolution. Now your customizations are built, tested, and deployed using the same declarative, GitOps-friendly workflows you use for applications.

    What about bootc?

    If you're familiar with image mode for RHEL and the bootc project, you might be wondering how this relates. While OpenShift will transition to bootc sometime in 2026, this is designed to be a seamless, non-event for users. RHEL CoreOS currently uses RPM-OSTree native containers, a technology that served as the inspiration for bootc. This provides the same user experience and container image capabilities.

    OpenShift has specific requirements for Red Hat Enterprise Linux CoreOS that we're still bridging in bootc. Rather than wait, we're delivering image mode capabilities now with a smooth migration to bootc planned. What's important is that everything you learn today—the Containerfile syntax, the workflows, the patterns—will work identically throughout the process. We have your back.

    How it works: Architecture overview

    The new on-cluster system introduces three key components:

    • MachineOSConfig: A Kubernetes custom resource that defines registry details and customizations for its targeted MachineConfigPool. One MachineOSConfig per customized MachineConfigPool.
    • Build Controller: This runs inside a pod called MachineOS Builder where it watches for customization requests and orchestrates builds.
    • Build Job: This creates the actual image customization and manages failures and retries.

    Here's the workflow:

    MachineOSConfig Manifest → Build Controller → Job → Custom Image → Node Rollout

    When you apply a customization:

    1. You define and apply your changes using standard Containerfile directives in a MachineOSConfig YAML manifest.
    2. The build controller detects the change and dispatches a build job.
    3. The builder pod pulls the correct Red Hat Enterprise Linux CoreOS base image for your OpenShift version.
    4. Your customizations are layered on top using Buildah.
    5. On success, the new image rolls out to the specified machine pools just like a normal OpenShift upgrade.
    6. On failure, the upgrade halts so you can investigate. No broken nodes!

    Once you are happy with your configuration, there's nothing else to do but respond to potential future build failures. Your custom content is now built and rolled out on every OpenShift update. Figure 2 illustrates the on-cluster image mode workflow.

    A fully managed on-cluster workflow where your Dockerfile customizations are saved in a manifest and applied to etcd. The build controller then creates a build job and stores the resulting image in a container registry where the Machine Config Operator automatically picks it up and rolls it out to the targeted machine config pools.
    Figure 2: On-cluster image mode.

    Key concept: The /var limitation

    Before diving into examples, there's one critical concept to understand: Red Hat Enterprise Linux CoreOS will not merge changes to /var after the initial cluster install. For the purposes of customization, only consider the /usr directory tree. This is by design. We don't want to overwrite the local machine state. This means that software installing to /opt needs special handling, as /opt is symlink to /var/opt in OSTree and bootc systems. Thankfully, you can often work around this limitation in your build process.

    One common approach is to let the install proceed and simply move the results under /usr. You will need to create /var/opt and use /usr/lib/tmpfiles.d functionality to create symlinks so that the binaries work as expected. Take a look at the Containerfile below.

    FROM quay.io/fedora/fedora-coreos:stable
    # This example is for illustrative purposes only. FCOS is not officially supported by Red Hat or HPE.
    RUN mkdir /var/opt && \
        rpm -Uvh https://downloads.linux.hpe.com/repo/stk/rhel/8/x86_64/current/hp-scripting-tools-11.60-7.rhel8.x86_64.rpm && \
        mv /var/opt/hp/ /usr/lib/hp && \
        echo 'L /opt/hp - - - - ../../usr/lib/hp' > /usr/lib/tmpfiles.d/hp.conf && \
       bootc container lint
       # NOTE: on 4.18 use `ostree container commit` in place of
       # `bootc container lint`

    For the curious, bootc container lint (and ostree container commit in OpenShift 4.18) verifies certain conditions in /var and cleans up /tmp.

    Getting started

    Before you begin, ensure you have the necessary prerequisites in place. You will need a registry endpoint and push secret for that registry. Those are the only strict requirements.

    Enabling the OpenShift internal registry (optional)

    For development and test environments, you might use the OpenShift internal registry. It's disabled by default, but it's just fine for our needs here and the easiest way to get started in your lab without external dependencies. For production, consider using your enterprise registry for better manageability and compliance. Let's get started.

    This Red Hat Knowledgebase article will walk you through enabling the OpenShift registry with local persistent storage. 

    As noted in the article, we'll need to allow the registry to enable default route creation:

    oc patch configs.imageregistry.operator.openshift.io/cluster --patch '{"spec":{"defaultRoute":true}}' --type=merge

    Check to see if the default route is live (this can take a few minutes to return without error):

    oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}'

    Since we're using the internal registry, we'll create an image stream to store our images:

    oc create imagestream os-images -n openshift-machine-config-operator

    Next, let's grab the pushSpec:

    oc get imagestream/os-images  -n openshift-machine-config-operator -o=jsonpath='{.status.dockerImageRepository}'

    This should return: 

    image-registry.openshift-image-registry.svc:5000/openshift-machine-config-operator/os-images

    Lastly, we'll need the name of the push secret to write new images:

    oc get secrets -o name -n openshift-machine-config-operator -o=jsonpath='{.items[?(@.metadata.annotations.openshift\.io\/internal-registry-auth-token\.service-account=="builder")].metadata.name}'

    In my case, this returns builder-dockercfg-rfl85. Yours will have a different alphanumeric ending after dockercfg-. We'll save this output for our configuration later.

    Your first customization

    Let's start with a simple example. Your included RHEL subscription is automatically wired into the build pod, so adding packages from the RHEL BaseOS and AppStream content sets is simple. Say you want to add the tcpdump utility to your nodes. Note that in the case of single node OpenShift, you must target the master pool. First we'll create a MachineOSConfig manifest (we'll call it tcpdump.yaml, but the file name doesn't matter) with the following contents:

    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineOSConfig
    metadata:
      name: master-image-config
    spec:
      machineConfigPool:
        name: master
      containerFile:
      - content: |
          FROM configs AS final
          RUN dnf install -y tcpdump && \
              bootc container lint
              # NOTE: on 4.18 use `ostree container commit` in place of
              # `bootc container lint`
      # Here is where you can select an image builder type. For now, we only
      # support the "Job" type that we maintain ourselves. Future
      # integrations can / will include other build system integrations.
      imageBuilder:
        imageBuilderType: Job
      # Here is where you specify the name of the push secret you use to push
      # your newly-built image to.
      renderedImagePushSecret:
        name: builder-dockercfg-rfl85
      # Here is where you specify the image registry to push your newly-built
      # images to.
      renderedImagePushSpec: image-registry.openshift-image-registry.svc:5000/openshift-machine-config-operator/os-images:latest

    Note

    Important: Replace the renderedImagePushSecret here (builder-dockercfg-rfl85) with your registry secret and change the renderedImagePushSpec if not using the internal registry.

    The inputs are:

    • name: The name of the MachineOSConfig (e.g., master-image-config).
    • machineConfigPool: The target machine config pool (e.g., worker, master).
    • content: The Containerfile content that defines your customizations (here: RUN dnf install -y tcpdump).
    • imageBuilderType: Today, that is always Job (we're using Kubernetes Jobs to take advantage of automatic retries).
    • renderedImagePushSpec: An OCI registry endpoint to push the image.
    • renderedImagePushSecret: The write access secret for your registry.

    Apply your new MachineOSConfig:

    oc apply -f tcpdump.yaml

    The Machine Config Operator (MCO) will start the build controller pod (the name will start with machine-os-builder), which will quickly determine that a build is necessary. To track the build in progress, you should look for a pod in the MCO namespace starting with build, followed by the name of your MachineOSConfig. Let's watch the MCO namespace and wait for the build to appear:

    oc get pods -n openshift-machine-config-operator -w
    NAME                                                                    READY   STATUS              RESTARTS      AGE
    build-master-image-config-41a842710c6d989d331f06a52d6d5e4wzzl7         0/2     ContainerCreating   0             5s
    kube-rbac-proxy-crio-hpe-dl365gen10plus-01.khw.eng.rdu2.dc.redhat.com   1/1     Running             3 (19h ago)   19h
    machine-config-controller-7d666c9fd6-hvjnc                              2/2     Running             0             18h
    machine-config-daemon-fffq7                                             2/2     Running             0             18h
    machine-config-operator-797f59db97-jldms                                2/2     Running             0             19h
    machine-config-server-mjlzl                                             1/1     Running             0             18h
    machine-os-builder-55768c88cf-jqvcj                                     1/1     Running             0             16s
    build-master-image-config-41a842710c6d989d331f06a52d6d5e4wzzl7         2/2     Running             0             44s

    The last pod is our build. We can now stream the build logs in progress:

    oc logs -f -n openshift-machine-config-operator build-master-image-config-41a842710c6d989d331f06a52d6d5e4wzzl7

    Assuming it completes without error, your newly customized image will start rolling out automatically.

    The build environment and third-party software

    Since the build job runs in a pod on the cluster and not a local build context, you have several options for bringing in third-party or your own content:

    • Inline content: Create config files and run scripts directly using heredoc notation.
    • Direct downloads: Use curl to fetch files from any accessible URL.
    • Package repositories: Configure accessible YUM/DNF repositories and keep your add-ons up to date on each build.
    • Multi-stage builds: Pull content from other container images.

    In this Containerfile example using HPE's AMSD tool, we'll use an inline approach to create repository definitions and work around a package that expects to write a text file into /opt. 

    # On-cluster image mode is a multi-stage build where "configs" is the stock image plus
    # machineconfig content and "final" is the final image
    FROM configs AS final
    RUN rpm --import https://downloads.linux.hpe.com/repo/spp/GPG-KEY-spp
    RUN rpm --import https://downloads.linux.hpe.com/repo/spp/GPG-KEY2-spp
    # Create repo file for Gen 11 repo
    RUN cat <<EOF > /etc/yum.repos.d/hpe-sdr.repo
    [spp]
    name=Service Pack for ProLiant
    baseurl=https://downloads.linux.hpe.com/repo/spp-gen11/redhat/9/x86_64/current
    enabled=1
    gpgcheck=1
    gpgkey=https://downloads.linux.hpe.com/repo/spp/GPG-KEY-spp,https://downloads.linux.hpe.com/repo/spp/GPG-KEY2-spp
    EOF
    # Create directory to satisfy amsd & install packages
    RUN mkdir /var/opt && \
        dnf install -y amsd
    # Move the /opt content to the system partition
    RUN mkdir /usr/share/amsd && mv /var/opt/amsd/amsd.license /usr/share/amsd/amsd.license && \
        bootc container lint
        # NOTE: on 4.18 use `ostree container commit` in place of
        # `bootc container lint`

    Power tip: Use yq to insert your Containerfile content

    Use yq to save yourself some YAML indentation pain when working with Containerfiles like the one above. Although not included in RHEL, yq can be found in EPEL and on various platforms such as macOS (via Homebrew), Windows, and Fedora.

    Begin by creating your MachineOSConfig. Instead of embedding Containerfile directives directly, we'll use a placeholder in the content section, like so:

    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineOSConfig
    metadata:
      name: master-image-config
    spec:
      machineConfigPool:
        name: master
      containerFile:
      - content: |
          <containerfile contents>
      # Here is where you can select an image builder type. For now, we only
      # support the "Job" type that we maintain ourselves. Future
      # integrations can / will include other build system integrations.
      imageBuilder:
        imageBuilderType: Job
      # Here is where you specify the name of the push secret you use to push
      # your newly-built image to.
      renderedImagePushSecret:
        name: builder-dockercfg-rfl85
      # Here is where you specify the image registry to push your newly-built
      # images to.
      renderedImagePushSpec: image-registry.openshift-image-registry.svc:5000/openshift-machine-config-operator/os-images:latest

    We'll save this to machineosconfig.yaml. Keeping our Containerfile in the same directory, we can insert it with the correct indentation by running:

    export containerfileContents="$(cat Containerfile)"
    yq -i e '.spec.containerFile[0].content = strenv(containerfileContents)' ./machineosconfig.yaml

    Important considerations

    As you begin customizing your nodes, keep the following in mind.

    Reboot policies

    Currently, machine pools using on-cluster image mode are incompatible with node disruption policies. This means custom and system reboot suppression rules won't work. Nodes will reboot after any and all image and configuration updates. This limitation is targeted for resolution in OpenShift 4.20.

    Changing or deleting your configuration

    Typically, you would use oc replace to update the in-cluster object from your locally edited manifest. If your customization was only temporarily needed, simply delete the MachineOSConfig for the particular pool and the nodes will be rebooted into the default image.

    Power tip 2: Testing your builds with an empty pool

    Create an empty machine config pool for testing:

    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfigPool
    metadata:
      name: test-pool
    spec:
      machineConfigSelector:
        matchLabels:
          machineconfiguration.openshift.io/role: test-pool
      nodeSelector:
        matchLabels:
          node-role.kubernetes.io/test-pool: ""
      paused: false

    After creating the new pool, create a MachineOSConfig that targets it. The build will run even without nodes, letting you validate your content and settings before applying to active pools.

    Roadmap: What's next

    Today's on-cluster image build pod is just the beginning; the full vision of image mode extends beyond simple customization. We want to bring enterprise-grade CI/CD practices to the operating system layer. Imagine treating your OS images exactly like any other critical application: automated testing that validates your customizations, security scanners that check for vulnerabilities before deployment, approval gates that ensure compliance, and cryptographic signatures that guarantee authenticity. That's where we're heading.

    Pipeline integration: Your DevSecOps, your OS

    To that end, we plan to integrate OpenShift Pipelines (Tekton) to extend on-cluster image mode. This allows you to build images with complete customization. Our early demos demonstrate that Red Hat Enterprise Linux CoreOS images can be processed through the same pipelines as your applications, enabling the implementation of custom test suites and security controls. See Figure 3.

    On-cluster workflow similar to the prior one, but here the build job is run through OpenShift Pipelines (Tekton).
    Figure 3: The future of cloud-native OS management.

    This builder will be available as an option alongside the default builder, giving you the flexibility to start simply and evolve toward full pipeline automation when you're ready.

    What we're working on now

    • Node disruption policy compatibility (targeted for OpenShift 4.20)
    • Image pruning
    • Expanded builder options and customization
    • Smooth transition to bootc as the underlying technology
    • Install-time support
    • Hosted control planes integration

    Ready to transform your OpenShift infrastructure?

    Image mode for OpenShift is a fundamental change in how to approach node customization at scale. 

    We're excited to see what you build with this capability. Whether you're adding specialized drivers, deploying critical agents, or creating purpose-built node configurations, we want to hear about your use cases, challenges, and successes. Your real-world experiences will help shape the future of image mode on OpenShift. Please reach out!

    Look out for a follow-up post where we'll take a deep dive into managing and troubleshooting the build process, everything you need to know to debug failed builds and get the most out of image mode.

    Welcome to the future of OpenShift node management.

    Related Posts

    • How to customize Fedora CoreOS for dedicated workloads with OSTree

    • How to run containerized workloads securely and at scale with Fedora CoreOS

    • bootc: Getting started with bootable containers

    • Use bootc logically bound images to deploy a Kafka cluster

    • How to use system-reinstall-bootc to install a bootc image

    • Alternatives to creating bootc images from scratch

    Recent Posts

    • Customize RHEL CoreOS at scale: On-cluster image mode in OpenShift

    • How to set up KServe autoscaling for vLLM with KEDA

    • How I used Cursor AI to migrate a Bash test suite to Python

    • Install Python 3.13 on Red Hat Enterprise Linux from EPEL

    • Zero trust automation on AWS with Ansible and Terraform

    What’s up next?

    Automated builds and deployments—known as CI/CD—of container-based applications can reduce mistakes, improve productivity, and promote more thorough testing. This learning path introduces OpenShift Pipelines for automated builds and deployment.

    Start the activity
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2025 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue