When the systemd native container tool is the better choice
Where Docker ships a full runtime complete with its own image format, registry integration, and orchestration, systemd-nspawn takes the opposite approach: a minimalist tool, deeply integrated into systemd, that turns a chroot-like directory or image into a fully isolated container within seconds, with no daemon, registry, or compose file involved.
Table of Contents
- 1. When systemd-nspawn beats Docker
- 2. Creating and starting a container from a directory
- 3. Starting and importing a container from an image
- 4. Networking options: from shared network to bridge
- 5. machinectl integration for everyday operations
- 6. Resource limits and depth of isolation compared
- 7. Bind mounts and persistent data
- 8. Where it diverges from full Docker setups
- 9. Practical example: an isolated build sandbox for packaging
- 10. Summary
- 11. FAQ
1. When systemd-nspawn beats Docker
systemd-nspawn fits whenever you need a complete, isolated Linux system that behaves like a standalone machine, rather than a single process wrapped in a container shell. Unlike a typical Docker container, which runs exactly one main process, an nspawn container usually boots a full init system as PID 1, complete with its own logging, its own services, and its own network stack, making it feel closer to a very lightweight virtual machine than a classic application container.
Typical use cases include build and test environments for different distributions, isolated development sandboxes on a workstation, or minimalist hosting setups where you would rather not install an additional container runtime daemon, since systemd already runs on virtually every modern Linux server anyway. For microservice architectures with many independently deployable single processes, Docker or Podman remains the better fit most of the time, since the one process per container model and the rich image ecosystem offer clear advantages there.
2. Creating and starting a container from a directory
The simplest entry point is a container built directly from a local directory holding a complete root filesystem. Tools such as debootstrap for Debian based systems or dnf --installroot for RPM based distributions populate an empty directory with a bootable base system that then serves directly as the container root, with no prebuilt registry image involved at all.
The command systemd-nspawn -D starts that directory as a container with an interactive shell, while systemd-nspawn -bD instead triggers a full boot with an init system as PID 1, including systemd's own logging and service management inside the container. This boot mode is really the core of nspawn: afterward the container behaves in almost every respect like a standalone system, where services can be managed with systemctl status just as on a physical host.
# Install a minimal Debian system into a directory
sudo debootstrap bookworm /var/lib/machines/test-container
# Start the container interactively as a shell
sudo systemd-nspawn -D /var/lib/machines/test-container
# Full boot with an init system as PID 1
sudo systemd-nspawn -bD /var/lib/machines/test-container
3. Starting and importing a container from an image
Besides local directories, systemd-nspawn also supports ready made disk images in raw, qcow2, or tar format via the -i or --image= flag. Such images can be imported directly from an HTTP or HTTPS source using machinectl pull-tar or machinectl pull-raw, with systemd optionally verifying the image's GPG signature before storing it locally.
After importing, the image lands by default under /var/lib/machines/, the standard directory shared by systemd-nspawn and machinectl for containers. From there the container can be started directly by name with machinectl start, without having to specify the full path again, which makes day to day handling considerably more convenient than the plain directory approach.
# Import a ready made tar image, including GPG signature verification
machinectl pull-tar https://images.example.com/debian-12.tar.xz debian-base
# Start the imported container via machinectl
machinectl start debian-base
machinectl shell debian-base
4. Networking options: from shared network to bridge
Without explicit network configuration, an nspawn container shares the host's network namespace by default, which is simple but provides no network isolation whatsoever and invites port conflicts between host and container. For real isolation, the --network-veth flag enables a virtual ethernet pair between host and container, automatically creating a host side interface named after the pattern ve-.
For more production oriented setups, this veth interface can be bound directly to an existing bridge on the host via --network-bridge=, giving the container its own IP address on the same subnet as the host. Alternatively, --network-macvlan= lets the container participate directly on the physical network through its own virtual MAC address, which matters especially for containers that need to be reachable from outside via their own IP, with no NAT or port forwarding involved.
# Start a container with its own veth pair attached to a host bridge
sudo systemd-nspawn -bD /var/lib/machines/web-container \
--network-bridge=br0
# Alternative: a dedicated virtual MAC address via macvlan
sudo systemd-nspawn -bD /var/lib/machines/web-container \
--network-macvlan=eth0
5. machinectl integration for everyday operations
machinectl is the central management tool for nspawn containers and treats them as full fledged systemd machines, the same concept systemd also uses for virtual machines. Commands like machinectl list, machinectl status, and machinectl poweroff work regardless of whether the container was started manually with systemd-nspawn or through a registered systemd unit.
Especially handy is machinectl shell, which opens a login shell directly inside the container without having to set up SSH access, along with machinectl copy-to and machinectl copy-from for copying files between host and container through the machinectl interface. Containers meant to run permanently are best registered as a systemd unit via systemctl enable systemd-nspawn@, so machinectl and systemd jointly manage the lifecycle, including automatic startup at boot.
# List all registered containers
machinectl list
# Permanently enable a container as a systemd unit
sudo systemctl enable --now systemd-nspawn@web-container
# Direct shell access without SSH
machinectl shell web-container
6. Resource limits and depth of isolation compared
Since systemd-nspawn containers are managed as regular systemd units, the same resource limit mechanisms apply as for any other service: MemoryMax, CPUQuota, and IOWeight can be set directly in the generated unit file or at runtime via systemctl set-property, with no separate cgroup configuration outside the systemd ecosystem needed. That is an advantage over Docker, where resource limits are configured through dedicated CLI flags instead of native distribution tooling.
In terms of isolation depth, nspawn sits between a simple chroot and a full VM: it uses the same kernel namespaces as Docker, PID, mount, UTS, IPC, and optionally network, but by default skips the strict seccomp filtering Docker ships out of the box, though a custom seccomp profile can be added via --system-call-filter. For workloads with high security requirements, this flag should be set explicitly rather than relying on the comparatively open defaults.
# Set CPU and memory limits for a running container via systemctl
sudo systemctl set-property systemd-nspawn@web-container.service \
CPUQuota=50% MemoryMax=512M
# Add a custom syscall filter set
sudo systemd-nspawn -bD /var/lib/machines/web-container \
--system-call-filter=@basic-io --system-call-filter=@network-io
7. Bind mounts and persistent data
Unlike Docker with its explicit volume concept, nspawn relies on classic bind mounts via the --bind= flag, or --bind-ro= for read only mounts, to surface host directories inside the container. That feels more natural to administrators familiar with classic chroot or jail environments than Docker's own volume management, though it also brings less built in abstraction such as named volumes.
For persistent database or application data, it is best to maintain a separate directory outside the container's root filesystem and mount it in via bind mount, so that accidentally deleting or rebuilding the container never endangers the actual data. Because nspawn containers correspond to regular filesystem paths, backups can be performed with classic tools like rsync or tar, with no container specific tooling required.
# Mount a persistent data directory from the host into the container
sudo systemd-nspawn -bD /var/lib/machines/db-container \
--bind=/srv/container-data/db:/var/lib/mysql
# Mount a read only configuration directory
sudo systemd-nspawn -bD /var/lib/machines/db-container \
--bind-ro=/etc/container-config:/etc/app-config
8. Where it diverges from full Docker setups
A full Docker setup brings a rich image ecosystem, standardized compose definitions for multi container applications, and a broad tooling landscape for CI pipelines, registries, and orchestration. systemd-nspawn has none of that: no registry integration, no layered image format, and no built in multi container orchestration, which quickly turns into manual busywork for complex applications made up of many services.
The trade off pays off exactly where that Docker complexity is unwelcome: single, long lived system containers that should behave like a standalone lightweight VM, minimalist hosts without an extra runtime daemon, or test environments that need several distributions running in parallel as full booting systems. For a typical Magento stack made of several loosely coupled services, Docker or Podman remains the more pragmatic choice most of the time, while nspawn suits individual, isolated system services better, for example a dedicated build server per distribution.
9. Practical example: an isolated build sandbox for packaging
A common scenario in hosting environments is an isolated build sandbox where packages or PHP extensions get compiled for a specific distribution, without polluting the host with build dependencies. A minimal Debian directory created via debootstrap serves as a reusable base that can be reset from a clean snapshot before every build run, something a simple btrfs or overlayfs snapshot of the container directory accomplishes in seconds.
Because the build runs inside a fully booted system, even build processes that themselves depend on systemd services, such as a local database service for integration tests during the build run, work without extra contortions. Once the build finishes, the resulting artifact can simply be copied out of the container directory, while the container itself gets discarded afterward or reset to the clean snapshot.
# Boot the build sandbox from a clean snapshot, copy out the result afterward
sudo systemd-nspawn -bD /var/lib/machines/build-bookworm \
--bind=/srv/build-output:/output
# After the build: reset the container directory to a clean state
sudo btrfs subvolume delete /var/lib/machines/build-bookworm
sudo btrfs subvolume snapshot /var/lib/machines/build-bookworm-clean \
/var/lib/machines/build-bookworm
| Criterion | systemd-nspawn | Docker |
|---|---|---|
| Base model | full system with init as PID 1 | one main process per container |
| Daemon | no daemon, systemd manages the lifecycle | dockerd as a permanent background service |
| Image format | directory or tar/raw image, no registry | layered OCI image with registry ecosystem |
| Resource limits | native systemd unit properties | dedicated CLI flags and cgroup wrapper |
| Typical use | isolated system containers, build sandboxes | microservices, multi container applications |
Mironsoft
Server administration, Docker hosts, and performance tuning
Linux servers nobody on the team really understands anymore?
We handle setup, hardening, and performance tuning of Linux servers and Docker hosts for Magento deployments, documented and traceable instead of grown and unclear.
Server Audit
Review the existing server configuration for security gaps and performance bottlenecks.
Docker Host Setup
Set up and secure production-ready Docker environments for Magento cleanly.
Monitoring & Tuning
Measure resource usage and tune systemd, kernel, and services with purpose.
10. Summary
systemd-nspawn
Core idea
Full system with init as PID 1, no one process model
Management
machinectl and native systemd units, no separate daemon
Networking
veth, bridge, or macvlan depending on isolation needs
Ideal use
Build sandboxes and isolated system services, not microservices