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

How to deploy an image mode update in offline and air-gapped environments

August 13, 2025
Armando Ortiz
Related topics:
Linux
Related products:
Image mode for Red Hat Enterprise LinuxRed Hat Enterprise Linux

Share:

    The usual workflow for deploying image mode updates onto a host machine is dependent upon a network connection to access a registry and to obtain updates. However, for reasons involving security, location, or even hardware limitations, a system might end up needing an update when remote access isn't possible. Fortunately, image mode for Red Hat Enterprise Linux is flexible enough to maintain and update when deployed online, offline, and in air-gapped environments.

    Prerequisites

    For this article, I prepared my updates on a system running Fedora Workstation 42, and deployed the update to hardware running Red Hat Enterprise Linux 10 (RHEL). However, as long as you're able to create containers and have a system that supports image mode updates, the exact setup isn't important. 

    Here are the requirements for this workflow:

    • Podman
    • Skopeo
    • Access to a registry or a locally stored container
    • An external storage device for the container requiring an update 

    Benefits and disadvantages

    The method I demonstrate here is based on deploying updates on a device by device basis. While this is a perfectly functional method, it’s not ideal for all situations.

    Benefits

    • The machine you’re updating is able to be fully offline and airgapped

    Disadvantages

    • Can be a time consuming process when used across many devices
    • Requires someone on site capable of deploying the update

    If your system is capable of being online, then this approach is not the best option for you. Remote repositories are a great way to deploy updates to a system, and it doesn't require as much setup as this method does. If your situation doesn't match the benefits of this method, and you do have access to remote registries, read How to build, deploy, and manage image mode for RHEL to learn more about managing those systems.

    An overview of the process from start to finish:

    1. Prepare an external storage device on an online system
    2. Copy the image containing the updates you want to distribute to external storage
    3. Use the external storage device to apply updates to your offline system

    Prepare an external storage device on an online system

    One of the challenges of an offline update is obtaining the container you wish to use as the source of the update. In most cases, a container would be made on another system where testing can be done, and then distributed remotely for deployment. However, because this workflow assumes no internet or wireless connection, everything must be done with external storage devices rather than remote registries.

    Before you plug an external storage device into your online system, get a report about what storage devices are already connected to your system. Currently, you only care about the NAME column:  

    $ lsblk
    NAME          MAJ:MIN     SIZE   RO TYPE  MOUNTPOINTS
    zram0           251:0       8G    0 disk  [SWAP]
    nvme0n1         259:0   476.9G    0 disk  
    ├─nvme0n1p1    259:1     600M    0 part  /boot/efi
    ├─nvme0n1p2    259:2       1G    0 part  /boot
    └─nvme0n1p3    259:3   475.4G    0 part  

    Now plug in your external storage and run the same command. You can compare these two outputs to ensure that you know what your system calls your external storage device. In my case, the USB drive being used is named sda, and it has a partition called sda1.

    $ lsblk
    NAME        MAJ:MIN   SIZE   RO TYPE  MOUNTPOINTS
    sda             8:0   28.9G    0 disk  
    └─sda1         8:1   28.9G    0 part 
    zram0         251:0      8G    0 disk  [SWAP]
    nvme0n1       259:0  476.9G    0 disk  
    ├─nvme0n1p1  259:1    600M    0 part  /boot/efi
    ├─nvme0n1p2  259:2      1G    0 part  /boot
    └─nvme0n1p3  259:3  475.4G    0 part  

    The MOUNTPOINTS column lists the mount points of the partitions on your external storage. If your system mounts external storage automatically, then valid mount points already exist. However, if there are no mount points (as in my example), then you must mount it yourself before you can store anything on the device.

    Start with an empty directory. You can either create one, or use an existing empty directory that already exists for this purpose:

    $ sudo mkdir /mnt/usb/

    Once you've got an empty directory, you can mount your device partition. The mount command doesn't normally provide confirmation (only an error generates output). You can verify success by checking the mount point again (I've truncated the output for brevity):

    $ sudo mount /dev/sda1 /mnt/usb
    $ lsblk
    NAME         MAJ:MIN    SIZE   RO TYPE  MOUNTPOINTS
    sda              8:0   28.9G    0 disk  
    └─sda1          8:1   28.9G    0 part  /mnt/usb
    [...]

    Your external storage device is now ready for copying files onto it.

    Transfer an image to external storage

    You can now copy the container to your mounted device. For a container you've got stored locally, use the skopeo command (adapt the paths and names of the container for your own environment): 

    $ sudo skopeo copy --preserve-digests --all \
    containers-storage:localhost/rhel-container:latest \
    oci://mnt/usb/

    For a container stored on a remote registry:

    $ sudo skopeo copy --preserve-digests --all \
    example://quay.io/example:latest \
    oci://mnt/usb/

    Depending on the size of the container, these commands might take a few minutes to complete. Once the container has been copied, unmount and eject the external storage:

    $ sudo umount /dev/sda1
    $ sudo eject /dev/sda1

    Update the container on an offline system

    To apply the update, first plug the external storage device into your offline system. This might not be mounted automatically, so use the mkdir and mount commands as needed to locate the external storage and then mount it.

    For the sake of stability and reusability, it's best to copy the container from the external device over to the offline system's local container registry. Copy the container to the offline machine's local container storage:

    $ skopeo copy --preserve-digests --all \
    oci://var/mnt/usb \
    containers-storage:rhel-update:latest

    In this case, the mount point of the external storage is the path entered to the oci section, while the containers-storage section varies depending on the name and tag you wish the container to have. Use Podman to verify that your container is now local:

    $ podman images
    REPOSITORY			        TAG     IMAGE ID  CREATED  SIZE
    example.io/library/rhel-update   latest  cdb6d...  1 min    1.48 GB

    Deploy the update using bootc:

    $ bootc switch --transport containers-storage \
    example.io/library/rhel-update:latest

    If you weren't able to copy your container to local storage, then you must use the oci transport and the path to your storage device instead:

    $ bootc switch --transport oci /var/mnt/usb

    While it might seem to make more sense to use bootc upgrade, it's the --transport flag in bootc switch that enables you to specify an alternative source for the container. By default, bootc would attempt to pull from a registry because the bootc image builder used a registry to build the original image. There is no way to specify where an update is located when using bootc upgrade. By using bootc switch and specifying that you're using local container storage, this enables you to not only remove the requirement of a remote registry, but also to deploy updates by using  this local container in the future.

    After you’ve done this once, you can now successfully use bootc upgrade, as long as your local container and the update share the same location. If you want to switch to updates on a remote repository in the future, then you’d have to use bootc switch again. To ensure that the update was properly deployed, use the command bootc status:

    $ bootc status
    Staged image: containers-storage:example.io/library/rhel-update:latest
      Digest: sha256: 05b1dfa791...
      Version: 10.0 (2025-07-07 18:33:19.380715153 UTC)
    Booted Image: localhost/rhel-intel:base
      Digest: sha256: 7d6f312e09...
      Version: 10.0 (2025-06-23 15:58:12.228704562 UTC)

    The output shows your current booted image, along with any changes staged to happen. The container you used earlier is here, but note that staged changes do not occur until the next reboot. The output also confirms that updates will be pulled from your container storage.

    After a reboot, you can verify that you've booted into the correct image:

    $ bootc status
    Booted image: containers-storage:example.io/library/rhel-update:latest
    	Digest: sha256: 05b1dfa791...
    	Version: 10.0 (2025-07-07 18:33:19.380715153 UTC)
    Rollback image: localhost/rhel-intel:base
    	Digest: sha256: 7d6f312e09...
    	Version: 10.0 (2025-06-23 15:58:12.228704562 UTC)

    The Booted image is your updated image, and the Rollback image is your previous image. You've successfully performed an offline image mode update.

    If you're not using containers yet, and need help setting one up, read Image mode for Red Hat Enterprise Linux: A quick start guide. 

    Related Posts

    • How to build, deploy, and manage image mode for RHEL

    • How to create CI/CD pipelines for image mode for RHEL

    • Creating a VMDK using image mode for Red Hat Enterprise Linux

    • Image mode for RHEL: 4 key use cases for streamlining your OS

    • Containerizing workloads on image mode for RHEL

    • How image mode for RHEL simplifies software appliances

    Recent Posts

    • Cloud bursting with confidential containers on OpenShift

    • Reach native speed with MacOS llama.cpp container inference

    • A deep dive into Apache Kafka's KRaft protocol

    • Staying ahead of artificial intelligence threats

    • Strengthen privacy and security with encrypted DNS in RHEL

    What’s up next?

    Use Podman Desktop to create a bootable Flask-based application using image mode for RHEL. We will integrate Flask, Gunicorn, and NGINX into a bootable container.

    Start the activity
    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    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