From 54afb68c420488427363d2c15de85804742a21da Mon Sep 17 00:00:00 2001 From: Eduard Tolosa Date: Sun, 27 Sep 2026 05:21:18 -0500 Subject: [PATCH 1/2] Document the capabilities of apps outside a user namespace --- content/en/docs/machines.md | 15 +++++++++++---- content/en/docs/reference.md | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/content/en/docs/machines.md b/content/en/docs/machines.md index 578b4ce..1fee192 100644 --- a/content/en/docs/machines.md +++ b/content/en/docs/machines.md @@ -152,7 +152,9 @@ network namespace is built on the host before the program starts, so the network is there from the first instruction, as in docker. For the same reason an app on the bridge runs without a user namespace (`PrivateUsers=no`), which is also docker's default; capabilities, seccomp and the other namespaces still -apply. +apply, and such an app keeps docker's default capabilities rather than +systemd-nspawn's, whose `CAP_SYS_ADMIN` would be root on the host outside a +user namespace. `--cap-add` puts one back, `--privileged` all of them. - `exec` and `shell` enter the namespaces of the machine's leader process, on a pseudo terminal, with the image's environment. `shell` runs `/bin/sh`, and @@ -359,9 +361,14 @@ its unit: is refused) and the working directory the program runs with, instead of the image's. App images only. - `--cap-add`, `--cap-drop` and `--privileged`: capabilities on top of, or - out of, systemd-nspawn's default set (`NET_ADMIN` or `CAP_NET_ADMIN`; - `ALL`). `--cap-drop ALL --cap-add NET_BIND_SERVICE` keeps that one, as with - docker, and `--privileged` keeps every one. + out of, the default set (`NET_ADMIN` or `CAP_NET_ADMIN`; `ALL`): docker's + (CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, MKNOD, NET_BIND_SERVICE, + NET_RAW, SETFCAP, SETGID, SETPCAP, SETUID, SYS_CHROOT, AUDIT_WRITE) for an + app on a bridge network, which runs without a user namespace, and + systemd-nspawn's for a machine in one. An interface given with + `--interface` keeps `NET_ADMIN` on its own. `--cap-drop ALL --cap-add + NET_BIND_SERVICE` keeps that one, as with docker, and `--privileged` keeps + every one. - `--read-only`: the root read-only. `--tmpfs PATH[:OPTIONS]`: an empty tmpfs at a path (`size=64m`, `mode=1777`), which is how a read-only machine still writes `/tmp` or `/var/cache`. `/run` is a tmpfs of every machine already, diff --git a/content/en/docs/reference.md b/content/en/docs/reference.md index cfd86f9..95354bb 100644 --- a/content/en/docs/reference.md +++ b/content/en/docs/reference.md @@ -297,7 +297,7 @@ Boots an image as a machine. Every option is remembered for the next start. | `--hostname NAME` | Hostname inside the machine. Default: its name. A booted machine gets it as its `/etc/hostname`. | | `-u`, `--user USER[:GROUP]` | User the program runs as, a name or a uid (listed in the image's `passwd` or not), instead of the image's, with a group after a colon as docker takes it: a name of the image's `group` file or a number, which becomes the primary and only group of the program; a name the image lacks is refused. nspawn resolves both from the image's `passwd` and `group` files through a stand-in for getent, as docker does. App images only. | | `-w`, `--workdir DIR` | Working directory of the program, instead of the image's. App images only. | -| `--cap-add CAP` | Capability to keep on top of systemd-nspawn's default set: `NET_ADMIN`, `CAP_NET_ADMIN`, `ALL`. Repeatable; `none` forgets them. | +| `--cap-add CAP` | Capability to keep on top of the default set (docker's for an app on a bridge network, systemd-nspawn's for a machine in a user namespace): `NET_ADMIN`, `CAP_NET_ADMIN`, `ALL`. Repeatable; `none` forgets them. | | `--cap-drop CAP` | Capability to drop from the default set. `--cap-drop ALL --cap-add X` keeps `X`, as with docker. Repeatable; `none` forgets them. | | `--privileged` | Every capability, like `docker --privileged`; `--privileged=false` takes it back. | | `--read-only` | Mount the machine's root read-only; `--read-only=false` takes it back. | From 46e86cbe2a7db1a4cc47decbbb7082202587e4ce Mon Sep 17 00:00:00 2001 From: Eduard Tolosa Date: Sun, 27 Sep 2026 06:42:49 -0500 Subject: [PATCH 2/2] Document the fixes of 1.6.1 --- content/en/docs/images.md | 4 +++- content/en/docs/machines.md | 40 +++++++++++++++++++++++------------- content/en/docs/reference.md | 28 +++++++++++++++---------- hugo.yaml | 2 +- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/content/en/docs/images.md b/content/en/docs/images.md index e51e34d..e328931 100644 --- a/content/en/docs/images.md +++ b/content/en/docs/images.md @@ -16,7 +16,9 @@ An image reference has the form `[registry/]repository[:tag|@digest]`: is the hub, `hub.nspawn.org`; the [configuration](/docs/configuration/) page shows how to change it. The first path component counts as a registry when it looks like a host, for example `docker.io/library/nginx` or - `registry.example:5000/team/app`. + `registry.example:5000/team/app`, or when it is the configured registry's own + name, so a registry of the local network without a dot or a port (`myhub`) + reads back as itself. - Without a tag or digest, the tag is `latest`. - Repository names may only contain lowercase letters, digits, `.`, `_`, `-` and `/`. Official Docker Hub images live under `library/`, so nginx is diff --git a/content/en/docs/machines.md b/content/en/docs/machines.md index 1fee192..79a5d84 100644 --- a/content/en/docs/machines.md +++ b/content/en/docs/machines.md @@ -152,9 +152,10 @@ network namespace is built on the host before the program starts, so the network is there from the first instruction, as in docker. For the same reason an app on the bridge runs without a user namespace (`PrivateUsers=no`), which is also docker's default; capabilities, seccomp and the other namespaces still -apply, and such an app keeps docker's default capabilities rather than -systemd-nspawn's, whose `CAP_SYS_ADMIN` would be root on the host outside a -user namespace. `--cap-add` puts one back, `--privileged` all of them. +apply, and such an app keeps docker's default capabilities and `SYS_BOOT`, +with the kexec system calls filtered out, rather than systemd-nspawn's, whose `CAP_SYS_ADMIN` would be root on the host +outside a user namespace. `--cap-add` puts one back, `--privileged` all of +them. - `exec` and `shell` enter the namespaces of the machine's leader process, on a pseudo terminal, with the image's environment. `shell` runs `/bin/sh`, and @@ -363,9 +364,10 @@ its unit: - `--cap-add`, `--cap-drop` and `--privileged`: capabilities on top of, or out of, the default set (`NET_ADMIN` or `CAP_NET_ADMIN`; `ALL`): docker's (CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, MKNOD, NET_BIND_SERVICE, - NET_RAW, SETFCAP, SETGID, SETPCAP, SETUID, SYS_CHROOT, AUDIT_WRITE) for an - app on a bridge network, which runs without a user namespace, and - systemd-nspawn's for a machine in one. An interface given with + NET_RAW, SETFCAP, SETGID, SETPCAP, SETUID, SYS_CHROOT, AUDIT_WRITE) plus + SYS_BOOT, so that a reboot from inside ends the machine, for an app on a + bridge network, which runs without a user namespace, and systemd-nspawn's + for a machine in one. An interface given with `--interface` keeps `NET_ADMIN` on its own. `--cap-drop ALL --cap-add NET_BIND_SERVICE` keeps that one, as with docker, and `--privileged` keeps every one. @@ -377,10 +379,12 @@ its unit: `/dev/shm`. - `--device HOST[:CONTAINER[:PERMISSIONS]]`: a device node of the host, bound into the machine and allowed to its cgroup (`r`, `w`, `m`; `rwm` by - default). In a machine with private users the node keeps the host's + default), or a directory such as `/dev/dri`, whose nodes are allowed one by + one. In a machine with private users the node keeps the host's ownership. - `--dns` and `--dns-search`: the machine's `resolv.conf`, instead of the - host's resolvers. `--add-host HOST:IP`: lines for its `/etc/hosts`, + host's resolvers. `--add-host HOST:IP` (or `HOST=IP`, for an IPv6 address): + lines for its `/etc/hosts`, `host-gateway` standing for the host's address on the machine's network. - `--ulimit NAME=SOFT[:HARD]`: resource limits of the program (`nofile`, `nproc`, `core`, ...; `unlimited` is a value). `--oom-score-adj`: the @@ -400,7 +404,10 @@ its unit: A list takes `none` to forget it (`--cap-drop none`, `--tmpfs none`), a value an empty string (`--hostname ""`) or `0` (`--oom-score-adj 0`), and -`--privileged=false` and `--read-only=false` take those back. A path the image +`--privileged=false` and `--read-only=false` take those back. A path inside the +machine, the target of a volume or a secret, a `--tmpfs` or a `--device` path, +must be plain: a `.` or `..` component is refused, since it would land elsewhere +once mounted. A path the image declares as a volume with nothing mounted over it gets a note at start: nspawn has no anonymous volumes, so what is written there goes with the machine. @@ -469,13 +476,17 @@ sudo nspawn exec MACHINE [-u USER] [-e VAR[=VALUE]]... [-w DIR] [-T] [-t] [-i] [ sudo nspawn shell MACHINE [-u USER] ``` -`exec` runs one command inside a running machine of either kind, attached to -your terminal, and exits with the command's status, so it works in scripts and +`exec` runs one command inside a running machine of either kind, on a terminal +when standard input and output are both one, and exits with the command's status, so it works in scripts and pipelines (what goes through stdin and stdout is byte exact). The program is looked up on the machine's `PATH`, the image's environment and the `-e` variables of the machine apply, and the working directory is the machine's. -Neither D-Bus nor anything else is needed inside. The flags are `docker exec`'s: -`-e` adds variables for this command, `-w` a working directory, `-T` refuses a +Neither D-Bus nor anything else is needed inside. The command runs with the +machine's capabilities and resource limits, like the machine's own processes; +right after a start it waits, a few seconds at most, until systemd-nspawn has +finished confining the machine. The flags are `docker exec`'s: `-u USER[:GROUP]` +takes names or numbers of the image's passwd and group files (with a group, that +one is the only group), `-e` adds variables for this command, `-w` a working directory, `-T` refuses a terminal even from one (pipes, as in a script) and `-t` asks for one even without, `-i` is accepted for docker's sake, and `-d` leaves the command running in the background and returns at once. @@ -503,7 +514,8 @@ not, with docker cp's rules: times are kept. - Paths inside the machine are resolved inside it, so a link there, absolute or not, never leads to the host. Links are copied as links; devices, sockets - and fifos are left out. + and fifos are left out, and so are the kernel's file systems mounted inside + a running machine (`/proc`, `/sys` and the like). A stopped overlay or flat machine can be copied into and out of; a stopped `mstack` machine cannot, since its tree only exists while it runs. Where diff --git a/content/en/docs/reference.md b/content/en/docs/reference.md index 95354bb..271636f 100644 --- a/content/en/docs/reference.md +++ b/content/en/docs/reference.md @@ -297,16 +297,16 @@ Boots an image as a machine. Every option is remembered for the next start. | `--hostname NAME` | Hostname inside the machine. Default: its name. A booted machine gets it as its `/etc/hostname`. | | `-u`, `--user USER[:GROUP]` | User the program runs as, a name or a uid (listed in the image's `passwd` or not), instead of the image's, with a group after a colon as docker takes it: a name of the image's `group` file or a number, which becomes the primary and only group of the program; a name the image lacks is refused. nspawn resolves both from the image's `passwd` and `group` files through a stand-in for getent, as docker does. App images only. | | `-w`, `--workdir DIR` | Working directory of the program, instead of the image's. App images only. | -| `--cap-add CAP` | Capability to keep on top of the default set (docker's for an app on a bridge network, systemd-nspawn's for a machine in a user namespace): `NET_ADMIN`, `CAP_NET_ADMIN`, `ALL`. Repeatable; `none` forgets them. | +| `--cap-add CAP` | Capability to keep on top of the default set (docker's and SYS_BOOT for an app on a bridge network, systemd-nspawn's for a machine in a user namespace): `NET_ADMIN`, `CAP_NET_ADMIN`, `ALL`. Repeatable; `none` forgets them. | | `--cap-drop CAP` | Capability to drop from the default set. `--cap-drop ALL --cap-add X` keeps `X`, as with docker. Repeatable; `none` forgets them. | | `--privileged` | Every capability, like `docker --privileged`; `--privileged=false` takes it back. | | `--read-only` | Mount the machine's root read-only; `--read-only=false` takes it back. | | `--tmpfs PATH[:OPTIONS]` | An empty tmpfs at a path inside (`/tmp:size=64m,mode=1777`). One that lands on `/run`, a tmpfs of every machine already, is left out with a note. Repeatable; `none` forgets them. | | `--shm-size SIZE` | Size of `/dev/shm`: `64m`, `1g`; `0` for the default. | -| `--device HOST[:CONTAINER[:PERMISSIONS]]` | A device node of the host for the machine, like `docker --device` (`/dev/dri`, `/dev/ttyUSB0:/dev/ttyUSB0:rw`). Repeatable; `none` forgets them. | +| `--device HOST[:CONTAINER[:PERMISSIONS]]` | A device node of the host for the machine, like `docker --device` (`/dev/ttyUSB0:/dev/ttyUSB0:rw`), or a directory whose nodes are allowed one by one (`/dev/dri`). Repeatable; `none` forgets them. | | `--dns ADDRESS` | DNS server for the machine, instead of the host's. Repeatable; `none` forgets them. | | `--dns-search DOMAIN` | DNS search domain. Repeatable; `none` forgets them. | -| `--add-host HOST:IP` | A line for the machine's `/etc/hosts`; `host-gateway` is the host's address on the machine's network. Repeatable; `none` forgets them. | +| `--add-host HOST:IP` | A line for the machine's `/etc/hosts`, `HOST:IP` or `HOST=IP` (the second for an IPv6 address); `host-gateway` is the host's address on the machine's network. Repeatable; `none` forgets them. | | `--ulimit NAME=SOFT[:HARD]` | A resource limit of the program, like `docker --ulimit` (`nofile=1024:4096`, `core=unlimited`). Repeatable; `none` forgets them. | | `--oom-score-adj N` | OOM score adjustment of the machine, -1000 to 1000. | | `--stop-signal SIGNAL` | Signal `stop` sends the program, instead of the image's (`SIGTERM`). | @@ -319,6 +319,9 @@ Boots an image as a machine. Every option is remembered for the next start. | `--no-wait` | Do not wait for a booted machine's init to be up before returning. Its registration is still awaited, so that ports and firewall rules can be applied. | | `-- ARGUMENTS...` | App images: replace the image's cmd; they follow its entrypoint, as with docker. | +A path inside the machine, the target of `-v` or `--secret`, a `--tmpfs` or a +`--device` path, must be plain: a `.` or `..` component is refused. + ## run ```text @@ -346,7 +349,7 @@ image shows its console until it powers off, and Ctrl-C powers it off. | `REFERENCE` | `[registry/]repository[:tag\|@digest]`, for example `nginx:1.27`. | | `COMMAND [ARGUMENT...]` | App images: replace the image's cmd and follow its entrypoint, as with docker; everything after the reference that is not an option of `run`, or everything after `--`. | | `-d`, `--detach` | Start the machine in the background and return, like `docker run -d`; `nspawn logs -f NAME` follows its output. | -| `--rm` | Remove the machine once it ends, with or without `-d`; named volumes stay. An image the run had to pull is kept under the image's local name, as docker keeps images, and without `--name` the machine gets a name of its own (`alpine-3-1f0c9a2e`). Refused with a restart policy. A run that fails to start leaves nothing behind. | +| `--rm` | Remove the machine once it ends, with or without `-d`; named volumes stay. An image the run had to pull is kept under the image's local name when that name is free, as docker keeps images, and without `--name` the machine gets a name of its own (`alpine-3-1f0c9a2e`). Refused with a restart policy. A run that fails to start leaves nothing behind. | | `-i`, `--interactive` | App images: give the program this standard input, like `docker run -i`. | | `-t`, `--tty` | App images: give the program a terminal, like `docker run -t`; Ctrl-C and resizes reach it through the terminal. Closing the terminal stops the machine. With `-i` on a booted image: wait for it to boot, open a root shell, and power it off when the shell ends, with the shell's exit code. Not with `-d`. | | `-n`, `--name NAME` | Name of the machine. Default: derived from the reference, for example `nginx-1.27`. A name that is taken is refused, with a pointer to `start`. | @@ -445,17 +448,19 @@ nspawn exec MACHINE [-u USER] [-e VAR[=VALUE]]... [-w DIR] [-T] [-t] [-i] [-d] C ``` Runs a command inside a running machine of either kind, attached to the -terminal, in the machine's namespaces, with the image's environment and the -machine's `-e` variables. The program is found on the machine's `PATH` and -runs with the machine's capabilities, like its own processes. Exits with the -command's status. +terminal when standard input and output are one, in the machine's namespaces, +with the image's environment and the machine's `-e` variables. The program is found on the machine's `PATH` and +runs with the machine's capabilities and resource limits, like its own +processes; right after a start it waits, a few seconds at most, until +systemd-nspawn has finished confining the machine. Exits with the command's +status. | Option | Meaning | | --- | --- | -| `-u`, `--user USER` | User inside the machine. Default: `root`. | +| `-u`, `--user USER[:GROUP]` | User inside the machine, a name or a number of its passwd file, with a group of its group file after a colon (then the only group; otherwise the user's supplementary groups come along) and the home of the passwd entry. Default: `root`. | | `-e`, `--env VAR[=VALUE]` | A variable for the command, `VAR=value` or `VAR` copied from the calling shell, like `docker exec -e`. Repeatable. | | `-w`, `--workdir DIR` | Working directory of the command, instead of the machine's. | -| `-T`, `--no-tty` | No terminal, even from one: pipes, as in a script. | +| `-T`, `--no-tty` | No terminal, even from one: pipes, as in a script. Without `-t` or `-T`, a terminal only when standard input and output are one, so redirected output is byte-exact. | | `-t`, `--tty` | A terminal for the command, even without one here. | | `-i`, `--interactive` | Accepted for docker's sake: the command's input is always this one. | | `-d`, `--detach` | Leave the command running in the background and return at once. | @@ -501,7 +506,8 @@ machine; a stopped mstack machine has no tree on the host until it runs. modification times are kept. - Paths inside the machine are resolved inside it: a link there, absolute or not, never leads to the host. Links are copied as links; devices, sockets and - fifos are left out. + fifos are left out, and so are the kernel's file systems mounted inside a + running machine (`/proc`, `/sys` and the like). - A relative path after `MACHINE:` starts at the machine's root. A local path with a colon is written `./a:b`. diff --git a/hugo.yaml b/hugo.yaml index 7c40db3..ccded2b 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -53,7 +53,7 @@ params: privacy_policy: /about/#privacy # Version of nspawn the documentation describes. - version: 1.6.0 + version: 1.6.1 archived_version: false # In-page links to open issues and suggest changes.