Images and the hub
References
An image reference has the form [registry/]repository[:tag|@digest]:
- Without a registry part, the configured registry is used. Out of the box that
is the hub,
hub.nspawn.org; the configuration page shows how to change it. The first path component counts as a registry when it looks like a host, for exampledocker.io/library/nginxorregistry.example:5000/team/app. - Without a tag or digest, the tag is
latest. - Repository names may only contain lowercase letters, digits,
.,_,-and/. Official Docker Hub images live underlibrary/, so nginx isdocker.io/library/nginx;searchprints references in that form.
The local name of an image, the one start, stop, exec and friends
use, is derived from the reference (fedora:44 becomes fedora-44) unless
--name says otherwise. It has to be a valid machine name, so keep it to
letters, digits, - and _.
The hub
The hub is a plain OCI registry. nspawn hub ls lists its repositories with
their tags, and nspawn hub tags REPOSITORY the tags of one of them:
nspawn hub ls # everything
nspawn hub ls deb # repositories whose name contains "deb"
nspawn hub ls --no-tags # faster on big registries
nspawn hub tags fedora
The same commands work against any registry that implements the catalog
endpoint; point them somewhere else with --registry, the NSPAWN_REGISTRY
variable or the configuration file. A private CA is trusted with --ca-cert
or NSPAWN_CA_CERT.
The images on the hub are built with mkosi from the public definitions in nspawn/mkosi-definitions, with the package manager of each distribution.
Searching
nspawn search TERM [--source hub|dockerhub] [-n LIMIT]
search is docker search across the sources nspawn knows: the configured
hub (its catalog, case-insensitive substring match, with the tags of every
match) and Docker Hub (its search API). Every hit names its source and the
reference pull takes:
SOURCE NAME DESCRIPTION STARS OFFICIAL
hub.nspawn.org debian tags: 12, 13, bookworm, trixie - -
Docker Hub docker.io/library/debian Debian is a Linux distribution that's... 5100 yes
Docker Hub docker.io/someone/debian-x Debian with extras 3 -
--source asks one side only and -n limits the results per source (25 by
default). A source that cannot be reached is reported as a warning, not as an
error, so the other one still answers.
Registries and credentials
Registries are used anonymously until you log in:
sudo nspawn login # the hub
sudo nspawn login docker.io -u USER # Docker Hub
echo "$TOKEN" | sudo nspawn login registry.example -u ci --password-stdin
sudo nspawn logout docker.io
login asks for the user name and the password on the terminal (without
echo), or reads the password from standard input with --password-stdin, and
checks them the way docker login does: GET /v2/ on the registry, then basic
authentication or a token request at the realm the registry announces. Bad
credentials are refused with a clear message. A registry that never asks for
credentials gets them stored anyway, with a note saying so.
Credentials are kept in /etc/nspawn/auth.json, mode 0600, in the auth.json
format that podman and skopeo use. Credentials left on the host by
docker login or podman login are picked up as well, from
$XDG_RUNTIME_DIR/containers/auth.json, /run/containers/0/auth.json,
~/.docker/config.json and ~/.config/containers/auth.json, for root and for
the user behind sudo. Every operation chooses the credentials of the registry
it talks to, so the hub’s never travel to Docker Hub; the many names of Docker
Hub (docker.io, index.docker.io, registry-1.docker.io) are one entry.
Docker Hub limits anonymous pulls per address; logging in lifts that. push
authenticates before it uploads anything and, when the registry wants
credentials it does not have, says which login to run.
Pulling
sudo nspawn pull REFERENCE [--name NAME] [--backend BACKEND] [--mode MODE] [--force]
pull needs root. It resolves the reference to the manifest for the host’s
platform (image indexes are followed), downloads every layer and the config
blob that is not already in the store, checking each one against its sha256
digest while it streams (a blob is written next to its final name and renamed
only once verified, so an interrupted download never passes for a complete
one), and then:
- assembles the root file system with the chosen backend;
- reads the OCI config and decides whether the image is a boot or an app image;
- writes
/etc/systemd/nspawn/NAME.nspawn, the settings the machine boots with, and the drop-in that makessystemd-nspawn@NAME.servicecall nspawn around its life; - records the image (reference, manifest digest, layers, backend, mode,
network) under
/var/lib/nspawn, and keeps the manifest and blobs so that the image can be pushed or cloned later.
An image with the same name is not replaced unless you pass --force, and
never while its machine is running. The old image only goes once the new one
is downloaded, and the store is locked only for that last step, so a long
download holds up neither other commands nor the unit hooks.
Backends
The backend decides how the layers become a directory systemd-nspawn can boot:
| Backend | What it does | Needs |
|---|---|---|
overlay | Layers are extracted once under /var/lib/nspawn and shared. Each machine gets an overlayfs mount at /var/lib/machines/NAME, defined by a generated mount unit, with a private upper directory for its writes. | Any systemd with overlayfs. |
mstack | A native systemd.mstack directory at /var/lib/machines/NAME.mstack that points at the shared layers; the machine boots with managed user namespaces (PrivateUsers=managed) and the layers are shifted into the foreign UID range. | systemd 261 or newer with the systemd-nsresourced and systemd-mountfsd sockets available (nspawn starts them). |
flat | The layers are extracted into a plain directory at /var/lib/machines/NAME. Nothing is shared, maximum compatibility. | Nothing. |
auto (default) | mstack when the host supports it, otherwise overlay when overlayfs is available, otherwise flat. |
Pick one per image with --backend on pull, build or create, or set a
default in the configuration file. nspawn images ls shows which backend each
image uses.
App images are always assembled as overlay (or flat), even where mstack
is available: a machine under managed user namespaces cannot join the network
namespace nspawn prepares for apps on the bridge. pull says so with a note
when it makes that choice.
Boot and app images
After assembling, nspawn looks at the root file system and at the OCI config:
- An image that ships an init program (
usr/lib/systemd/systemd,lib/systemd/systemd,sbin/initorusr/sbin/init, as the topmost layer leaves it) and whose entrypoint is that init, or has no entrypoint at all, is a boot image. It is started withBoot=yes, asmachinectl startwould. - Everything else is an app image: the entrypoint and command from the
config run as PID 2 under nspawn’s stub init (
Boot=no,ProcessTwo=yes), with the config’s environment, working directory, user and stop signal.
--mode boot or --mode app overrides the detection. The mode is recorded with
the image; Machines explains how it changes start, exec,
shell and stop.
More machines from one image
sudo nspawn create SOURCE NAME [--backend BACKEND] [--network NETWORK] [-p HOST:CONTAINER]...
[--entrypoint PROGRAM] [-e VAR=VALUE]... [-v SOURCE:TARGET[:ro]]... [-f] [-- ARGUMENTS...]
create is docker create: another machine from an image that is already
local, given by its name or by the reference it was pulled from, without
touching the registry. It shares the source’s layers and gets a writable layer,
an address, settings and a record of its own, with create as its origin;
images rm of one never affects the others.
The network kind is inherited from the source unless --network says
otherwise; published ports are not, since two machines cannot publish the same
one. --entrypoint, -e and the arguments after -- apply to app images only
and mean the same as on start; -v works for both kinds. Everything on the
command line is checked before anything is made, so a refused create leaves
nothing behind. pull --name ends up the same way, but resolves the manifest
through the registry first.
Listing and removing
nspawn images ls
NAME TYPE BACKEND ORIGIN SOURCE SIZE RO
fedora-44 directory overlay pull hub.nspawn.org/fedora:44 612 MiB no
db directory overlay create hub.nspawn.org/fedora:44 612 MiB no
web directory overlay pull docker.io/library/nginx:latest 190 MiB no
old-arch directory - - - 1.2 GiB no
The list comes from systemd-machined, so it includes images nspawn did not
create (old-arch above, with - in the nspawn columns). ORIGIN is pull,
build or create.
sudo nspawn images rm NAME...
images rm needs root and refuses to remove the image of a running machine.
For an image nspawn manages it unmounts and deletes the assembled root, the
settings file, the generated units and drop-ins, the network namespace of an
app and the record; for any other image, or for the leftovers of a failed
install, it removes what it finds and asks machined to forget the rest.
Afterwards the layers and blobs that no remaining image references are
deleted, the /etc/hosts files of the machines on the bridge are regenerated
and the published ports are brought in line with the machines that still run.
Named volumes are not deleted: they may belong to another machine.
Where things live
| Path | Contents |
|---|---|
/var/lib/machines/NAME | The root of a machine with the overlay or flat backend (a mount point in the first case). |
/var/lib/machines/NAME.mstack | The systemd.mstack directory of a machine with the mstack backend. |
/var/lib/nspawn/ | nspawn’s own state: blobs as downloaded, extracted layers, image records, stored manifests, the private upper directories of overlay machines, the generated network files and units of each machine, mkosi build output and cache, and the store lock. |
/var/lib/nspawn/volumes/NAME | A named volume (-v NAME:/inside). |
/etc/systemd/nspawn/NAME.nspawn | The settings nspawn generates for a machine; regenerated at every start. |
/etc/systemd/system/systemd-nspawn@NAME.service.d/ | The drop-in with the unit hooks and, for an overlay machine, the one that requires its mount unit. The mount unit itself is next to them in /etc/systemd/system/. |
/etc/nspawn/nspawn.toml | The configuration file, optional. |
/etc/nspawn/auth.json | The credentials login stored, mode 0600. |
/run/netns/nspawn-NAME | The network namespace of a running app machine on the bridge. |
The state is kept outside /var/lib/machines on purpose: machined would list
a directory there as an image, and machinectl clean would delete it.