Custom image using mkosi
Note
The following instruction is for building a Debian bookworm image. When building other distributions, the instruction will not work without modifications.
Note
The build was done on a WSL2 installation of Ubuntu 24.04.
mkosi
A re-introduction to mkosi -- A Tool for Generating OS Images
Install
Note
The guide was developed when running as root
Follow the instructions at Installation to install mkosi.
This guide was tested using the following method:
apt install -y pipx git
pipx install git+https://github.com/systemd/mkosi.git
export PATH=$PATH:/root/.local/bin
For cross-build support of arm64 on x86-64 architecture and support for Debian repositories the following packages must be installed
apt install -y qemu-user-static debian-archive-keyring
For testing using systemd-nspawn:
apt install -y systemd-container
Documentation
For further instructions, see man mkosi
Configuration
Create the following files and directory structures in the same directory path.
The configuration below is for building Debian Bookworm for arm64 and generating
an rootfs.tar.xz output that can be used to create an LXC container.
The package list can be modified to include the packages needed or to e.g.
change the editor from nano to vim. The only strictly necessary package is
init, without it the container will not run on the Edge device.
mkosi.conf
[Distribution]
Distribution=debian
Release=bookworm
Architecture=arm64
[Output]
Output=rootfs
Format=tar
CompressOutput=xz
[Content]
# Cleanup temporary shadow paths
RemoveFiles=/etc/*-
Packages=
apt
init
systemd-resolved
dbus
iproute2
iputils-ping
netbase
net-tools
openssh-client
procps
ca-certificates
nano
BuildPackages=gcc,libc6-dev
The BuildPackages are only used when building the test application and will
not be included in the final image.
The following is a post-installation script that runs inside the chroot
environment during the image build process. It enables custom modifications to
the final root filesystem. To ensure it can be executed, set the appropriate
permissions with chmod +x mkosi.postinst.chroot.
mkosi.postinst.chroot
#!/bin/bash
set -eux
# The firstboot services hangs, disable for now
systemctl mask systemd-firstboot.service
# LXC template support for hostname
echo "LXC_NAME" > /etc/hostname
sed -i '1i127.0.1.1 LXC_NAME' /etc/hosts
The following file is used to setup the root password, the defined content will
result in a passwordless root login. Set the file permissions so that only the
owner can access it, using chmod 600 mkosi.rootpw.
mkosi.rootpw
hashed:
Files in mkosi.extra directory are copied into the image after distribution is
installed.
Configuration for systemd management of eth0 and enable DHCP client.
mkosi.extra/etc/systemd/network/eth0.network
[Match]
Name=eth0
[Network]
DHCP=true
[DHCPv4]
UseDomains=true
[DHCP]
ClientIdentifier=mac
Add Debian sources for apt.
mkosi.extra/etc/apt/sources.list
deb http://deb.debian.org/debian bookworm main contribdeb
deb http://deb.debian.org/debian bookworm-updates main contribdeb
deb http://deb.debian.org/debian-security/ bookworm-security main contrib
Host journal
Note
This is an Edge specific configuration and is not meant for general mkosi configuration.
This is an example of how to automatically symlink the host systemd journal with the containers journal, as described here, using systemd.
mkosi.extra/usr/bin/symlink-host-journal
#!/bin/sh -eu
JOURNAL_PATH=/var/log/journal
for file in "$JOURNAL_PATH"/*; do
if [ -L "$file" ]; then
symlink="$file"
target=$(realpath "$symlink")
if [ ! -e "$target" ]; then
echo "Remove symlink: $symlink"
rm -rf "$symlink"
fi
fi
done
for file in /host/journal/*; do
base=$(basename "$file")
target="$JOURNAL_PATH/$base"
if [ ! -e "$target" ]; then
ln -s "$file" "$target"
echo "Created symlink $target -> $file"
fi
done
This file is the script that is run at startup, it removes the old symlink if it
no longer exists and adds the new one if it does not exist. Set the file
permissions so that it can be executed,
chmod 750 mkosi.extra/usr/bin/symlink-host-journal.
Note
Depending on how the systemd-journal is configured the path to the journal can
change. If it is configured to be persistent /var/log/journal is used and
/run/log/journal when it is configured as volatile. Change the
JOURNAL_PATH varable according to the systemd-journal configuration.
To add the script as a service that is run on startup of the container the following configuration needs to be added.
mkosi.extra/etc/systemd/system/symlink-host-journal.service
[Unit]
Description=Symlink the host journal to the container journal directory
[Service]
Type=oneshot
ExecStart=/usr/bin/symlink-host-journal
[Install]
WantedBy=default.target
Build the image
Run commands from the same directory as the configuration files.
mkosi
The result of the build operation is a rootfs.tar.xz file that will work with
the LXC image meta.tar.xz.
Test the image
The image can be tested using lxc:
lxc-create hostname -t local -- --fstree rootfs.tar.xz --meta meta.tar.xz
lxc-start hostname
lxc-attach hostname
The image can be also be tested using systemd-nspawn:
# Unpack to directory
mkdir -p image && tar xJf rootfs.tar.xz -C image
# Start using systemd init
systemd-nspawn -D image --resolv-conf=bind-host --register=no --keep-unit --boot
# or just enter a shell
systemd-nspawn -D image --resolv-conf=bind-host --register=no --keep-unit
--register=no and --keep-unit is currently needed to be able to run the
systemd-nspawn command multiple times.
During development iterations the tar steps can be skipped.
# Build into directory
mkosi --format=directory
systemd-nspawn -D rootfs --resolv-conf=bind-host --register=no --keep-unit --boot
Warning
The testing might give unexpected results when relying on the qemu
emulation support. The recommendation is to do the testing in a native
environment.
Higher efficiency and better development host compatibility can be achieved with the native build.
mkosi --format=directory --architecture x86-64
# The container will now run without emulation
systemd-nspawn -D rootfs --resolv-conf=bind-host --register=no --keep-unit --boot
Building custom application
This section will show a simple example of how to include as custom built application into the image rootfs.
Preparation
Cache
Setup build cache directories
mkdir mkosi.cache mkosi.builddir
Source
hello.c
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
Build and install script
mkosi.build.chroot
#!/bin/bash
set -eux
# Build test application
cd $BUILDDIR
gcc $SRCDIR/hello.c -o hello
# Install test application into /root
mkdir -p $DESTDIR/root/
cp hello $DESTDIR/root/
Build and test
The incremental build is enabled to take advantage of caching and the force
flag to make it possible to rebuild even if a previous image already exists. If
possible, build and test native first for better efficiency.
mkosi --format=directory --incremental yes --architecture x86-64 --force
# The container run without emulation
systemd-nspawn -D rootfs --resolv-conf=bind-host --register=no --keep-unit --boot
The final build is a cross build resulting in a LXC compatible rootfs.
mkosi