Add Read-Only Runtime Files Safely with systemd-sysext

systemd-sysext lets you layer extra files onto a running system without touching the base install, and unmerging takes them straight back off again. In this guide you will stage a directory-based system extension, check that systemd accepts it, merge its files into /usr/ and /opt/, and remove the overlay again. The same workflow also covers systemd-confext for /etc/. Allow about 15 minutes for a small test extension, plus time to build or obtain the real image.

Before you start

You need a systemd host with the systemd package installed, a shell, and root access for changes to the live filesystem. This local machine has systemd 255.4-1ubuntu8.17. The commands and option names below are verified against its systemd 255 manpage and executable.

System extensions are additive runtime overlays, not packages. A sysext contributes files below /usr/ and /opt/; anything it places below /etc/ or /var/ is ignored. A confext contributes only below /etc/. While a sysext is merged, the host /usr/ and /opt/ become read-only, same as the extension itself. Do not use this workflow for a change that must survive removal or reboot unless you have a separate deployment plan.

1. Inspect the current state

Start with read-only commands. Use systemd-confext instead when working with configuration extensions.

systemd-sysext --no-pager --no-legend status
systemd-sysext --no-pager --no-legend list
systemctl is-enabled systemd-sysext.service

On a host with no extensions, status can report /usr none - and /opt none -, while list reports No OS extensions found. The service may show disabled, which only means boot activation is off, not that the command cannot be run by hand. It needs the privileges required to mount an overlay, so prefix any state-changing invocation with sudo.

2. Build a compatible extension directory

The normal installation directory is /var/lib/extensions/. Directories in /etc/extensions/ and /run/extensions/ are also searched and are handy for small extensions or symlinks. A directory image must share its name with its release metadata file. This example creates an extension called acme-tools containing one executable. Replace the placeholder with files you have reviewed yourself.

sudo install -d -m 0755 \
  /var/lib/extensions/acme-tools/usr/local/bin \
  /var/lib/extensions/acme-tools/usr/lib/extension-release.d
sudo install -m 0755 ./acme-tools-check \
  /var/lib/extensions/acme-tools/usr/local/bin/acme-tools-check
sudo tee /var/lib/extensions/acme-tools/usr/lib/extension-release.d/extension-release.acme-tools >/dev/null <<'EOF'
ID=ubuntu
VERSION_ID=24.04
SYSEXT_LEVEL=1
ARCHITECTURE=x86-64
EOF

The metadata is checked against the host's os-release data. In a real build, copy the matching ID, and use the host's SYSEXT_LEVEL when defined, otherwise its VERSION_ID. ARCHITECTURE is optional but worth setting when the image is not portable between architectures. Check the values before continuing:

cat /etc/os-release
cat /var/lib/extensions/acme-tools/usr/lib/extension-release.d/extension-release.acme-tools

Warning: do not put a replacement /usr/lib/os-release in the extension. It would overlay the host's own operating-system identity. The systemd 255 image policy accepts unprotected images by default, but signed or Verity-protected images are preferable for production. Treat --force as an emergency compatibility override, not a routine fix for mismatched metadata.

Checkpoint: the image is discoverable

Ask systemd to enumerate the extension before mounting anything.

sudo systemd-sysext --no-pager --no-legend list

You should see acme-tools in the list. If it is missing, check the directory name, the .raw rule for disk images, and permissions. A directory extension must sit directly below one of the search paths, not nested in an extra project directory.

3. Merge and verify the overlay

Merge every currently installed sysext image with one elevated command.

sudo systemd-sysext merge
systemd-sysext --no-pager --no-legend status
command -v acme-tools-check

The status output should show merged hierarchies, and command -v should resolve to /usr/local/bin/acme-tools-check. The file is visible as part of the merged host tree, but it is still supplied by the extension underneath. A merge fails if the hierarchies are already merged; use refresh instead after changing the installed images.

For a configuration extension, create content below /etc/, place it in /var/lib/confexts/, and run:

sudo systemd-confext list
sudo systemd-confext merge
systemd-confext --no-pager --no-legend status

Confext overlays are mounted with nosuid and, by default, noexec. --noexec=false disables the latter, and only when you have a clear reason to allow execution from the configuration overlay.

4. Refresh after an installation change

Adding or removing an image while the overlay is already mounted does not update the existing merge on its own. Use:

sudo systemd-sysext refresh
sudo systemd-sysext --no-pager --no-legend status

refresh briefly unmounts the old overlay and mounts a new one. During that short interval, extension files disappear entirely. Avoid it during a sensitive deployment or while a process depends on a supplied file. The service unit uses refresh too, whenever it starts or reloads.

5. Undo the change

Unmerge first, then remove or archive the image only once the files it supplied have actually disappeared.

sudo systemd-sysext unmerge
systemd-sysext --no-pager --no-legend status
test ! -e /usr/local/bin/acme-tools-check && echo "extension file is gone"

This removes the overlay and reveals the original host trees again. It does not delete the extension directory itself. If the extension was only a test, remove that exact directory after unmerging:

sudo rm -rf -- /var/lib/extensions/acme-tools

Destructive action: that final command is irreversible and needs elevated privileges. Verify the path carefully before pressing Enter. If you remove the wrong files, restore the extension from its build artefact or backup, then run sudo systemd-sysext refresh or merge as appropriate.

Boot activation and boundaries

If the installed images should merge during boot, enable the service deliberately:

sudo systemctl enable systemd-sysext.service
systemctl is-enabled systemd-sysext.service

There is no per-image enable switch. Once the service is enabled, every installed image that passes discovery and compatibility checks activates automatically. To stop boot activation without deleting an image, disable the service instead, or place an empty directory with the same image name in /etc/extensions/ to mask a lower-precedence system extension.

Extensions are not a generic package manager: there is no dependency resolution. They are not a security boundary either; their files simply appear as part of the host system. Do not ship system services or systemd-sysusers definitions this way, because the service runs after the filesystems needed for discovery are already mounted. Use a portable service instead when you need service-level isolation for a disk image.

Done means