The mental model

nspawn is closer to LXC system containers than to Docker app containers. The container runs a full init (usually systemd), has its own users, runs multiple processes, and looks like a small VM. The host kernel is shared (it's still a container, not a VM).

Install (it's probably already there)

# Debian / Ubuntu
sudo apt install systemd-container

# Fedora / RHEL — already in systemd

# Verify
systemd-nspawn --version
machinectl --version

Bootstrap a Debian container from scratch

# Use debootstrap (or dnf, pacstrap, etc. for other distros)
sudo apt install debootstrap

sudo mkdir -p /var/lib/machines/dev-debian
sudo debootstrap --variant=minbase bookworm /var/lib/machines/dev-debian \
    http://deb.debian.org/debian

# Set a root password (otherwise you can't log in)
sudo systemd-nspawn -D /var/lib/machines/dev-debian passwd
# (prompts for new root password)

The container's filesystem lives under /var/lib/machines/dev-debian/ on the host. Plain files; no image format.

Boot it

# One-shot run (foreground, gets a shell)
sudo systemd-nspawn -D /var/lib/machines/dev-debian

# Boot the container's systemd init (background, networking, etc.)
sudo systemd-nspawn -b -D /var/lib/machines/dev-debian --network-veth

# Or as a persistent machine via machinectl
sudo machinectl start dev-debian
sudo machinectl list
sudo machinectl shell dev-debian

When booted with -b, the container's systemd runs as PID 1; all the usual systemd things work inside (timers, units, journal, networkd).

Use a pre-built image via systemd-importd

# Import a Debian root tarball as a machine
sudo machinectl pull-tar https://hub.nspawn.org/storage/debian/bookworm/tar/image.tar.xz debian-bookworm

# Or pull a "raw" image
sudo machinectl pull-raw https://example.com/fedora-cloud.raw fedora

# List images
sudo machinectl list-images
sudo machinectl image-status debian-bookworm

Imported images live under /var/lib/machines/; can be cloned with machinectl clone to spawn instances.

The .nspawn config file

Per-machine config under /etc/systemd/nspawn/<name>.nspawn:

# /etc/systemd/nspawn/dev-debian.nspawn
[Exec]
Boot=yes
PrivateUsers=yes
Capability=CAP_NET_ADMIN

[Network]
VirtualEthernet=yes
Bridge=br0           # bind to a host bridge; container gets a real LAN IP

[Files]
Bind=/srv/shared
BindReadOnly=/etc/ssl/certs

[Service]
SyslogIdentifier=dev-debian

Persistent across machinectl start; no need to remember CLI flags.

Boot at host startup

# Enable the auto-start unit for nspawn machines
sudo systemctl enable systemd-nspawn@dev-debian
sudo systemctl start systemd-nspawn@dev-debian

# Check
sudo systemctl status systemd-nspawn@dev-debian
sudo machinectl status dev-debian

Same systemd unit semantics as any other service: dependencies, restart policy, resource limits, sandboxing.

Network configurations

  • Default (no flag) — shares the host's network namespace. Container sees host interfaces; risk of port conflicts.
  • --network-veth — veth pair to a host-managed bridge (default: vz-<name> via systemd-networkd). NATted to outside.
  • --network-bridge=br0 — attach veth to a specific host bridge. Container gets a real LAN IP if the bridge is LAN-facing.
  • --network-macvlan=eth0 — macvlan interface; container gets its own MAC + LAN IP, no bridge needed.
  • --private-network — isolated; no external network at all.

Use machinectl like virsh / docker

# List running
machinectl list

# Stop
machinectl poweroff dev-debian
machinectl terminate dev-debian      # force kill

# Reboot
machinectl reboot dev-debian

# Get a shell as a specific user
machinectl shell amir@dev-debian /bin/bash

# Copy files between host and machine
machinectl copy-to dev-debian /etc/hostname /etc/host-hostname
machinectl copy-from dev-debian /etc/os-release /tmp/container-os

# Open a journal session (view the container's journal from the host)
journalctl -M dev-debian -f

Where systemd-nspawn shines

  • Reproducing a different distro's environment. Run Debian on Arch host (or vice versa) for testing software builds across distros.
  • Running legacy software with old library dependencies. Pin a container to a CentOS 7 userspace forever, no host system changes.
  • Lightweight VMs without QEMU overhead. Boot a full systemd init; access via SSH; manage with systemctl.
  • Privileged workloads. nspawn supports running with the host's UID 0 (less isolated) for cases that need real root.

nspawn vs Docker / Podman

  • Docker / Podman — application containers (one process, no init, OCI image format). Better for packaging and shipping apps.
  • systemd-nspawn — system containers (full distro userspace, multiple processes, systemd init). Better for "I want a small distro that's not the host."
  • Different use cases; both worth knowing. nspawn is invisible (already there); Docker/Podman dominate app deployment.

nspawn vs Incus / LXC

  • Incus / LXD (see that tutorial) — richer management story: profiles, snapshots, clustering, GUI. The right pick for managing a fleet of containers across multiple hosts.
  • nspawn — ad-hoc per-host containers; smaller management surface. Good for "run one or two containers on this box without installing anything."

Limitations worth knowing

  • Not a security boundary for hostile workloads — less battle-tested isolation than Docker / Podman; for serious tenant isolation, use a VM.
  • Sharing the host kernel means kernel-version-dependent workloads can't be isolated by container alone.
  • Networking is less ergonomic than Docker's auto-NAT story; manual bridge / veth configuration needed for non-trivial cases.
  • No image-based distribution by default (no Hub of nspawn images comparable to Docker Hub).

For "I have systemd, I want a container, I don't want to install Docker," nspawn is the right size in 2026.