sdme 0.2.5

Lightweight systemd-nspawn containers with overlayfs
Documentation

sdme

This is more of a manifesto than a project.

The tool in here, the systemd machine editor (sdme), manages containers built on systemd-nspawn and overlayfs. It cooperates with machined, D-Bus, and the broader systemd ecosystem.

Learn about the tool from the usage documentation. In there you will find a practical guide for installing and using sdme as a beginner or experienced user.

Why?

I wrote exactly zero lines of this code. Not one. Every line was written by AI, directed by me.

It's 2026 and I've been a systems engineer for a while. I wanted to understand what vibe coding actually looks like on a real project. Because of experience and situation, I decided to play with systemd booted containers as first class citizens on Linux.

Not that they aren't. But with sdme I am now able to spin up a systemd-nspawn container running the distro of my choice, imported from a directory or a URL, or an OCI registry, and I can join this container and use it like a normal machine.

If you have infinite time and look through the architecture you'll see where I started: really wanted to mimic what virtme-ng does, cloning the host's root filesystem with an overlayfs on / and booting up a container. It's what you get by default just running sudo sdme new and from there I went... far.

Once I got the lifecycle of the clone rootfs working, I thought it would be natural to allow using any other rootfs, not just my own. Importing a directory meant trying to recognise something like /etc/os-release, and checking for the existence of systemd and dbus inside so it would be more or less certain to be able to boot. That became the sdme fs command, primarily for importing debootstrap'ed rootfs. On a digression I made it also import QCOW2 disk images. I find it crazy how assisted coding can do these things so fast: look into the partition table, infer where the rootfs is, run the checks, and import it. Now we can import directories and cloud images. There is no cloud-init support, but you can configure things after boot.

From there it felt natural to add a build system. sdme fs build takes a stripped-down Dockerfile-like config with FROM, COPY, and RUN directives, spins up a temporary container from an existing rootfs, executes the operations inside it, and captures the merged overlayfs as a new rootfs. This means you can compose custom rootfs on top of any imported base: install packages, drop in config files, run setup scripts, and get a reusable snapshot out the other end without touching the original.

These containers are quite cool because you can join and install packages and common services from the existing distros, it all works well. But they are also boring for my learning experience. Do what, now? Sit and run something inside? Meh.

Actually, half true. Convenience was my initial driver for supporting OCI containers. We all know off the top of our head docker.io/ubuntu but I bet you wouldn't tell me the qcow2 URL for any distro outside your own maybe. Using debootstrap is fine but I wanted to ensure the systemd container ecosystem can be enriched by ecosystems that already exist but are not easily accessible from systemd-nspawn.

So, enabling to download from an OCI registry and strip out all but the rootfs was the first step. The next step was distinguishing base OS images (Debian, Ubuntu) from app images (nginx, redis, mysql). In sdme, base OS imports work like directory imports: detect the distro, install systemd and D-Bus, done. App images are different: sdme creates a systemd container with one of your existing rootfs and places the OCI app inside it as a chroot with its own network namespace. A systemd service manages the lifecycle. It was tricky to get right, but it works.

Once those were working, we needed pods. A pod is a shared network namespace. You can place the entire nspawn container in a pod, or place only the OCI app in one, sharing it across apps in other containers on the same host. This is useful for composing control-plane instrumentation alongside application workloads on separate loopbacks.

The last stage of the journey was security hardening. Spent time adding the most relevant security options in an attempt to bring sdme containers on par with Docker and Podman. The models are quite different and I think the end result is very acceptable.

My takeaways from this journey in creating sdme:

  • I'm totally sold on vibe coding; I can do kettlebells while vibing
  • It's a long and iterative process, there are no magic prompts
  • It takes significant time to define, review, and refine your craft
  • The choice of language matters less than domain knowledge; I'm new to Rust
  • The verification tasks are insanely useful and AI is really good at this

The question I had to answer for myself was: what happens to two decades of systems knowledge when AI can write code faster than I can type? Does the experience become obsolete, or does it become a multiplier?

My bet is on multiplier. Deep domain knowledge becomes a multiplier when paired with AI's analytical capability, particularly for verification: building the loop of test, review, fix, and converge.