Model or dataset
systemd/mkosi avatar
systemd/mkosi

mkosi: a package manager wrapper that lays out disk images

💽 Build Bespoke OS Images

2,039 stars459 forksPythonLicense varies

At a glance

What is it?
mkosi wraps dnf, apt, pacman and zypper to produce customized disk images, and it also plugs into systemd kernel-install as a UKI or addon builder. It fits integrators who already have a host package manager, and its own test and packaging layout show where the rough edges sit.
Who is it for?
mkosi earns its keep when one disk image has to be rebuilt for many machines, when the build host already carries dnf, apt, pacman or zypper, and when systemd is already the init system. It is the wrong tool for a single throwaway VM and for hosts that cannot run the package manager it wraps.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A wrapper that drives four different package managers

mkosi describes itself as a fancy wrapper around `dnf --installroot`, `apt`, `pacman` and `zypper` that generates customized disk images. The wrapper framing is the part worth holding onto: there is no package database inside mkosi, no embedded kernel, and no image format of its own to learn. What you configure is a package manager run plus a disk layout applied to the tree it produces. The repository topic tags say which layouts matter in practice, naming `luks`, `verity`, `gpt`, `efi`, `squashfs`, `btrfs-subvolume` and `initramfs`.

That puts mkosi in a narrow place. Teams that own a fleet and need the same image rebuilt across several boot or hardware variants get real value from it. Someone who only wants a package set extracted into a directory already has `dnf --installroot` and can skip all of this.

The entry points into the option set are the man page at `mkosi/resources/man/mkosi.1.md` and the `mkosi documentation` command, and the project sends newcomers to the `EXAMPLES` section for a first build. Those options do not appear in the README, which works as a map rather than a manual.

The version floor pushes you off your distribution package

The distribution package carries a floor: the packaged build has to be at least mkosi v16 or newer, and `mkosi --version` is the check. Packages for Debian, Ubuntu, Fedora and SUSE are built from latest main and published as repositories on OBS, so a distribution that ships an older mkosi will lag, and the project points you at alternative methods instead. Any of these works on a host with Python 3.9 or newer, since pyproject.toml sets `requires-python = ">=3.9"`.

The shim is the lightest route. Running from a clone needs no install step beyond a symlink, and `MKOSI_INTERPRETER` selects the Python interpreter that executes the code. You may have to add `~/.local/bin` to your PATH yourself.

bash
git clone https://github.com/systemd/mkosi
ln -s $PWD/mkosi/bin/mkosi ~/.local/bin/mkosi
mkosi --version

The same trick applies to the companion tools, which live beside the main shim as `bin/mkosi-addon`, `bin/mkosi-initrd` and `bin/mkosi-sandbox`.

bash
pipx install git+https://github.com/systemd/mkosi.git
mkosi --version

That pipx form drops a binary into `~/.local/bin` inside a virtual environment, and the project describes it as equivalent to creating a venv by hand. The zipapp route packages the whole tree as one file you can deploy anywhere on your PATH, leaving a `builddir/mkosi` binary behind.

bash
git clone https://github.com/systemd/mkosi
cd mkosi
tools/generate-zipapp.sh
builddir/mkosi --version

One warning belongs with these routes: `python3 -m mkosi` works when mkosi is not installed as a zipapp, but the installation notes state that the python module exists solely for the mkosi binary and is not a public API.

Four console scripts and a wheel full of repart assets

Installing the project gives you more than one binary. pyproject.toml declares four console scripts, `mkosi`, `mkosi-initrd`, `mkosi-sandbox` and `mkosi-addon`, mapped to `mkosi.__main__:main`, `mkosi.initrd:main`, `mkosi.sandbox:main` and `mkosi.addon:main`. The module list underneath is short, `mkosi`, `mkosi.distribution`, `mkosi.installer` and `mkosi.resources`, so most of the shipped weight sits in resources rather than in Python code.

Those resources are the interesting part. Package data covers `completion.*`, `man/*`, `mkosi-addon/**/*`, `mkosi-initrd/**/*`, `mkosi-obs/**/*`, `mkosi-tools/**/*`, `mkosi-vm/**/*`, `repart/**/*`, `tmpfiles.d/*` and `pandoc/*`. Repart assets and tmpfiles.d units travel with the install rather than arriving as a separate package, which tells you the partition layout stage is part of mkosi itself and not something you provision beside it.

One dependency sits outside that default set. The `bootable` extra installs `pefile >= 2021.9.3`, and none of the documented install methods ask for it.

toml
[project.optional-dependencies]
bootable = [
  "pefile >= 2021.9.3",
]

The project header reports no licence at all, while pyproject.toml declares `license = {text = "LGPL-2.1-or-later"}`, so the identifier for compliance work has to be read out of the build configuration rather than taken from the header. The same file declares `setuptools-scm` in the build requirements and pins `version = "27.1"` as a literal string at the same time, so the version number is not derived from a tag. Tag history runs v26 on 2025-12-17, v27 on 2026-08-27 and v27.1 on 2026-09-28, and the last push on record is 2026-10-06.

Two kernel-install plugins, and one silent precondition

mkosi doubles as a systemd kernel-install plugin, and the two plugins do opposite jobs. The UKI plugin is installed by putting `kernel-install/50-mkosi.install` into `/usr/lib/kernel/install.d`. Distro defaults for the initrd come from `/usr/lib/mkosi-initrd`, and site customizations go in `/etc/mkosi-initrd`. Turning it on means writing `initrd_generator=mkosi-initrd` and `layout=uki` into `/usr/lib/kernel/install.conf` or `/etc/kernel/install.conf`, after which a full self-contained UKI is built and installed.

The addon plugin, `kernel-install/51-mkosi-addon.install`, covers the reverse case: enhancing a UKI the vendor already ships with local-only modifications. Its configuration is read from `/usr/lib/mkosi-addon`, with `/etc/mkosi-addon` and `/run/mkosi-addon` for local overrides.

Here is the detail a skim misses. Copying the plugin file into place does not start the addon plugin. Unless at least one of the last two directories is present, the plugin will not operate, and nothing in the kernel-install output explains why your addon never made it into the UKI. The same documentation also says to use only one of the two plugins at a given time, so installing both plugin files and expecting a merged initrd generator is not a supported configuration.

mkosi builds its own images with mkosi

The project dogfoods itself. The root of the tree carries mkosi's own configuration: `mkosi.conf`, `mkosi.conf.d/`, `mkosi.credentials/`, `mkosi.extra/`, `mkosi.profiles/` and `mkosi.tools.conf/`, next to `bin/`, `tools/` and `docs/`. A directory named `mkosi.credentials/` sits in plain sight there, and the README never says what belongs in it, so anyone auditing the tree has to read the configuration code to learn whether it holds secrets or just build settings.

Development runs inside that arrangement rather than on the host. Linters such as `ruff` and `mypy` are run inside `mkosi box`, from inside `mkosi.tools/`, so the toolchain is the same everywhere and the consistency comes from the image rather than from a lock file. Build that tools tree first:

bash
bin/mkosi -f box -- true

When a tool that mkosi runs inside its sandbox fails, `docs/debugging.md` describes how to replay the command by hand instead of guessing from a log line.

Most of the test suite is skipped by default

mkosi's tests run through pytest and cover linting, type checking and unit tests in one go. Because the linters execute inside `mkosi box`, a plain pytest run is a container build in disguise, which surprises a contributor expecting a quick local check.

bash
bin/mkosi box -- pytest

Two markers hide most of the rest. Installation tests, covering venv, editable and zipapp installs, are marked with the `install` marker and are skipped by default, because they create virtual environments and install packages from the network.

bash
bin/mkosi box -- pytest -m install

Integration tests are worse. They are marked `integration`, skipped by default, need a tools tree and an image to be built first, and require KVM, so without `/dev/kvm` they are skipped or very slow. `tools/integration-test-setup.sh` writes a local configuration for the given image and tools tree distribution and builds both.

sh
tools/integration-test-setup.sh $DISTRIBUTION $TOOLS_TREE_DISTRIBUTION

The example in the project runs that setup for an Arch image against a Fedora tools tree, then runs a single bootloader test from the boot suite.

sh
bin/mkosi box -- pytest -m integration --distribution arch --capture=no --verbose \
    'tests/test_boot.py::test_bootloader[systemd-boot]'

Read together, the markers mean a plain pytest invocation on a laptop checks typing, linting and units, and nothing about whether the produced image boots. Boot coverage lives entirely behind hardware with KVM.

Building the same image without mkosi

The alternative is not another wrapper. It is doing both halves by hand: `dnf --installroot` on Fedora, the apt, pacman or zypper equivalents elsewhere, then a partition layout applied to that tree with systemd-repart. mkosi automates exactly that pair, and the repart assets packaged under `mkosi.resources` are the trace of that dependency. The second difference is boot. Rather than replacing the initrd pipeline, mkosi plugs into systemd kernel-install through `kernel-install/50-mkosi.install` and `kernel-install/51-mkosi-addon.install`, so an image built with mkosi still boots through the bootloader and initrd machinery you already run.

The trade is control against repeatability. Writing both halves by hand gives you exact say over every package and every partition, and costs you a script encoding a sequence of steps nobody can diff when it breaks. A mkosi config concentrates that sequence in files, but you inherit the behaviour of whichever package manager and repart version the build host happens to carry. The project links a 2017 LWN story and a 0pointer.net re-introduction under References, and ships no comparison or migration notes for existing hand-rolled recipes, so anyone moving an image over has to plan the switch themselves.

Editorial conclusion

mkosi earns its keep when one disk image has to be rebuilt for many machines, when the build host already carries dnf, apt, pacman or zypper, and when systemd is already the init system. It is the wrong tool for a single throwaway VM and for hosts that cannot run the package manager it wraps. Before the first build, run mkosi --version and confirm you have v16 or newer, pick one of the two kernel-install plugins rather than both, and check that the directory your chosen plugin reads exists, because the addon plugin stays silent when it does not.

Frequently asked questions

What minimum mkosi version does my distribution package need?

The installation notes say a packaged install has to be at least mkosi v16 or newer, and `mkosi --version` is the check. If the distribution packages an older build, the project points you at the git shim, pipx or the zipapp instead.

How do I run mkosi without installing it through my package manager?

Clone the repository and symlink the shim at `bin/mkosi` into `~/.local/bin`, setting `MKOSI_INTERPRETER` to pick the Python interpreter. `pipx install git+https://github.com/systemd/mkosi.git` does the same thing in a virtual environment, and `tools/generate-zipapp.sh` leaves a single-file binary in `builddir/`.

Why does the mkosi addon kernel-install plugin do nothing after I install it?

Installing `kernel-install/51-mkosi-addon.install` into `/usr/lib/kernel/install.d` is not enough on its own. Unless at least one of `/etc/mkosi-addon` or `/run/mkosi-addon` is present, the plugin will not operate, and the project also advises using only one of the two plugins at a given time.

Can I import the mkosi Python module in my own tooling?

The installation notes state that the python module exists solely for the usage of the mkosi binary and is not to be considered a public API. `python3 -m mkosi` is a convenience for running mkosi outside a zipapp, not a supported interface.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. systemd/mkosi on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/systemd-mkosi.svg)](https://hysenlabs.com/projects/systemd-mkosi)

Was this useful?