Install

On Debian/Ubuntu the tool is in the systemd-container package:

sudo apt install systemd-container debootstrap

On Fedora/Arch/openSUSE: included in the base systemd or available as systemd-container. Verify:

systemd-nspawn --version
machinectl
ls /var/lib/machines/

Bootstrap a Debian container

Use debootstrap to populate a directory with a Debian root filesystem:

sudo debootstrap bookworm /var/lib/machines/web1 http://deb.debian.org/debian/

# Set a root password (otherwise the container has no way to log in)
sudo systemd-nspawn -D /var/lib/machines/web1 passwd

# Drop a hostname
echo web1 | sudo tee /var/lib/machines/web1/etc/hostname

The directory now holds a complete Debian userland. The host kernel is shared (this is a container, not a VM).

Boot the container

sudo systemd-nspawn -bD /var/lib/machines/web1

The container boots its own systemd, ttys, journal — from inside, it looks identical to a regular Debian VM. Log in as root with the password set above. Ctrl-]]] (three close-brackets in quick succession) is the escape sequence to detach.

Running it as a managed service

systemd ships systemd-nspawn@.service — a template unit that boots any container in /var/lib/machines/ by name:

sudo systemctl enable --now systemd-nspawn@web1
sudo systemctl status systemd-nspawn@web1
sudo machinectl list

# Drop into a login shell on the running container
sudo machinectl login web1

# Open a non-login root shell
sudo machinectl shell web1

The container's journal merges into the host's:

sudo journalctl -M web1 -u nginx
sudo journalctl -M web1 -f

Same for service control:

sudo systemctl -M web1 status nginx
sudo systemctl -M web1 restart nginx

Networking

Three options, picked via overrides to the systemd-nspawn@web1.service unit:

  • Shared host networking (default for ad-hoc systemd-nspawn): the container uses the host's network namespace directly. Simplest, no isolation.
  • Private network with a veth pair (default for systemd-nspawn@.service): --network-veth creates a virtual ethernet pair, the host sees ve-web1, the container sees host0. The host runs a small DHCP server inside its systemd-networkd for the container.
  • Bridge: --network-bridge=br0 attaches the container to an existing bridge so it gets an address on the LAN.

Per-container override:

sudo systemctl edit systemd-nspawn@web1
# Add:
[Service]
ExecStart=
ExecStart=systemd-nspawn --quiet --keep-unit --boot \
    --link-journal=try-guest --machine=web1 \
    --network-bridge=br0 -i web1

Resource limits

Because nspawn containers are first-class systemd units, all the resource-control directives (MemoryMax, CPUWeight, IOWeight, TasksMax) work via drop-in:

# /etc/systemd/system/systemd-nspawn@web1.service.d/limits.conf
[Service]
MemoryMax=2G
CPUWeight=80
TasksMax=4096

Then systemctl daemon-reload + restart. Limits are enforced by cgroup v2, same as any other systemd service.

btrfs subvolumes for fast clones

If /var/lib/machines is on btrfs, machinectl clone uses subvolume snapshots: a new container is a copy-on-write of the original, near-zero disk, ready in milliseconds.

sudo machinectl clone web1 web1-test
sudo systemctl start systemd-nspawn@web1-test

For ext4, clones are full file copies and considerably slower.

Image management with machinectl

The machinectl pull-* family pulls prebuilt images from hub.nspawn.org or a custom URL:

sudo machinectl pull-tar https://hub.nspawn.org/storage/debian/bookworm/raw/image.tar.xz debian-bookworm
sudo machinectl list-images
sudo systemctl start systemd-nspawn@debian-bookworm

Or build an image once with debootstrap, then tar it for distribution. Other servers can machinectl import-tar it.

Portable services

For "I want to ship one daemon as a unit, not a full distro," systemd has portable services — a tarball or raw image containing a minimal root filesystem + unit files. The host attaches the image and runs the units; the image stays read-only and self-contained.

sudo portablectl attach ./my-app.raw
sudo systemctl start my-app
sudo portablectl detach my-app

Building portable images is its own topic (mkosi is the official tool); the using side is one command.

Where nspawn fits, and where it doesn't

Good fits:

  • Running one Debian/Fedora/Arch userland on a host that's running a different distro, to use software unavailable on the host.
  • Cleanly isolating long-running daemons that don't want to be containerized in the Docker sense — databases, custom forks, niche servers.
  • Building / testing software on multiple distros from one host (mkosi on top of nspawn is a great cross-distro CI sandbox).
  • The kind of "I just want a sandboxed copy of Debian" use case that LXC also fills, but without LXC's separate daemon and tooling.

Less good:

  • Application packaging in the Docker sense — no layered image format, no registry ecosystem, no Dockerfile.
  • Scheduling across hosts — nspawn is single-host. Kubernetes / Nomad / Docker Swarm have no idea what it is.
  • OCI-runtime workflows — podman and Quadlet (see that tutorial) are the right tool when you want OCI images and systemd supervision.

nspawn is the thing to reach for when the question is "this should look like its own Debian, but I don't want a VM and I don't want OCI." For the rest, the existing container ecosystem is fine.