Build a Compatible systemd System Extension Release File
You will finish with the metadata file that lets systemd identify a system extension and reject an image built for the wrong operating system, release level or CPU architecture. The examples target systemd 255.4-1ubuntu8.17 from Ubuntu package systemd on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about twenty minutes. You need a shell, a system extension tree you can build or inspect, and the ability to read /etc/os-release. The guide uses a temporary directory and does not install an image, merge an overlay or change a service. Merging an extension changes what appears below /usr and /opt; treat that as a maintenance-window operation and keep a recovery command ready.
1. Check the installed contract
Confirm the local tool version and the commands it exposes. These are ordinary, read-only checks:
$ systemd-sysext --version
systemd 255 (255.4-1ubuntu8.17)
$ systemd-sysext --help
systemd-sysext [OPTIONS...] COMMAND
...
list List installed extensions
The relevant systemd 255 layout is a file inside the image at /usr/lib/extension-release.d/extension-release.NAME. For a directory image called labtools, the metadata path is therefore /usr/lib/extension-release.d/extension-release.labtools. The suffix must match the image name exactly. A mismatch is a packaging error, not a reason to use --force.
Checkpoint: write down the image name before creating the file. Do not use a convenient versioned filename in one place and an unversioned deployment name in another.
2. Record the host values
Read the host identity without sourcing it as shell code. The file uses shell-compatible assignments, but it supports data values only, not variable expansion or arbitrary shell features:
$ grep -E '^(ID|VERSION_ID|SYSEXT_LEVEL|ARCHITECTURE)=' /etc/os-release
VERSION_ID="24.04"
ID=ubuntu
Your output may contain additional fields. For the normal compatibility check, copy the host ID into the extension. If the host publishes SYSEXT_LEVEL, use the same value when the extension is intended for that support level. Otherwise use the host VERSION_ID. These values are matching data, not a general description of the extension's own release.
Use SYSEXT_ID and SYSEXT_VERSION_ID when readers need to identify the extension itself. The unprefixed fields describe the base OS compatibility; the prefixed fields avoid confusing that with the extension's name and version.
3. Create a metadata file in a build tree
Make a temporary directory and place a small, additive file under the extension's /usr tree. This command changes only /tmp and needs no elevated privileges:
$ work=$(mktemp -d)
$ image="$work/labtools"
$ mkdir -p "$image/usr/lib/extension-release.d" "$image/usr/local/lib/labtools"
$ cat > "$image/usr/lib/extension-release.d/extension-release.labtools" <<'EOF'
ID=ubuntu
VERSION_ID=24.04
SYSEXT_ID=labtools
SYSEXT_VERSION_ID=1.0
ARCHITECTURE=x86-64
EOF
$ cat > "$image/usr/local/lib/labtools/README" <<'EOF'
Files supplied by the labtools system extension.
EOF
Replace the example compatibility and architecture values with those from the target host. The field syntax permits comments and blank lines, but values containing spaces or shell punctuation must be quoted. Do not concatenate separately quoted fragments, and do not write $VERSION_ID expecting systemd to expand it.
ARCHITECTURE is optional. If present, it must match the kernel architecture using systemd's ConditionArchitecture= identifiers, unless it is _any. On this host, x86-64 is the relevant identifier; confirm a different target with uname -m and the systemd architecture names before publishing.
4. Inspect the result before packaging
Check the exact path, filename and content. This catches the most common error, putting the file at /etc or omitting the image suffix:
$ test -f "$image/usr/lib/extension-release.d/extension-release.labtools" && echo metadata-present
metadata-present
$ sed -n '1,20p' "$image/usr/lib/extension-release.d/extension-release.labtools"
ID=ubuntu
VERSION_ID=24.04
SYSEXT_ID=labtools
SYSEXT_VERSION_ID=1.0
ARCHITECTURE=x86-64
Keep the extension tree focused on /usr and /opt for a system extension. Files placed under /etc or /var are not merged by systemd-sysext. Do not add /usr/lib/os-release: the systemd manual warns that merging one would override the host's OS identity. If the goal is an /etc overlay, use the related confext mechanism and its CONFEXT_LEVEL matching rules instead.
5. Test discovery without merging the image
Before installing anything below /var/lib/extensions, use a root-relative test where supported by the installed tool:
$ mkdir -p "$work/etc/extensions"
$ cp -a "$image" "$work/etc/extensions/labtools"
$ systemd-sysext --root="$work" list
NAME TYPE PATH TIME
labtools directory /tmp/ROOT/etc/extensions/labtools DATE
The listing shows the name and type; its timestamp is variable. The path and date in the example are abbreviated placeholders for the temporary root and current time. This is a safe check that the command discovers the selected directory without merging it. For a real deployment, place the finished directory in the system search path, then run systemd-sysext list before considering merge. Discovery and activation are separate actions.
Do not run systemd-sysext merge as a casual validation step. It establishes an overlay and may require elevated privileges. If an already-merged test must be stopped, run sudo systemd-sysext unmerge, then verify with systemd-sysext status. Unmerging removes the extension's view from /usr and /opt; it does not delete the image directory.
6. Diagnose a rejection
Work through the checks in this order:
- Filename:
extension-release.NAMEmust use the exact containing image name after the suffix is removed. - Identity:
IDmust match the host, unless the extension uses the documented_anyvalue. - Compatibility level: when the extension has
SYSEXT_LEVEL, it must match the host. If it does not, systemd falls back to matchingVERSION_ID. - Architecture: an explicit value must match the kernel, unless it is
_any. - Syntax: quote values with spaces or special characters, use UTF-8, avoid duplicate keys and do not add shell commands.
Only after those checks should you inspect image permissions, filesystem support or an image policy failure. --force bypasses version compatibility; that weakens the safety check and is not a repair. Do not use it for a production deployment unless you have independently established ABI and API compatibility.
Done means
- The release file is under
/usr/lib/extension-release.dinside the image. - Its suffix exactly matches the deployed image name.
ID,SYSEXT_LEVELorVERSION_ID, and optional architecture data match the target host.- Extension identity uses
SYSEXT_fields and does not replace the host'sos-release. - You inspected discovery without merging, and you have
unmergeavailable before any elevated activation.