Translation(s): none


UKIs (Unified Kernel Images) are UEFI PE binaries that embed everything needed to boot a system. They can be launched from the UEFI firmware, or from a first stage or second stage bootloader.

They provide several benefits over a traditional split-artifacts approach:

At its very basic, a UKI is created by combining systemd-stub, kernel, cmdline, and initrd into one single EFI binary, and signing it for SecureBoot. There are various ways to build a UKI, remotely and locally, and the rest of this document will cover the available options. Users can evaluate what their preferred option is.

Build locally with systemd-ukify and initramfs-tools

systemd's ukify provides an easy way to generate unified kernel images. Ensure systemd-boot-efi is installed, and sbsigntool for signing:

   1 apt install systemd-ukify-efi sbsigntool

The default initrd built by initramfs-tools will be used for the UKI. This will build the UKI:

   1 dpkg-reconfigure linux-image-$(uname -r)

Out of the box, the image is not signed, which will cause problems with SecureBoot enabled machines. To build and sign the image automatically with every kernel upgrade and initrd generation, create these two scripts:

/etc/kernel/postinst.d/zz-ukify:

   1 #!/bin/bash
   2 set -e
   3 
   4 /usr/lib/systemd/ukify build \
   5     --linux="$2" \
   6     --initrd="/boot/initrd.img-$1" \
   7     --cmdline="replace with your cmdline" \
   8     --splash="/path/to/splash.bmp" \ # Remove this line if you don't want one
   9     --output="/boot/efi/EFI/Linux/debian.efi"
  10 
  11 # Add this line if you want to sign the image for secure boot
  12 sbsign --key /path/to/db.key --cert /path/to/db.crt --output /boot/efi/EFI/Linux/debian.efi /boot/efi/EFI/Linux/debian.efi

/etc/initramfs/post-update.d/zz-ukify:

   1 #!/bin/bash
   2 set -e
   3 
   4 /usr/lib/systemd/ukify build \
   5     --linux="/boot/vmlinuz-$1" \
   6     --initrd="$2" \
   7     --cmdline="replace with your cmdline" \
   8     --splash="/path/to/splash.bmp" \ # Remove this line if you don't want one
   9     --output="/boot/efi/EFI/Linux/debian.efi"
  10 
  11 # Add this line if you want to sign the image for secure boot
  12 sbsign --key /path/to/db.key --cert /path/to/db.crt --output /boot/efi/EFI/Linux/debian.efi /boot/efi/EFI/Linux/debian.efi

Note that those files *look* the same, but they are slightly different. They also need to be tweaked to use your enrolled SecureBoot keys.

Make them executable, create the destination folder, and update the initrd to create the first image:

chmod +x /etc/kernel/postinst.d/zz-ukify
chmod +x /etc/initramfs/post-update.d/zz-ukify
mkdir -p /boot/efi/EFI/Linux
update-initramfs -u

Build locally with mkosi-initrd

A UKI can provide many more features than what can be obtained by assembling it manually from Debian's initramfs-tools initrd. Using mkosi it is possible to set up local builds of UKIs that assemble the initrd using standard Debian packages, instead of the initramfs-tools or dracut local scripts. Among other things, this also sets up the UKI with a corresponding signed TPM2 policy, so that secrets (e.g.: full disk encryption with systemd-cryptenroll) can be sealed against the public key signing the image, rather than raw values that change on every build.

Install the required packages:

apt install mkosi systemd-boot-efi

Set up the kernel-install configuration to make it use mkosi-initrd:

   1 mkdir -p /etc/kernel/
   2 cat <<EOF >/etc/kernel/install.conf
   3 initrd_generator=mkosi-initrd
   4 uki_generator=mkosi
   5 layout=uki
   6 EOF

This is enough to get an initrd with the default configuration, that will match the content of the local host in terms of kernel modules being loaded. A kernel command line can be added via /etc/kernel/cmdline, for example to configure a rootfs on a BTRFS subvolume and plymouth:

   1 mkdir -p /etc/kernel/
   2 cat <<EOF >/etc/kernel/cmdline
   3 rootflags=subvol=@rootfs quiet splash
   4 EOF

In case additional packages are required, for example any firmware package, a mkosi-initrd configuration can be also added. This can be extended at will, including any desired extra packages, a root password for emergency shell, etc.

For example, to add plymouth, firmware for Atheros wifi cards and the kernel module for erofs:

   1 mkdir -p /etc/mkosi-initrd/
   2 cat <<EOF >/etc/mkosi-initrd/mkosi.conf
   3 [Content]
   4 Packages=
   5  plymouth-themes
   6  firmware-atheros
   7 KernelModulesInclude=
   8  erofs.ko
   9 EOF

A /etc/mkosi-initrd/mkosi.extra/ directory can also be created, and any file/directory under it will be added to the root of the generated initrd.

To sign the UKI and its PCR policies the means to access a signing key must be given, for a plaintext key (not recommended):

   1 mkdir -p /etc/mkosi-initrd/
   2 cat <<EOF >>/etc/mkosi-initrd/mkosi.conf
   3 [Validation]
   4 SecureBoot=yes
   5 SecureBootCertificate=/path/to/cert
   6 SecureBootKey=/path/to/key
   7 SignExpectedPcrCertificate=/path/to/cert
   8 SignExpectedPcrKey=/path/to/key
   9 EOF

It is better for security to store the secret key on a hardware token, but be aware that this means interactive builds are required, so automated generation of the UKI will need to be disabled (along with unattended-upgrades). For example, if the key can be used via PKCS11:

   1 mkdir -p /etc/mkosi-initrd/
   2 cat <<EOF >>/etc/mkosi-initrd/mkosi.conf
   3 [Validation]
   4 SecureBoot=yes
   5 SecureBootKeySource=provider:pkcs11
   6 SecureBootCertificateSource=provider:pkcs11
   7 SecureBootKey=pkcs11:<PKCS%%URI>
   8 SecureBootCertificate=pkcs11:<PKCS%%URI>
   9 SignExpectedPcrKeySource=provider:pkcs11
  10 SignExpectedPcrCertificateSource=provider:pkcs11
  11 SignExpectedPcrKey=pkcs11:<PKCS%%URI>
  12 SignExpectedPcrCertificate=pkcs11:<PKCS%%URI>
  13 EOF

For SecureBoot, the public key needs to be enrolled in MOK, following the same steps detailed elsewhere on this page.

Finally, when the configuration is in the desired state, build UKIs for all available kernels:

for k in /boot/vmlinu[xz]-*; do \
  [ -f "$k" ] || continue; \
  ver=$(basename "$k" | sed s/^vmlinu[xz]-//); \
  kernel-install add "$ver" "$k"; \
done

Install a pre-built UKI

The upstream systemd project has started offering pre-built UKIs for some combinations of distributions and architectures, built and signed on the SUSE Open Build Service. These are NOT official Debian images. They are built using the latest stable branch of systemd. amd64 Trixie and testing UKIs are currently offered.

/!\ These are NOT OFFICIAL Debian images, and are experimental, use at your own risk

   1 mkdir -p /etc/sysupdate.d/
   2 cat <<EOF >>/etc/sysupdate.d/70-upstream-UKI.transfer
   3 [Source]
   4 Type=url-file
   5 Path=https://download.opensuse.org/repositories/system:/systemd:/stable/debian_13_images/
   6 MatchPattern=debian-trixie_@v_%a.efi
   7 
   8 [Target]
   9 Type=regular-file
  10 Path=/EFI/Linux
  11 PathRelativeTo=boot
  12 MatchPattern=debian-trixie_@v_%a+@l-@d.efi \
  13   debian-trixie_@v_%a+@l.efi \
  14   debian-trixie_@v_%a.efi
  15 Mode=0644
  16 TriesLeft=3
  17 TriesDone=0
  18 InstancesMax=2
  19 EOF

Due to a bug in systemd-sysupdate at the time of writing, it is necessary to add a workaround to avoid the currently running UKI from being removed to make space for updates:

   1 mkdir -p /etc/systemd/system/systemd-sysupdate.service.d/
   2 cat <<EOF >>/etc/systemd/system/systemd-sysupdate.service.d/preserve-uki.conf
   3 [Service]
   4 BindReadOnlyPaths=-/run/systemd/stub/os-release:/usr/lib/os-release
   5 EOF

The key that signs the shasum needs to be added to the importd keyring:

   1 wget https://build.opensuse.org/projects/system:systemd:stable/signing_keys/download?kind=gpg -O- | gpg --dearmor > /etc/systemd/import-pubring.gpg

If SecureBoot is enabled (recommended!) the signing key needs to be imported in DB or MOK:

   1 wget https://build.opensuse.org/projects/system:systemd:stable/signing_keys/download?kind=ssl -O- | openssl x509 -inform pem -outform der -out obs.der
   2 mokutil --import obs.der

After these setup steps are completed, systemd-sysupdate can be ran and it will fetch and install the UKI, and keep it up to date when configured to run on a timer:

   1 systemctl enable --now systemd-sysupdate.timer

GNOME 50 will also include automated support for sd-sysupdate via the GNOME Software program.

If customizations to the kernel command line and/or initrd are necessary, it is recommended to build local addons with mkosi-addon and place them under the global addon directory in the ESP: /boot/efi/loader/addons/ If SecureBoot is used, self-signing can be done, and remember to enroll the key in MOK.

The build process for these UKIs happens on OBS: https://build.opensuse.org/package/show/system:systemd/uki-debian Manifest files are provided next to the images, listing the full content, and build logs can be accessed to be checked too, the build tool is again mkosi Users familiar with OBS can branch that package and customize the build for their own needs, and adjust the systemd-sysupdate configuration, URLs, keys, etc accordingly.

See also

EFIStub


CategoryBootProcess CategoryBootProcess