6.8 KiB
doos specification
Authoritative description of behavior and limits. Code constants live in src/spec.hpp; change both together.
CLI
| Command | Behavior |
|---|---|
doos list |
Table of NAME, STATE (running/stopped), NET, IMAGE (host when none), SIZE (cap), AGE. |
doos get_or_create [OPTIONS] NAME |
Creates the container if missing. Prints created NAME or exists NAME. |
doos attach [OPTIONS] NAME [-- CMD...] |
get_or_create, then run CMD (default /bin/bash, else /bin/sh). Starts the container if stopped, joins it if running. Exit code is the command's. |
doos delete [--yes] NAME |
Dry run unless --yes. Refuses a running container. |
doos image list |
Installed images (SIZE, IDS) plus catalog entries marked available. |
doos image add [--sha256 HEX] SOURCE [NAME] |
Installs an image. See Images. |
doos image delete [--yes] NAME |
Dry run unless --yes. Refuses an image used by any container, even with --yes. |
get-or-create is accepted as an alias.
Container options (all four commands that take NAME-creating options: get_or_create, attach):
| Option | Meaning | Default | Changeable later |
|---|---|---|---|
--image NAME |
Start from an image instead of the host system. Missing catalog images are fetched on demand. | none (host mode) | no |
--size SIZE |
Cap for what the container writes (512m, 8g, 1t; k/m/g/t). |
4g | yes, on any later get_or_create/attach |
--net slirp|host|none |
Network mode. | slirp | no |
--ids single|subuid |
Id mapping. | subuid when available, else single | no |
Naming an option on an existing container with a different fixed value is an error, never a silent change.
Limits (hard caps)
| Item | Cap |
|---|---|
| Containers | 64 |
| Images | 16 |
| Name | containers [a-z0-9][a-z0-9_-]{0,31}, images [a-z0-9][a-z0-9._-]{0,31} (: becomes -) |
| Command arguments | 256 |
| Open files inside | 4096 |
/tmp, /run, /dev/shm |
256m, 64m, 64m (tmpfs) |
--size |
64m to 1t |
| Size scan interval | 10 s (floor, never faster) |
| Size scan entries | 5,000,000 |
| Image download | 2 GiB, 900 s |
| Skeleton directories | 200,000 |
/etc copy |
50,000 files, 256 MiB |
| Id range per container | 65536 ids (0 plus up to 65535 from subuid) |
| slirp4netns ready wait | 5 s |
| Lock wait | 5 s |
| Tree recursion depth | 256 |
State layout
$DOOS_HOME, else $XDG_DATA_HOME/doos, else ~/.local/share/doos. Path must not contain , or :.
containers/NAME/{meta,pid,lock,seeded.*,rootfs/,layers/<dir>/{upper,work},private/{root,var,etc}}
images/NAME/{meta,rootfs/}
meta keys: net created version image ids size.
Container filesystem, host mode (no --image)
| Path | Source |
|---|---|
/usr /opt (and /bin /sbin /lib /lib32 /lib64 when real dirs) |
overlayfs, lower = host dir, upper = layers/<dir>/upper, with a directory skeleton in the upper |
/bin /sbin /lib... when host symlinks |
same symlink recreated |
/etc |
private copy of the readable host files, made once at creation |
/root, /var |
persistent private dirs; /var pre-seeded with the apt/dpkg directories |
/var/lib/dpkg |
overlay of the host dpkg database (when the host has it), regular files copied into the upper, lock files pre-created |
/tmp /run /dev/shm |
tmpfs |
/dev |
tmpfs + bound null zero full random urandom tty, new devpts instance |
/proc |
own pid namespace; sys sysrq-trigger irq bus fs read-only; kcore keys timer_list masked |
/sys, /home, everything else on host |
absent |
Why the skeleton and copies: host root-owned files appear as nobody inside the container. Container root cannot write into directories it does not own, nor open files it does not own for writing, nor hard-link them. The skeleton recreates every host directory as a container-owned directory in the upper layer (same mode, owner rwx added); the copies make the few files package managers rewrite container-owned.
Container filesystem, image mode (--image)
/ is one overlay: lower = images/NAME/rootfs (shared, never written), upper = layers/root/upper. /tmp /run /dev /proc are mounted over it as in host mode. The image is extracted under the same id map the container uses, so image files owned by root are owned by the container's root and nothing needs a skeleton or copies. LANG is C.UTF-8.
Images
SOURCE for image add:
| Source | Resolution | Verification |
|---|---|---|
alpine |
latest-stable minirootfs from dl-cdn.alpinelinux.org, via latest-releases.yaml |
sha256 from the yaml, over https |
ubuntu, ubuntu-VERSION |
newest ubuntu-base-*-base-ARCH.tar.gz of that release from cdimage.ubuntu.com (default 26.04) |
sha256 from SHA256SUMS, over https |
https://... |
downloaded | --sha256 HEX required, otherwise rejected |
| a file path | read locally | verified when --sha256 is given; otherwise used with a printed note |
http:// is refused. A mismatch deletes everything it downloaded. The tarball is extracted with tar -p --numeric-owner, excluding dev/*, inside a user namespace with the image's id mode. Architectures: x86_64 and aarch64 for the catalog.
Size cap
The parent process scans the container's writable data every 10 s (hard floor). At 80% it warns once on stderr. At 100% it freezes the container's cgroup (cgroup.freeze); processes stop, nothing is deleted. It thaws on its own once usage is below 95%. A second doos attach is not frozen, so the user can free space from inside. Without cgroup v2 delegation the cap only warns and says so at start.
Isolation
User, mount, pid, uts, ipc namespaces always; network namespace unless host. Id map: container 0 is the invoking user; with subuid ids 1..65535 map to the user's /etc/subuid range through newuidmap/newgidmap; with single nothing else is mappable. no_new_privs, core dumps off. pid 1 is a reaping init that forwards SIGTERM/SIGINT/SIGHUP/SIGQUIT and kills the container when the command exits. Container lifetime = the first attach command; later attaches join via setns.
Networking
slirp: private netns,slirp4netns --configure --disable-host-loopback, DNS 10.0.2.3, outbound only, host 127.0.0.1 unreachable.none: private netns with loopback only.host: shares the host network namespace (no isolation, explicit opt-in).
Safety rules
delete(containers and images) is dry-run by default, scoped to the store, refuses running containers and used images, never follows symlinks. Trees with subuid-owned files are removed from inside the id map.- Unverifiable input is rejected: URL images without sha256, hash mismatches, bad names, bad state paths, missing slirp4netns or newuidmap when requested.
- Seeding and scans report what they skipped (
unreadable skipped: N). - A failed start kills the half-built container (no orphans); a failed image add leaves no partial image.