Post

Immutable digital workspaces with Red Hat Image Mode, IGEL and Omnissa Horizon

Immutable digital workspaces with Red Hat Image Mode, IGEL and Omnissa Horizon

Red Hat Image Mode was announced last year at Summit 2025 in Boston and is something I’ve been wanting to unpack in a blog. In this article I’ll demonstrate a truly immutable workstation that can be achieved end-to-end using technology from Red Hat, IGEL and Omnissa.

Immutability

Immutability (in computing) simply means that after something has been written it can’t be altered.

“the state of not changing, or being unable to be changed” - Cambridge Dictionary

In the post-ransomware era it’s been a widely marketed backup solution feature, because it prevents tampering with and possibly encrypting, or otherwise wreaking havoc on backups.

For the immutable operating system context, whether servers, workstations or other kinds of digital workspaces, it would entail that operators or users cannot change the OS or install applications due to the filesystem/partitions being read-only.

A pinch of “writeability” is necessary, otherwise one wouldn’t be able to actually do much of any work on a computer - because of configuration files, logs, applications’ cache or temporary files, et cetera

Immutability also affords easier upgrades as the entire operating system becomes a huge transactional blob that can be slotted into place. Fewer moving parts, less things that can go awry.

Applicable use-cases

There are “pros and cons” to immutability. Not every use-case will benefit from what I’m proposing in this blog.

Benefits

  • The OS is locked down and users can only access apps that were installed during its creation (barring an application streaming solution)
  • Easy and quick deployments, that fits right into CI/CD workflows - the recipe for the OS is codified
  • Creating a Software Bill of Materials (SBOM) becomes trivial
  • Upgrades and rollbacks are also easy to handle

Disadvantages

  • Power users needing admin-level permissions to make changes, install apps not in the base image, and similar, may require something bespoke and less hardened

Red Hat Image Mode

I learnt about Image Mode at a Red Hat event in Oslo last year and my mind immediately raced towards immutable end-user-computing as a valid, potential avenue to explore. Immutability in the container- and server space has been discussed for years, and there’s traction for desktops as well, with e.g. Fedora Silverblue.

Image Mode is available with RHEL 9.6+ and 10 as this post goes live

The secret sauce in Image Mode is bootc - bootable containers, which enables one to package the full-on RHEL-OS as a container, allowing for a unified, DevOps-friendly, repeatable way of handling the Linux OS as one would with garden variety cloud-native container images. Upgrades and rollbacks involve a small operation of switching the container image that will spin up on next boot.

See the Containerfile of the RHEL 9 bootc image that’s based on UBI

Immutable-VDI Red Hat Image Mode

“Over the last decade, OCI containers have become a de facto way to deploy a complete functioning Linux user space as an application. A large set of practices and tooling have evolved around them. Bootable containers are a modern opinionated way of deploying, configuring and managing immutable image based Linux systems using those practices and tooling” - What is a bootable container

Partitions besides /etc and /var are read-only, such that installing additional apps is only possible at build-time. A reboot will undo any changes that didn’t pertain to those two partitions.

What made me think of VDI as a possible use-case - and I didn’t know it was possible until trying - is that one can convert these Image Mode artifacts to virtual machine-ready disk files to make them deployable on e.g Red Hat OpenShift Virtualization, VMware vSphere, or even baremetal, making it extremely flexible.

There’s a lot of information in Red Hat’s documentation and learning portals to get started, and also a demo page, YouTube videos and an introductory lab. Matthias Hu has also written an awesome article on bootc and Image Mode that I happened upon

IGEL OS

IGEL Secure Endpoint OS is the operating system in IGEL’s three-pronged portfolio of OS, management (UMS) and cloud offering - an immutable OS with mostly read-only partitions, that can, for instance, be used as a secure workspace to access VDI (through e.g. AVD, Horizon, Citrix, RDP/Terminal Servers), employed as a lightweight desktop in its own right, or turned into a kiosk station.

The IGEL Community has a comprehensive IGEL Sandbox Guide for bootstrapping a lab environment; do also read the related blog

In real-life IGEL OS is installed on physical endpoint devices such as thin clients or laptops, but in my lab I’ve deployed it on a virtual machine for ease-of-testing and maneuverability

I’ve installed the Horizon Client and a few other test apps on my IGEL OS lab VM like so:

Immutable-VDI IGEL OS 12

Omnissa Horizon

Omnissa Horizon is a VDI solution with nearly 20 years worth of history and is the one I have the most experience with.

I’ll be using Instant Clones with a floating desktop pool - fleeting desktops that are deleted and new ones spawned on every logoff.

In that sense it’s already immutable because any changes made to the VDI won’t persist across logins as it’s a new machine every time. By combining this with Image Mode, users are incapable of altering them in any way, shape or form. Every VDI will be a tabula rasa with few attack vectors.

Experiment

In this experiment I create a Red Hat Image Mode container with all the components necessary to be run as Horizon Instant Clones. I then access this pool of machines via the Horizon Client on my (virtual) IGEL OS lab environment.

Components

The diagram and table below show the main components that I’m using for this blog.

block
  columns 2
  user(("End-user")) :2
  blockArrowId7<["&nbsp;&nbsp;&nbsp;"]>(down) :2

  a["IGEL OS with Horizon Client"] :2
  b["Omnissa Horizon"] :2  
  c["Red Hat Image Mode Workstation"] :2

Using these versions:

ComponentVersion
Horizon2512
IGEL OS12.7.5
IGEL UMS12.10.100
Red Hat Enterprise Linux (Image Mode)9.7

Creating the image

I’m using a RHEL 10 machine to build the bootc image and later convert it to the VMDK format.

Read my recent blog about getting started with the Red Hat Developer Subscription for Individuals

Sign up for a free https://quay.io account by connecting it to your Red Hat account.

Follow this Red Hat guide for a detailed account on how to create a service account for logging into the registry.

1
podman login registry.redhat.io

Start by cloning my redhat-image-mode-vdi GitHub repository that contains a Containerfile and sssd configuration file for the domain. The latest Horizon Agent RPM (at the time of this post 8.17) should also be downloaded and placed in this folder.

1
2
3
4
5
6
7
8
9
10
git clone https://github.com/erikgraa/redhat-image-mode-vdi

cd redhat-image-mode-vdi

tree

.
├── Containerfile
├── domain.conf
└── Omnissa-horizonagent-linux-2512-8.17.0-20250894398.el.x86_64.rpm

The Containerfile specifies what is installed and configured, and which bootc image is being used:

  • RHEL 9.7 for the bootc image
  • Gnome is installed as the desktop environment (the “workstation” grouping)
  • System registration notification suppressed, and the Gnome “tour” and initial setup removed
  • SSSD is used for Active Directory integration
  • In my environment the vdiusers AD group is cleared to login via the /etc/sssd/conf.d/domain.conf file, and use sudo through an entry in the the /etc/sudoers.d/01-vdi-users file

Wayland instead of X, along with RHEL 10, is the way forward; I will probably test VDI with RHEL10 Image Mode out soon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
FROM registry.redhat.io/rhel9/rhel-bootc:latest

# Install dependencies
RUN dnf -y install bc cloud-init libXScrnSaver nss-tools open-vm-tools pciutils pulseaudio-utils xorg-x11-drv-vmware xorg-x11-server-utils xorg-x11-xauth zenity oddjob oddjob-mkhomedir krb5-workstation

# Install desktop environment (Gnome)
RUN dnf -y group install "Workstation"

# Install Omnissa Horizon Agent
COPY Omnissa-*.rpm /tmp
RUN rpm -Uvh /tmp/Omnissa*.rpm

# Set graphical as the default target
RUN systemctl set-default graphical

# Enable/disable services
RUN systemctl disable mcelog
RUN systemctl disable firewalld
RUN systemctl enable viewagent.service
RUN systemctl enable vmtoolsd.service

# Suppress system not registered message
RUN systemctl --global mask org.gnome.SettingsDaemon.Subscription.service

# Disable Gnome Tour and initial setup
RUN dnf -y remove gnome-tour
RUN dnf -y remove gnome-initial-setup
RUN echo "X-GNOME-Autostart-enabled=false" >> /etc/xdg/autostart/gnome-initial-setup-first-login.desktop

# Active directory integration
COPY domain.conf /etc/sssd/conf.d/domain.conf
RUN chown root:root /etc/sssd/conf.d/domain.conf
RUN chmod 600 /etc/sssd/conf.d/domain.conf
RUN authselect select -f sssd
RUN echo "OfflineJoinDomain=sssd" >> /etc/omnissa/viewagent-custom.conf
RUN echo "%vdiusers ALL=(ALL) ALL" >> /etc/sudoers.d/01-vdi-users
RUN chmod 440 /etc/sudoers.d/01-vdi-users

# Clear cache and temporary files
RUN dnf clean all
RUN rm -rf /var/{cache,log} /var/lib/{dnf,rhsm}

# Linting
RUN bootc container lint

Modify the sssd.conf to reflect your domain of choice (change any references to dev.graa). This is the domain that RHEL VDI machines will be joined to. Do also change the AD groups and any users allowed to login.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[sssd]
domains = dev.graa
config_file_version = 2
services = nss, pam

[domain/dev.graa]
ad_domain = dev.graa
krb5_realm = DEV.GRAA
realmd_tags = manages-system joined-with-adcli
cache_credentials = True
id_provider = ad
krb5_store_password_if_offline = True
default_shell = /bin/bash
ldap_id_mapping = True
use_fully_qualified_names = False
fallback_homedir = /home/%u@%d
access_provider = simple
ad_gpo_map_interactive = +gdm-vmwcred
simple_allow_groups = [email protected]
simple_allow_users = erik
ad_gpo_access_control = permissive

At this point we can build the image using my Containerfile with the latest RHEL 9 bootc image - I’m calling it rhel-bootc-vdi.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
time podman build -t localhost/rhel-bootc-vdi .

[..]

Checks passed: 10
Checks skipped: 1
Warnings: 2
COMMIT localhost/rhel-bootc-vdi
--> aa079ac72bbf
Successfully tagged localhost/rhel-bootc-vdi:latest
aa079ac72bbfa0438f81f84d6faf749b7804e37654a5ed7cf6ec089e6037033e

real    8m19.987s
user    2m47.913s
sys     4m38.814s
1
2
3
podman image ls
REPOSITORY                                    TAG             IMAGE ID      CREATED         SIZE
localhost/rhel-bootc-vdi              latest          aa079ac72bbf  About a minute ago  6.51 GB

Convert it to VMDK with the latest bootc-image-builder image:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mkdir output
time podman run --rm -it --privileged -v /var/lib/containers/storage:/var/lib/containers/storage -v ./output:/output --security-opt label=type:unconfined_t --pull newer registry.redhat.io/rhel10/bootc-image-builder:latest --local --type vmdk localhost/rhel-bootc-vdi

type vmdk localhost/rhel-bootc-vdi
Trying to pull registry.redhat.io/rhel10/bootc-image-builder:latest...
Getting image source signatures
Checking if image destination supports signatures
Copying blob f518af3ed348 done   |
Copying blob 2ad61786712b skipped: already exists
Copying config c510ba6f25 done   |
Writing manifest to image destination
Storing signatures
WARNING: --local is now the default behavior, you can remove it from the command line
[-] Disk image building step
[5 / 5] Pipeline vmdk [---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->] 100.00%
[2 / 2] Stage org.osbuild.qemu [------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->] 100.00%
Message: Results saved in .

real    7m37.263s
user    0m2.242s
sys     0m2.322s

The finished artifact in /output nets a little over 3GB.

1
2
3
du -sh output/vmdk/disk.vmdk

3.3G    output/vmdk/disk.vmdk

Creating the VM and desktop pool

Follow the VMware vSphere guide and use the govc tool, or create a virtual machine, copy the disk file and attach it as an existing hard disk.

In order to make use of the image I needed to convert it using vmkfstool from an ESXi Shell or through SSH

1
vmkfstools -i disk.vmdk rhel-Immutable-VDI.vmdk

Immutable-VDI Add existing hard disk to Golden Image VM

Take a snapshot of the Golden Image VM.

Immutable-VDI Golden Image snapshot

Use this snapshot as the basis in a Horizon Instant Clone Desktop Pool for Linux - see the documentation for more information.

Immutable-VDI Golden Image snapshot

Results

I smoketested one of the VMs by logging in with my Active Directory user directly from the console, circumventing Horizon and observed the viewagent service running!

Immutable-VDI Red Hat Image Mode VDI

Immutable-VDI Red Hat Image Mode VDI

The litmus test then lay in being able to connect to the Red Hat Image Mode VM through Horizon via IGEL OS.

The following commands and video show that the VDI is running on a bootable container and that important partitions are immutable (read-only):

1
2
3
sudo bootc status

sudo rm -rf /bin

Closing notes

This post showcased a simple prototype of how one might accomplish immutability for a digital workspac, from a thin client or similar physical endpoint device, via an immutable OS, all the way to an immutable VDI running Red Hat Image Mode. Image Mode was put to the test by trying to remove some system files, and validating that bootc is actually being used.

Even as root it’s simply not possible to do anything with the OS. Users are completely beholden to what was installed at logon-time - and that’s true for the OS on the underlying, physical endpoint as well. The attack surface becomes exceedingly small. It’s not a panacea or be-all-end-all solution, and may be too inflexible for veritable power-users, but in any case a very fun experiment to document.

I see enormous application potential that could be underpinned by Image Mode in quests for immutability and security and hope to write more about Image Mode soon!

Immutable-VDI Red Hat Image Mode VDI

Lessons and caveats

It did take a few attempts to arrive at the final Containerfile, but it did wind up being quite nimble and also quick to “compile”.

  • sssd.conf needed chmod 600 in order for the service to start
  • For some reason sssd.conf got overwritten with a near-blank config, meaning that I needed to have my actual domain’s config in the /etc/sssd/conf.d/ folder
  • The mcelog service and some others seem to fail after some time on Image Mode VDIs, so I disabled that for this experiment
  • Port tcp/22443 isn’t open all the time on VDIs/Horizon Agent - only when a connection has been established
  • Firewalld is disabled for testing purposes, and I had some problems adding rules - firewall-offline-cmd could possibly work better
  • Not allowing sudo would be even better from a security-standpoint
  • When VDIs fail to join the domain during customization, they’ll power off and a new attempt is made every few minutes, making troubleshooting slightly inconvenient - especially when Horizon reported the VMs as available while still waiting for realm join to finish
    • It wasn’t immediately obvious why the RHEL VDIs didn’t join the domain successfully. It turned out the oddjob and oddjob-mkhomedir packages needed to be installed as well
    • Install cloud-init and use VM advanced settings to push a local account for easier troubleshooting

Immutable-VDI Red Hat Image Mode VDI - Domain Join

Future work

To “professionalize” and automate this concept fully one would want to add the following to an automated pipeline:

  • Building the Image Mode container image with IaC tooling - Ansible, or Terraform, with e.g. GitHub Actions
  • Pushing images to an actual container registry - in this blog they were kept locally
  • Uploading the disk file and creating a new machine with a snapshot
  • Maintenance of the desktop pool to constantly use the latest possible image built
  • Creating SBOM artifacts as part of the process
  • Learn about flatpacks and how they might complement bootc

Credits

Some photos by IGEL, Omnissa and Red Hat. Header photo from the movie Gattaca (1997).

This post is licensed under CC BY-NC-SA 4.0 by the author.
Written by human, not by AI