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-vethcreates a virtual ethernet pair, the host seesve-web1, the container seeshost0. The host runs a small DHCP server inside itssystemd-networkdfor the container. - Bridge:
--network-bridge=br0attaches 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 (
mkosion 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.