Skip to content
LinuxHow-To Published Updated 4 min readViews unavailable

How to Use systemd-nspawn Containers on Linux

Creating and running a lightweight systemd-nspawn container — useful for isolated testing environments without a full container runtime's overhead.

systemd-nspawn provides namespace-based container isolation built directly into systemd — using the same namespace primitives that underlie Docker and Podman, without needing a separate container runtime installed.

Step 1: create a root filesystem for the container

sudo dnf --releasever=39 --installroot=/var/lib/machines/mycontainer install systemd passwd dnf fedora-release vim-minimal -y

Or, on Debian-based systems:

sudo debootstrap stable /var/lib/machines/mycontainer http://deb.debian.org/debian

Either produces a minimal, bootable root filesystem in the standard /var/lib/machines/ location machinectl expects.

Step 2: boot the container

sudo systemd-nspawn -D /var/lib/machines/mycontainer -b

-b boots the container’s own init system (systemd) rather than just running a single command inside it — the container behaves like a lightweight, independently-booting system.

Step 3: manage it with machinectl instead of raw nspawn commands

sudo machinectl start mycontainer
sudo machinectl shell mycontainer
sudo machinectl stop mycontainer

machinectl is the higher-level tool for managing nspawn containers as persistent, named machines — closer to docker start/exec/stop in spirit.

Step 4: list running containers

machinectl list

Step 5: set resource limits via systemd’s own resource control

systemctl set-property machine-mycontainer.scope MemoryMax=512M CPUQuota=50%

Because nspawn containers run as systemd scopes, the same resource-control mechanisms covered in cgroups apply directly — no separate resource-limiting subsystem needed.

Step 6: share a directory between host and container

sudo systemd-nspawn -D /var/lib/machines/mycontainer --bind=/host/data:/data -b

Step 7: enable the container to start automatically at boot

sudo machinectl enable mycontainer

Step 8: remove a container you no longer need

sudo machinectl remove mycontainer

Why nspawn is worth knowing alongside Docker/Podman, not instead of them

nspawn containers boot a full init system and behave much more like lightweight VMs than application containers — genuinely useful for testing a full distribution environment, building packages in isolation, or running a legacy service that expects a real init system, scenarios where Docker’s single-process-per-container model is a worse fit. It’s a different tool for a different job, not a competing choice for the container-orchestration workloads Docker and Podman are built around.

Step 9: run a fully throwaway container for one-off testing

sudo systemd-nspawn -D /var/lib/machines/mycontainer --ephemeral -b

--ephemeral boots from a temporary copy-on-write snapshot of the specified root filesystem rather than the filesystem itself, and discards every change automatically the moment the container stops — the base image is never modified no matter what happens inside the running container. This is genuinely useful for reproducible testing (verifying an install script against a known-clean base every single run) or for confidently trying something destructive inside the container without any cleanup step needed afterward, since there’s nothing left to clean up once it exits.

Step 10: give a container its own isolated virtual network interface

sudo systemd-nspawn -D /var/lib/machines/mycontainer --network-veth -b

By default, nspawn containers share the host’s network namespace entirely — --network-veth instead creates a veth pair (the same mechanism network namespace testing uses) connecting the container to the host through a private virtual link, giving the container its own network stack rather than direct visibility into the host’s. machinectl and systemd-networkd cooperate to set up addressing on this private link automatically in most current distribution configurations, and this isolation is genuinely important for any container scenario where you don’t want the containerized environment able to see or interfere with the host’s actual network configuration and listening services directly.

Step 11: fetch a ready-made image instead of building a root filesystem by hand

sudo machinectl pull-tar https://cloud-images.example.org/debian-13.tar.xz mycontainer

Rather than manually running debootstrap or dnf --installroot as shown in Step 1, machinectl pull-tar (or pull-raw for disk images) downloads and verifies a pre-built image directly, registering it under /var/lib/machines/ ready to boot — a meaningfully faster path to a working container when a suitable pre-built image already exists, reserving the manual bootstrap approach for cases needing a genuinely custom or unusual base image.

Step 12: take a snapshot before making risky changes inside a container

sudo machinectl clone mycontainer mycontainer-backup

Because a container’s root filesystem is just an ordinary directory tree (or a disk image) under /var/lib/machines/, machinectl clone makes a full copy under a new name in one command — a cheap, disposable checkpoint to fall back to before testing something destructive inside the container, without needing any of the copy-on-write snapshot infrastructure a filesystem like Btrfs would otherwise provide for this. If /var/lib/machines is itself backed by Btrfs, machinectl automatically uses a fast, space-efficient reflink-based clone instead of a full byte-for-byte copy — worth knowing about specifically because it changes clone speed and disk usage dramatically without requiring anything different in the command you actually type.

Step 13: log into a container without a full shell session

sudo machinectl login mycontainer

machinectl login opens a genuine getty-style login prompt against the container, authenticating as a real user inside it, distinct from machinectl shell’s more direct root-equivalent shell access covered in Step 3 — useful specifically when you want to test the container’s actual login path (PAM configuration, a specific unprivileged user’s environment) rather than simply getting a working shell inside it as quickly as possible.

Related:

Sources:

Comments