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.