Files
doos/COMPARISON.md
T
retoorandClaude Sonnet 5 4c754b20da
ci / test (push) Waiting to run
ci / audit (push) Waiting to run
ci / build (push) Successful in 54s
ci / valgrind (push) Successful in 1m42s
Add Gitea CI, fix size overflow and helper-path inconsistency from review2.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-24 01:20:18 +02:00

20 KiB

doos compared with Docker, Podman and others

Docs: README · SPEC · ARCHITECTURE · RESEARCH · COMPARISON · AGENTS · CHANGELOG · CI

This page puts doos next to the tools people usually reach for instead. It separates three kinds of claims, and every row says which one it is:

  • [M] measured on the development machine, described in "Measurements" below.
  • [S] sourced from the linked documentation or articles. Vendor blogs are marked as such and their numbers are not used.
  • [D] design: what the doos source and SPEC do today.
  • [K] general knowledge that this research did not re-verify.

Nothing here is a roadmap. Where doos lacks something, the gap is listed with the code that would have to change.

Versions compared: doos 1.1.1, Docker 29.1.3 (rootful daemon on this machine), Podman 5.7.0 (netavark, crun, overlay), bubblewrap 0.11.1, on Ubuntu 26.04, kernel 7.0.0-38, 12 cores, one machine.

1. At a glance

doos Docker (rootful) Docker rootless Podman rootless
Needs a daemon no [D] yes, root daemon [S: Docker security] yes, per-user daemon [K] no [S: rootlesscontaine.rs]
Runs without root yes [D] no [S] yes [S] yes [S]
Who can use it safely any user only trusted users: access to the daemon is root-equivalent [S: Docker security, iximiuz] any user any user
Image source tarball, https URL with sha256, small catalog (Alpine, Ubuntu, Debian) [D] OCI registries, Dockerfile builds [K] same as Docker OCI registries, Containerfile builds [K]
Build images no [D] yes (BuildKit) [K] yes yes (Buildah) [K]
Host dirs into the container (-v) no [D] yes [K] yes yes
Published ports none for slirp; automatic for pasta [D] -p [K] -p -p [S: podman-rootless(7)]
Rootless network backend slirp4netns or pasta [D] n/a RootlessKit based [S: Podman blog] pasta default since 5.0 [S: podman-rootless(7)]
Resource limits memory, pids, cpus, written data [D][M] memory, cpu, pids, more [K] needs cgroup v2 + systemd delegation [S: Docker rootless tips] needs cgroup v2 [S: podman-rootless(7)]
Default syscall filter denylist: 48 listed calls plus rules for clone, unshare, clone3 [D] allowlist profile [S: Docker seccomp] same built-in profile [K]
Default capabilities Docker's default set [D][M] that set [S] that set a smaller set [S: dev.to comparison]
Orchestration (Compose, Kubernetes) none [D] Compose, Swarm, Kubernetes via runtimes [K] same Compose, pods, kube play, systemd units [K]
Background containers, logs, stop none: a container lives while its first attach command lives [D] yes [K] yes yes
Binary size 552 KiB, 6 shared libraries, 3,445 lines of C++ [M] CLI 29.9 MiB, plus dockerd and containerd [M][K] same 41.2 MiB [M]
Platforms Linux only, tested on x86_64 only [D] Linux, macOS, Windows (via a VM) [K] Linux Linux; macOS and Windows via a VM [K]
Maturity version 1.1, one test machine very mature, huge ecosystem mature mature

2. Other tools, one line each

Tool What it is Versus doos
bubblewrap (bwrap) Low-level unprivileged sandbox builder, the engine behind Flatpak [S: bubblewrap README, Julia Evans] Smaller and faster to start [M], but you assemble every mount by hand and it has no image handling, no persistent state and no cgroup limits [K]; it maps a single id unless you add a helper [K]. doos is the "batteries included" layer on the same kernel features.
systemd-nspawn Boots a whole OS tree as a container [S: forum overview] Rootless use is limited to a single UID/GID inside [S]. Not installed here, so not measured.
LXC / Incus (LXD fork) System containers: a full distro with init and several services, close to a light VM [S: OneUptime] Better for long-lived pet systems and multi-service setups, with API, storage pools and profiles. Heavier to set up. lxc binaries exist here, Incus does not, neither was run.
Apptainer (Singularity) Rootless, daemonless, single-file SIF images, built for HPC (MPI, InfiniBand, GPUs) [S: Alliance Canada, NYU HPC] Different audience. It also has --fakeroot and signed immutable images [S], which doos lacks. Not installed here.
Distrobox / Toolbx Podman- or Docker-backed "your distro inside your terminal", home directory shared on purpose, not a sandbox [S: Arch wiki, comparison] Opposite goal: doos hides the host's /home by default.
gVisor A user-space kernel that answers most syscalls itself, cutting host-kernel exposure at an I/O cost [S: Edera, Northflank] Stronger isolation than doos, needs its own runtime. Vendor blogs; their numbers are not used here.
Kata Containers / Firecracker Each workload in its own KVM micro-VM with its own kernel [S: same sources, MASCOTS 2020 paper] The right tool for hostile or multi-tenant code. doos shares the host kernel, so a kernel bug is a doos escape.

3. Measurements

Method: a trivial command (true) started 10 times in a row after one warm-up run, average wall time per run, the Alpine image already present for every tool, one machine, one run set on 2026-10-23. Versions: doos 1.1.2, Docker 29.1.3, Podman 5.7.0, bubblewrap 0.11.1. The numbers come from scripts/bench.sh (make bench); the charts and the tables below are generated from its output (make charts), so they cannot drift from each other.

Horizontal bar chart of the time to start a trivial command for doos, bubblewrap, Podman and Docker, with and without network. doos is highlighted. Exact values are in the table below.

Setup Network Time per run
bubblewrap (host /usr) none 8.4 ms
doos Alpine image none 13.8 ms
doos host mode none 18.7 ms
Podman Alpine none 208 ms
Docker Alpine (rootful) none 344 ms
bubblewrap (host /usr) default 7.4 ms
doos Alpine image default 52.1 ms
doos host mode default 56.9 ms
doos Alpine, pasta default 64.6 ms
Podman Alpine default 241 ms
Docker Alpine (rootful) default 436 ms

How to read this honestly:

  • doos is fast partly because it does less: no image store database, no registry code, no layer unpacking per run, no container metadata service. The extra time over bubblewrap is the id map helpers (newuidmap, newgidmap), the overlay mounts, a hardening filter and the network helper process.
  • Docker here is a rootful daemon, so its numbers include a client-to-daemon round trip. Rootless Docker was not measured.
  • Run-to-run spread is real. An earlier ad-hoc run of the same loop measured rootful Docker at 590 ms (no network) and 456 ms (default network); the run charted above measured 344 ms and 436 ms. Read the order of magnitude, not a ranking between close values.
  • Startup is one number among many. It matters for scripts that start hundreds of short-lived sandboxes. It does not matter for a service that starts once.

Horizontal bar chart of executable sizes on a logarithmic axis for bubblewrap, crun, doos, the docker CLI and podman. doos is highlighted. Exact values are in the table below.

Executable Size
bubblewrap 79 KiB (80,424 bytes)
crun 472 KiB (483,392 bytes)
doos 552 KiB (565,680 bytes)
docker CLI 29.9 MiB (31,374,336 bytes)
podman 41.2 MiB (43,180,896 bytes)

Disk, measured with du (which cannot read overlayfs' mode-000 work directories, so true usage is slightly higher):

Item Size
doos Alpine image, extracted once and shared 8.3 MiB
doos Ubuntu base image, extracted once and shared 98 MiB
one doos container on top of an image a few KiB of metadata plus its own writes
four containers (one Alpine, three Ubuntu) on shared images 20 KiB together
doos host-mode container after its first create (skeleton, /etc allowlist, dpkg state) 4 MiB, created in 0.5 s
Podman storage on this machine (many unrelated images) 26.3 GB images, 1.8 GB containers

The Podman figure is this machine's accumulated state, not a like-for-like comparison. What the comparison does show is the model: doos extracts an image once and every container adds only its own writes.

4. doos versus Docker

Where doos is better

  • No daemon and no root. With Docker's default install, anyone who can talk to the daemon controls the machine [S]. doos has no such trust boundary: it can only do what the invoking user can do, plus what the newuidmap helper allows.
  • Tiny and auditable: 552 KiB, 3,445 lines of C++ [M], one process tree, no background service, nothing listening.
  • Instant: 14 to 65 ms against 344 to 436 ms for a rootful Docker run and 208 to 241 ms for Podman [M].
  • Safe defaults a user does not have to remember: Docker's default capability set, a seccomp filter, an allowlist of host /etc, a memory and pid cap, a written-data cap [D][M].
  • A "host mode" that reuses the host's installed tools (compilers, apt state) for throwaway shells with no image download [D].
  • A size cap that freezes instead of filling the disk [D][M]. Docker's writable-layer size limit needs the overlay2 driver on XFS with project quotas [S: Fabian Lee].

Where Docker is better

  • Everything around images: registries, docker build, layer caching, multi-arch, signing and scanning ecosystems [K]. doos cannot pull an arbitrary nginx:1.27 from a registry.
  • Host directories and devices into the container (-v, --device, GPUs). doos has no bind-mount option [D].
  • Long-running services: background containers, restart policies, healthchecks, logs, exec, stop [K]. In doos a container exists only while its first attach command runs [D].
  • Networking: user-defined networks, DNS between containers, published ports on any interface [K].
  • Compose, Swarm and the Kubernetes ecosystem [K].
  • macOS and Windows support through a VM [K].
  • Strongest default syscall posture: an allowlist, which denies unknown new syscalls. The doos filter is a denylist, so a new kernel syscall is allowed until it is added [D][S].

5. doos versus Podman

Podman is the closest comparison, since both are daemonless and rootless.

Where doos is better

  • Smaller, faster to start [M] and one binary with no storage database. Podman's rootless setup brings a store under ~/.local/share/containers with its own metadata [K].
  • Opinionated limits and hardening out of the box, with a stated, measured effect (OOM kill, pids, cpus, freeze at a size cap) [M].
  • Two network backends with different, documented trade-offs, including a host-like mode that keeps host abstract unix sockets out of the container [M].
  • Image verification is explicit: sha256 plus signatures checked against fingerprints pinned in the source [D][M].
  • Frozen-at-the-cap behaviour with a never-frozen rescue shell [D][M].

Where Podman is better

  • OCI compatibility end to end: pull, push, build with Buildah, pods, kube play, Quadlet and systemd integration [K]. It can also run images made for Docker unchanged.
  • Docker-compatible CLI and socket, so existing tooling mostly works [K].
  • Bind mounts, volumes, secrets, exec, logs, stop, restart policies [K].
  • Years of hardening, a security team, and a large user base finding the sharp edges. doos has one test machine.
  • Podman documents rootless caveats: ports below 1024, /etc/subuid setup, cgroup v2 only, and a rootless port forwarder that does not preserve client source IPs [S: podman-rootless(7)]. doos shares the subuid and cgroup v2 requirements [D]. Binding ports below 1024 in doos was not tested.

What both share: the same kernel features (user, mount, pid, network namespaces, overlayfs, cgroup v2, seccomp), the same pasta/slirp4netns options, and the same weakness: they share the host kernel [S].

6. Security, side by side

  • Shared kernel. doos, Docker, Podman, bubblewrap, nspawn and LXC all run container processes on the host kernel. A kernel vulnerability reachable through allowed syscalls is a host vulnerability. gVisor reduces that exposure at a performance cost; Kata and Firecracker remove it by giving each workload its own kernel [S].
  • Rootless is the real difference from rootful Docker. A bug that gives root inside a rootless doos or Podman container yields the invoking user's rights on the host, not root [D][S].
  • doos filter style. A denylist built at runtime with an architecture check, x32 guard and clone3 handling [D]. Docker and Podman ship allowlist profiles [S]. An allowlist is the more conservative design.
  • Missing layers. Docker applies an AppArmor profile (docker-default) on this machine [M: docker info lists apparmor]. doos installs no AppArmor or SELinux profile of its own; its only profile exists to permit user namespaces on Ubuntu 24.04+ [D].
  • Supply chain. doos pins publisher signing keys for Alpine and Ubuntu and checks digests for Debian [D][M]. Registry-based tools rely on registry trust, content trust or external signing tools [K].

6.1 Is doos a security-wise Docker replacement?

Short answer: it is in the same class as rootless Podman and rootless Docker by design, but it is not a replacement for running untrusted code.

Question Answer
Better than rootful Docker on privilege? Yes. No root daemon, nothing runs as root on the host, so an escape yields the invoking user's rights, not root [D][S].
Equal to rootless Podman or Docker? Not proven. The mechanism is the same (user namespaces, seccomp, dropped capabilities, cgroup v2), the maturity is not [D][K].
What the default confines 14 capabilities (Docker's default set, CapEff a80425fb), no_new_privs, seccomp denylist of 48 syscalls, cgroup memory/pids/cpu limits, a written-data cap, no host /home, host loopback blocked in slirp [M].
Weaker than Docker or Podman Denylist instead of an allowlist (a new dangerous syscall is allowed by default); no per-container AppArmor or SELinux profile; host mode uses the host's own /usr and an /etc allowlist; --net host shares abstract unix sockets [D].
Kernel exposure Shared kernel, and user namespaces enlarge the reachable kernel surface, which is why Ubuntu 24.04 restricts them by default [S].
Assurance One developer, one machine (Linux x86_64), no external audit, no CVE history, Debian images verified by digest only [M][D].

Use doos for your own tools, builds and experiments, where you accept the risk. For code you do not trust, or for several tenants, use rootless Podman with its default seccomp and SELinux or AppArmor, and gVisor or Kata when the kernel must not be shared (see the conclusion table in section 8). Functionally it is also not a drop-in replacement: see section 7.

Full list of what doos does not cover is at the end of RESEARCH; the exact hardening is in SPEC under Hardening. Named CVE regression checks against build/doos live in audit/README.md (make audit).

7. What doos lacks, and which code would change

These are the gaps that matter when comparing, each next to where it lives. They are descriptions, not promises.

Gap Where
No OCI registry pull, no layers, no build src/image.cpp (Catalog, ImageStore)
No host directory bind mounts, no devices, no GPU setupRoot in src/sandbox.cpp, option parsing in src/cli.cpp
No background containers, stop, exec by name, logs lifetime model in Sandbox::start / Sandbox::watch (container lives with its first command)
No user-defined networks, no -p src/network.cpp
No Compose or Kubernetes integration not present
Environment, user, working-directory flags missing applyLimitsAndEnv in src/sandbox.cpp
Linux x86_64 tested only src/hardening.cpp guards aarch64, never run there
seccomp is a denylist deniedSyscalls in src/hardening.cpp
Test coverage: one machine tests/run.sh

8. Conclusion

doos is not a Docker replacement and does not try to be. It is a small, rootless, daemonless tool for disposable or per-user Linux shells and sandboxes that start in tens of milliseconds, with safe defaults and hard caps already set. Its strengths are the ones the measurements show: it is a 552 KiB single binary, it starts in 14 to 65 ms [M], it runs with no daemon and no root, it caps memory, processes, CPU and written data by default, and it verifies what it downloads.

Choose by the job:

If you need Use
A quick, isolated shell or script runner for one user, host tools or a distro image, with limits, no daemon doos
OCI images, Dockerfiles or Containerfiles, registries, CI that matches production Podman (rootless, daemonless) or Docker
Compose, Kubernetes parity, Docker ecosystem tooling Docker or Podman with its Docker compatibility
Sandbox one application with exact control over mounts bubblewrap
A full OS with init and several services, long lived Incus / LXC or systemd-nspawn
Shared HPC cluster, MPI, GPUs, signed single-file images Apptainer
A desktop environment tied to your home directory Distrobox / Toolbx
Running code you do not trust at all, or multi-tenant workloads Kata / Firecracker (or gVisor as a middle step), not any shared-kernel container

Honest limits of this comparison: one machine, 10-run averages, no rootless Docker, no systemd-nspawn, Incus or Apptainer measurements, and several Docker and Podman capabilities listed as general knowledge rather than re-verified. The conclusion about when to use doos rests on what the doos tests and measurements show (RESEARCH), not on a claim that it beats mature tools on features. On features it does not.

Back to README for usage, SPEC for the exact behavior, ARCHITECTURE for how it works, AGENTS for the rules, and CHANGELOG for history.