This section provides an overview of how you build a Linux image that can include additional extensions using Elemental and the elemental3ctl command-line interface. The image can be used to boot a virtual machine and run a Linux operating system, such as openSUSE Tumbleweed, with custom configurations and extensions.
Create a qcow2 disk with a size of 20GB:
qemu-img create -f qcow2 example.qcow2 20G
Associate the created virtual disk with a block device:
sudo modprobe nbd && sudo qemu-nbd -c /dev/nbd0 example.qcow2
Check for the block device:
sudo lsblk /dev/nbd0
elemental3ctl can apply basic configuration and extensions at deployment time in the following ways:
While we recommend reading through the complete document to understand system extensions in the context of elemental project, please refer to the “Create system extension images” section for specific steps. After that continue further with the “Configuring through a configuration script” section below.
Overlay is the way of merging contents of a system extension onto the host system such that it all looks like a part of the host system.
example-extension/, create an overlays/var/lib/extensions directory:
mkdir -p overlays/var/lib/extensions
kubectl.x86-64.raw or the tools-1.0_1.0_x86-64.raw file
to this directory. tar -cavzf overlays.tar.gz -C overlays .
You have now prepared an archive containing a system extension image for use during the installation process. This
adds the kubectl binary or strace package to the operating system after boot.
The OS installation supports configurations through a script that will run in a chroot on the unpacked operating system after expanding the provided overlays archives.
This configuration script applies the following set of configurations on the built image:
root user to linux.oneshot type systemd.service that will list the contents of the /var/lib/extensions/ directory.Steps:
Create configuration script:
cat <<- EOF > config.sh
#!/bin/bash
set -e
# Set root user password
echo "linux" | passwd root --stdin
# Configure example systemd service
cat <<- END > /etc/systemd/system/example-oneshot.service
[Unit]
Description=Example One-Shot Service
[Service]
Type=oneshot
ExecStart=/bin/ls -alh /var/lib/extensions/
[Install]
WantedBy=multi-user.target
END
systemctl enable example-oneshot.service
EOF
Make config.sh executable:
chmod +x config.sh
Once you run the below command, the virtual disk created as part of the Prepare the Installation Target section now holds a ready to boot image that will run openSUSE Tumbleweed and will be configured as described in the Prepare Basic Configuration section.
sudo elemental3ctl install \
--overlay tar://overlays.tar.gz \
--config config.sh \
--os-image registry.opensuse.org/devel/unifiedcore/tumbleweed/containers/uc-base-os-kernel-default:latest \
--target /dev/nbd0 \
--cmdline "root=LABEL=SYSTEM console=ttyS0"
Note that:
overlays.tar.gz tarball came from the system extension image example configuration.config.sh script came from the configuration script example./dev/nbd0 is the chosen block device from the qemu-nbd -c command in the Prepare the Installation Target section.NOTE:
elemental3ctlalso supports a--localflag that can be used in combination with theDOCKER_HOST=unix:///run/podman/podman.sockenvironment variable to allow for referring to locally pulled OS images.
In case you encounter issues with the process, make sure to enable the --debug flag for more information. If the issue persists and you are not aware of the problem, feel free to raise a GitHub Issue.
Since you attached a block device to the virtual disk created in the Prepare the Installation Target section, detach the block device before booting the image:
sudo qemu-nbd -d /dev/nbd0
To boot the image in a virtual machine, you can use either QEMU or libvirt utilities for creating the VM.
Using QEMU:
NOTE: Make sure you have
qemuinstalled on your system. If not, you can install it usingzypper install qemu-x86.
NOTE: If you are using a different architecture, make sure to adjust the
qemu-system-x86_64command accordingly.
NOTE: If you haven’t configured your user to be in the
kvmgroup, you can run the command withsudoto allow QEMU to access the KVM acceleration.
qemu-system-x86_64 -m 8G \
-accel kvm \
-cpu host \
-hda example.qcow2 \
-bios /usr/share/qemu/ovmf-x86_64.bin \
-nographic
You should see the bootloader prompting you to start openSUSE Tumbleweed.
Login with the root user and password as configured in the config.sh script.
Check you are running the expected operating system:
cat /etc/os-release
Check that example-oneshot.service has run successfully:
View service status:
systemctl status example-oneshot.service
View service logs:
journalctl -u example-oneshot.service
Check that elemental3ctl binary is available and working:
Check logs for the systemd-sysext.service:
journalctl -u systemd-sysext.service
Try calling the command:
elemental3ctl version
Elemental supports creating installation media in the form of live ISOs or RAW disk images. Content-wise they both are almost the same. The difference is that ISO installs to a target disk device and the RAW disk resets to factory from a recovery partition.
The ISO image includes EFI binaries and bootloader setup, the OS image (as a squashfs image) and the installation assets (configuration script and drop-in files added over the OS).
The RAW image includes the ESP partition with the EFI binaries, the bootloader setup and a recovery partition including the OS image (again as a squashfs image) together with the installation assets.
Regardless of whether the artifact is an ISO or a RAW disk, the respective image boots like a live OS system based on tmpfs overlayfs. The boot process relies on the dmsquash-live dracut module for live booting.
To create a self installer image, you should prepare and include a specific set of configuration assets. These include:
The installer media supports configurations through a script which will run in late initramfs in a writeable system root.
There are some of relevant kernel parameters used by the installer to define the boot context. Those are not configurable
and included by elemental3ctl when setting the bootloader.
elm.recovery: this flag is used to identify the current system is booting from a recovery partition.elm.reset: this flag is used to identify the system is booting from installer RAW image requiring a reset to be fully installed.These kernel parameters can be easily used to handle automated actions at boot like in the example below.
In this example, you prepare a configuration script that sets four aspects:
/run/extensions so that they are loaded at bootCreate the script and make it executable:
cat <<- END > config-live.sh
#!/bin/bash
# Set autologin for the Live ISO
mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d
cat > /etc/systemd/system/serial-getty@ttyS0.service.d/override.conf << EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM
EOF
mkdir -p /etc/systemd/system/getty@tty1.service.d
cat > /etc/systemd/system/getty@tty1.service.d/override.conf << EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM
EOF
# Ensure extensions included in ISO's /extensions folder are loaded at boot
# ISO filesystem is mounted at /run/initramfs/live folder
rm -rf /run/extensions
ln -s /run/initramfs/live/extensions /run/extensions
# Set the elemental-autoinstall.service
cat > /etc/systemd/system/elemental-autoinstall.service << EOF
[Unit]
Description=Elemental Autoinstall
Wants=network-online.target
After=network-online.target
ConditionPathExists=/run/initramfs/live/Install/install.yaml
ConditionFileIsExecutable=/usr/local/bin/elemental3ctl
[Service]
Type=oneshot
ExecStart=/usr/local/bin/elemental3ctl --debug install
ExecStartPost=reboot
[Install]
WantedBy=multi-user.target
EOF
systemctl enable elemental-autoinstall.service
# Set the elemental-reset service
cat > /etc/systemd/system/elemental-reset.service << EOF
[Unit]
Description=Elemental Reset
After=multi-user.target
ConditionPathExists=/run/initramfs/live/Install/install.yaml
ConditionFileIsExecutable=/usr/local/bin/elemental3ctl
ConditionKernelCommandLine=elm.recovery
ConditionKernelCommandLine=elm.reset
OnSuccess=reboot.target
StartLimitIntervalSec=600
StartLimitBurst=3
[Service]
Type=oneshot
ExecStart=/usr/local/bin/elemental3ctl --debug reset
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable elemental-reset.service
END
chmod +x config-live.sh
The provided OS does not include the elemental3ctl required to run the installation to the target disk. The elemental3ctl is delivered through a systemd extension image.
To ensure it is available at ISO boot, it has to be included in the ISO filesystem and either copied or linked to /run/extensions.
This example shows how to prepare the ISO overlay directory tree and the configuration script to ensure the elemental3ctl extensions are
available and loaded at boot.
Create an iso-overlay/extensions directory:
mkdir -p iso-overlay/extensions
Create the elemental3ctl extension image and move it to this directory:
mv example-extension/mkosi.output/elemental3ctl-3.0.x86-64.raw iso-overlay/extensions
Make sure the live configuration script links the extensions folder at /run/extensions
If you do not have mcopy command on your system, install it using:
zypper in mtools
The command below creates an ISO image inside the build output directory.
It will be using an openSUSE Tumbleweed image and will be configured to automatically self install to the target device (e.g. /dev/sda) at boot.
sudo elemental3ctl --debug build-installer \
--type iso \
--output build \
--os-image registry.opensuse.org/devel/unifiedcore/tumbleweed/containers/uc-base-os-kernel-default:latest \
--overlay dir://iso-overlay \
--cmdline "console=ttyS0" \
--config config-live.sh \
--install-target /dev/sda \
--install-overlay tar://overlays.tar.gz \
--install-config config.sh \
--install-cmdline "console=ttyS0"
In order to build a RAW disk image just use the same command as above but switching to RAW type (--type raw flag).
The RAW disk image only includes the ESP partition and a recovery partition. The recovery partition includes a squashfs OS image to boot from like a live ISO would.
Note that:
overlays.tar.gz tarball came from the system extension image example configuration.config.sh script came from the configuration script example./dev/sda is the target device you want the ISO to install to.iso-overlay is the directory tree including extensions that will be included in the ISO filesystem of the built image.config-live.sh script came from the live configuration script example.NOTE: Make sure you have
qemuinstalled on your system. If not, you can install it usingzypper -n install qemu-x86. If you are using a different architecture, ensure the package name and respective command below are adjusted accordingly.
Launch a virtual machine to boot the installer ISO and verify the automated installation:
cp /usr/share/qemu/ovmf-x86_64-vars.bin .
qemu-system-x86_64 -m 8G \
-accel kvm \
-cpu host \
-hda disk.img \
-cdrom build/installer.iso \
-drive if=pflash,format=raw,readonly,file=/usr/share/qemu/ovmf-x86_64-code.bin \
-drive if=pflash,format=raw,file=ovmf-x86_64-vars.bin \
-nographic
Note that:
disk.img can be an empty disk image file created with the qemu-img create command.NOTE: Make sure you have
qemuinstalled on your system. If not, you can install it usingzypper -n install qemu-x86. If you are using a different architecture, ensure the package name and respective command below are adjusted accordingly.
To test the RAW installer image with QEMU, you need to either dump the image to a bigger image or expand the generated image.
cp /usr/share/qemu/ovmf-x86_64-vars.bin .
qemu-img resize build/installer.raw 16G
Launch a virtual machine to boot the installer RAW and verify, at boot, it self expands the partition table to fulfill the disk geometry and creates additional partitions.
qemu-system-x86_64 -m 8G \
-accel kvm \
-cpu host \
-hda build/installer.raw \
-drive if=pflash,format=raw,readonly,file=/usr/share/qemu/ovmf-x86_64-code.bin \
-drive if=pflash,format=raw,file=ovmf-x86_64-vars.bin \
-nographic
Note that:
Suppose the image that you created as part of the previous sections has been running for a while and now you want to upgrade its operating system to include the latest available package versions.
You can do this through the elemental3ctl command line tool, by executing the following command:
elemental3ctl upgrade --os-image registry.opensuse.org/devel/unifiedcore/tumbleweed/containers/uc-base-os-kernel-default:latest
After command completion, a new snapshot will be created:
localhost:~ # snapper list
# | Type | Pre # | Date | User | Used Space | Cleanup | Description | Userdata
---+--------+-------+--------------------------+------+------------+---------+-----------------------------------------+---------
0 | single | | | root | | | current |
1- | single | | Wed Jul 16 12:57:23 2025 | root | 12.28 MiB | | first root filesystem, snapshot 1 |
2+ | single | | Wed Jul 16 13:00:13 2025 | root | 12.28 MiB | number | snapshot created from parent snapshot 1 |
What’s left is to reboot the OS and select the latest snapshot from the grub menu. After the reboot, your snapshots should look similar to this:
localhost:~ # snapper list
# | Type | Pre # | Date | User | Used Space | Cleanup | Description | Userdata
---+--------+-------+--------------------------+------+------------+---------+-----------------------------------------+---------
0 | single | | | root | | | current |
1 | single | | Wed Jul 16 12:57:23 2025 | root | 12.28 MiB | | first root filesystem, snapshot 1 |
2* | single | | Wed Jul 16 13:00:13 2025 | root | 12.28 MiB | number | snapshot created from parent snapshot 1 |
The latest snapshot will be running on the latest version of the registry.opensuse.org/devel/unifiedcore/tumbleweed/containers/uc-base-os-kernel-default image and will still hold any previously defined configurations and/or extensions.