Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions content/en/docs/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ mkosi configuration. A directory with a `Dockerfile` (or `dockerfile`,
Dockerfile flags below make it a Dockerfile build; a directory with
`mkosi.conf` or `mkosi.conf.d` and no Dockerfile, or the mkosi flags, an mkosi
build. Either result is stored like a pulled image and can be started right
away or pushed. A Dockerfile build of a tag replaces the image it built or
pulled under that reference before, as long as no machine ran from it; `--force`
replaces any other image of the name, an mkosi build's included. `-f` names the
Dockerfile, so `--force` has no short form.
away or pushed. A build of a tag replaces the image of that reference under the
tag's local name as long as nothing ran from it, whatever it was pulled or built
as: the tag moves to the new build, so a script that builds and runs its image
builds it again. A machine that has the name (one that ran, starts or runs), or
an image of another reference, keeps it, and the build goes beside it with the
same reference, into the image an earlier build of the tag put there or else
under a free name (`app-2`), which a note says; `run`, `create`, compose and the
Docker API find it by the reference, the image before the machine. An mkosi
build given `--name` is refused instead when a machine or an image of another
reference has that name. `--force` replaces whatever has the name, a machine
that ran included, and refuses one that runs. `-f` names the Dockerfile, so
`--force` has no short form.

## Dockerfiles

Expand Down
8 changes: 4 additions & 4 deletions content/en/docs/contribution-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,17 @@ sending it.
## Working on this site

The site is built with [Hugo](https://gohugo.io/) and the
[Docsy](https://www.docsy.dev/) theme. With Docker installed, a live preview
[Docsy](https://www.docsy.dev/) theme. With nspawn installed, a live preview
needs nothing else:

```shell
git clone https://github.com/nspawn/nspawn.org.git
cd nspawn.org
docker compose up
sudo nspawn compose up
```

Then open <http://localhost:1313>. The container runs as uid and gid 1000; the
Then open <http://localhost:1313>. The machine runs as uid and gid 1000; the
README of the repository explains how to pass yours if they differ. Without
Docker, install Node.js, Go and git and run `npm run install:safe` followed by
nspawn, install Node.js, Go and git and run `npm run install:safe` followed by
`npm run serve`. The pages are Markdown files under `content/en/`; every
documentation page has an "Edit this page" link that opens the file on GitHub.
237 changes: 183 additions & 54 deletions content/en/docs/docker-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,174 @@
title: The Docker API
weight: 8
description: >-
docker's socket for reverse proxies and dashboards, and auto-update of the
machines whose tag points to a newer image.
The Docker Engine API on a socket of the host and in each machine that mounts
/var/run/docker.sock, for the docker command, CI runners, reverse proxies and
dashboards, and auto-update of the machines whose tag points to a newer image.
---

## docker's socket in a machine
nspawn serves the Docker Engine API, 1.51 and any version down to 1.24 a client
asks for, on a socket of the host and in each machine that mounts
`/var/run/docker.sock`. Clients make, start, attach to, exec in, copy files to and from and
remove containers, pull and remove images, and make, join and remove volumes and
networks. The server is a client of the [service](/docs/overview/#the-service),
as the command line is: every change it makes is a method of `org.nspawn`, under
the same polkit actions, and what it makes is a machine like any other, which
`nspawn ps` shows.

Tools that follow containers through docker's socket find nspawn's machines the
same way: reverse proxies that route by labels (caddy-docker-proxy, Traefik,
## The host's socket

The packages ship it turned off:

```shell
sudo systemctl enable --now nspawn-api.socket
export DOCKER_HOST=unix:///run/nspawn/api.sock
```

Every user may connect to `/run/nspawn/api.sock`, and polkit decides what each
one may do, with the service's actions: reads and waits ask for
`org.nspawn.inspect`, logs and changes for `org.nspawn.manage`, `_ping` and
`version` for nothing. polkit is never asked to prompt, since nothing on a socket
answers an authentication agent: a user other than root needs a rule, such as
`nspawn-wheel.rules`, which the packages ship as an example. Root may do
everything.

Then the `docker` command works with it:

```text
$ docker pull busybox:1.37
1.37: Pulling from library/busybox
docker.io/library/busybox:1.37: manifest 66a6306db78b with 1 layer(s), assembling as overlay
68fe9bff2ad4: Already exists
30ecbe150909: Already exists
Digest: sha256:a7dbc319b1438115a207e11930dc4bb3e643dcb3ec0bdd694bb36cd8fad289ec
Status: Downloaded newer image for busybox:1.37
docker.io/library/busybox:1.37
$ docker run --rm busybox:1.37 echo hello
hello
$ docker run -d --name web -p 8080:80 busybox:1.37 httpd -f -p 80 -h /tmp
c7dac8ca487657695c14e21240f15fd99e7eac48d6c13e770febe7b0ac74d31e
$ echo hello > index.html && docker cp index.html web:/tmp/index.html
$ curl -s http://127.0.0.1:8080/
hello
$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES IMAGE STATUS PORTS
web busybox:1.37 Up Less than a second 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
$ docker exec web sh -c 'id; pwd'
uid=0(root) gid=0(root)
/
$ sudo nspawn ps
MACHINE IMAGE MODE COMMAND STATE UP PID NETWORK OS
web docker.io/library/busybox:1.37 app httpd -f -p 80 -h /tmp running 0s 2814430 10.99.0.3 8080->80/tcp -
```

Volumes and networks are nspawn's:

```text
$ docker volume create data
data
$ docker network create back
68735561ab7cd7e4da9b248b9c68ff65d4518c05d1730d79056e8dd3c2dc8eda
$ docker network connect back web
$ docker run --rm --network back -v data:/data busybox:1.37 sh -c 'wget -qO- http://web/ | tee /data/page'
hello
$ docker run --rm -v data:/data busybox:1.37 cat /data/page
hello
```

Where nothing else on the host has `/run/docker.sock`, clients that only look
there or at `/var/run/docker.sock` find the socket with a drop-in:

```shell
sudo mkdir -p /etc/systemd/system/nspawn-api.socket.d
printf '[Socket]\nSymlinks=/run/docker.sock\n' | sudo tee /etc/systemd/system/nspawn-api.socket.d/symlink.conf
sudo systemctl daemon-reload && sudo systemctl restart nspawn-api.socket
```

### GitLab Runner

GitLab Runner's docker executor runs its jobs through the socket: the clone, the
job's image, services with their aliases, artifacts, cache volumes and a network
per build (`FF_NETWORK_PER_BUILD`). In its `config.toml`:

```toml
[[runners]]
executor = "docker"
[runners.docker]
host = "unix:///run/nspawn/api.sock"
image = "alpine:3.22"
```

The runner runs as root, or as a user that a polkit rule lets through.

## What it serves

- `_ping`, `version`, `info` and `system df`.
- Containers: list, inspect, logs, stats, top, events; create, start, stop,
restart, kill, pause, unpause, wait, update, remove and prune. A create's
settings become the machine's options. What nspawn has no use for is named in
the answer's `Warnings` (`CpuShares`, `SecurityOpt`, a logging driver other than
`json-file`, `local`, `journald` or `none`, among others); what would change what
the container does is refused with 400 (`PidMode`, `IpcMode` or `UTSMode` of the
host, a user namespace mode, another runtime, `VolumesFrom`, an IPv6 address).
- Attach, with the input: `docker run -i`, `docker run -it` and `docker start -ai`
give the program their input, and an attach that keeps the input gets the output
byte for byte.
- Exec: a terminal or the two streams apart, the input, variables, working
directory and user. On a terminal, the end of the input or the client's hangup
sends the command SIGHUP.
- Copies: `docker cp` both ways, on running containers and on stopped ones, owners
and modes kept. A volume of a stopped container is mounted only while it runs, so
a copy to or from it is refused until it starts.
- Images: list, inspect, history, pull (`POST /images/create`), remove and prune.
A record pulled or built that never ran is an image, not a container; a machine
made from it is a container. An image's ID is its config's digest. An image built
here with a name without a registry (`nspawn build -t somos/app:1`, which nspawn
files as `hub.nspawn.org/somos/app:1`) goes by that short name too while Docker
Hub's image of it is not here, so `docker run somos/app:1` finds it.
- Volumes and networks: list, inspect, create, remove and prune; connect and
disconnect with aliases and fixed addresses. `bridge`, `host` and `none` are the
pre-defined networks.

Builds, loads, imports, tags, pushes and commits of images, container renames and
exports, swarm and plugins answer 501 with "not supported".

A name the API takes that is no machine name, such as `reaper_1`, stays the
container's name in the API, and its machine gets one made of it (`reaper-1-` and
eight hex digits), which `nspawn ps` shows. A network keeps the name the API gave
it the same way.

## The socket in a machine

A machine that mounts `/var/run/docker.sock`, `/run/docker.sock` or
`/run/nspawn/api.sock` from the host gets nspawn's Docker API there, whatever the
host has at that path, with `-v /var/run/docker.sock:/var/run/docker.sock` or
compose's `use_api_socket`. Mounted read-write, it takes every request the API serves:

```text
$ sudo nspawn run --rm -v /var/run/docker.sock:/var/run/docker.sock \
docker.io/library/docker:28.5.1-cli -- docker run --rm busybox:1.37 echo from-a-machine
docker-28.5.1-cli-6fd52594: 10 layer(s) shared with docker-28.5.1-cli, assembling as overlay
from-a-machine
```

Mounted with `:ro`, it reads, follows logs and kills, and refuses any other change
with 403:

```text
$ sudo nspawn run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro \
docker.io/library/docker:28.5.1-cli -- docker run --rm busybox:1.37 true
docker-28.5.1-cli-c46d6cef: 10 layer(s) shared with docker-28.5.1-cli, assembling as overlay
docker: Error response from daemon: this machine binds nspawn's Docker API read-only

Run 'docker run --help' for more information
```

Tools that follow containers through the socket find nspawn's machines the same
way: reverse proxies that route by labels (caddy-docker-proxy, Traefik,
nginx-proxy with docker-gen and acme-companion), dashboards (homepage) and log
viewers (Dozzle). A machine that mounts `/var/run/docker.sock` or
`/run/docker.sock` from the host, with `-v /var/run/docker.sock:/var/run/docker.sock`
(`:ro` is taken too) or compose's `use_api_socket`, gets nspawn's Docker API
there instead of the host's docker.

It speaks the Docker API 1.51, and any version down to 1.24 a client asks for:
`_ping`, `version` and `info`, the machines as containers with their labels,
networks and addresses, inspect with the environment, events as they happen,
logs, stats and top, and the networks. Reads of images, volumes and swarm
objects get 501. It changes nothing, with one exception:
`POST /containers/ID/kill` sends a signal as `nspawn kill` does, so that
docker-gen and acme-companion can have nginx read new configuration and
certificates. Any other write is refused with 403.

caddy-docker-proxy in front of a service, from a `compose.yaml` in a directory
`proxy`:
viewers (Dozzle). They only read, and kill to have a server read its
configuration again, so `:ro` is all they need. caddy-docker-proxy in front of a
service, from a `compose.yaml` in a directory `proxy`:

```yaml
services:
Expand All @@ -37,7 +180,7 @@ services:
environment:
- CADDY_INGRESS_NETWORKS=proxy_default
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/run/docker.sock:/var/run/docker.sock:ro
whoami:
image: traefik/whoami
labels:
Expand All @@ -52,42 +195,28 @@ Hostname: proxy-whoami-1
IP: 127.0.0.1
```

What a client sees, from a machine that mounts the socket:

```text
$ sudo nspawn run --rm -u 0 -v /var/run/docker.sock:/var/run/docker.sock \
docker.io/curlimages/curl:8.11.1 -- -s --unix-socket /var/run/docker.sock http://localhost/_ping
OK
$ sudo nspawn run --rm -u 0 -v /var/run/docker.sock:/var/run/docker.sock \
docker.io/curlimages/curl:8.11.1 -- -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/create
{"message":"nspawn's Docker API is read-only: POST /containers/create is not served"}
```

A machine that has it sees every machine and their
`-e` variables (`--secret` values never show). Only root inside the machine
can connect; the curl image runs as a user of its own, hence `-u 0`, and without
it the connection is refused:

```text
$ sudo nspawn run --rm -v /var/run/docker.sock:/var/run/docker.sock \
docker.io/curlimages/curl:8.11.1 -- -sS --unix-socket /var/run/docker.sock http://localhost/_ping
curl: (7) Failed to connect to localhost port 80 after 0 ms: Could not connect to server
```
A machine that has the socket sees every machine and their `-e` variables
(`--secret` values never show). Only root inside the machine can connect.

### How it is served

Each machine that takes the socket has its own, `/run/nspawn/api/NAME.sock`,
opened by systemd (`nspawn-api@NAME.socket`) before the machine starts and
closed after it stops. The server behind it, `nspawn-api@NAME.service`,
starts when something connects and leaves after a minute without a
connection. A container ID comes from the machine's name and creation time, so
it stays across restarts and updates. nspawn binds the machine's
`/etc/hostname` from a directory named after that ID, so tools
that look for their own container in `/proc/self/mountinfo` find it.

Each machine's API takes 64 connections at once and closes any past that. At
most 16 of them can stream logs or events, each running a `journalctl`; one more
gets 503. mstack machines cannot take the socket.
opened by systemd (`nspawn-api@NAME.socket`) before the machine starts and closed
after it stops; the host's is `nspawn-api.socket`. The server behind each,
`nspawn-api@NAME.service` or `nspawn-api.service`, starts when something connects
and leaves a minute after the last connection, the attached sessions and exec
commands of its clients included. A container ID comes from the machine's name and
creation time, so it stays across restarts and updates. nspawn binds the machine's
`/etc/hostname` from a directory named after that ID, so tools that look for their
own container in `/proc/self/mountinfo` find it.

What one socket's clients hold at once is bounded: 64 connections (one more is
closed at once), 16 of them logs, events or attach streams, each running a
`journalctl`, 16 waits and attaches, and 64 exec commands that run; one more of
any of these gets 503. On the host's socket a user other than root holds a quarter
of the connections and of the exec commands and half of the streams and of the
waits at most, so that no user keeps the others or root out, and root has 16
streams of its own. mstack machines cannot take the socket.

## auto-update

Expand Down
8 changes: 4 additions & 4 deletions content/en/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ No. nspawn's machines are systemd-nspawn containers managed by systemd: they are
`systemd-nspawn@NAME.service` units,
appear in `machinectl list`, log to journald and boot a full init when the
image has one. nspawn's own service holds none of them open. docker compose
files run with `nspawn compose`, Dockerfiles build with `nspawn build`, and a
machine that mounts `/var/run/docker.sock` gets a Docker API that reverse
proxies and
dashboards read, but there is no orchestration across hosts.
files run with `nspawn compose`, Dockerfiles build with `nspawn build`, and the
Docker Engine API, on a socket of the host and in machines that mount
`/var/run/docker.sock`, serves the `docker` command, CI runners, reverse proxies
and dashboards, but there is no orchestration across hosts.

## Can I run images from Docker Hub?

Expand Down
4 changes: 2 additions & 2 deletions content/en/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ keeps your credentials for that registry only; see
- [Compose](/docs/compose/): docker compose files, run by `nspawn compose`.
- [Building images](/docs/building/): Dockerfiles and mkosi with `build`, and
`push`.
- [The Docker API](/docs/docker-api/): the API nspawn serves to reverse proxies
and dashboards, and `auto-update`.
- [The Docker API](/docs/docker-api/): the API nspawn serves to the `docker`
command, CI runners, reverse proxies and dashboards, and `auto-update`.
- [Configuration](/docs/configuration/): `/etc/nspawn/nspawn.toml`, environment
variables and flags.
4 changes: 3 additions & 1 deletion content/en/docs/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,9 @@ made from; without `-a` it removes the dangling ones, which nspawn's store never
has, since a tag pulled or built again replaces its record. `system prune`
removes stopped machines made from an image (by `create` or `run`), networks no
machine uses and the build cache no image uses; with `-a` also the images no
machine was made from and the whole build cache. `--filter label=...` and
machine was made from and the whole build cache, and with `--volumes` the volumes
no machine uses that were made without a name, which only the Docker API makes.
`--filter label=...` and
`until=...` narrow what goes, and `-f` skips the question. `builder prune` takes
the [build cache](/docs/building/#the-build-cache) alone.

Expand Down
Loading