17 KiB
doos compared with Docker, Podman and others
Docs: README · SPEC · ARCHITECTURE · RESEARCH · COMPARISON · AGENTS · CHANGELOG
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 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: trivial command (true) started 10 times in a row, average wall time per run, warm caches, the Alpine image already present for every tool, one machine, one run set. Scripts were ad-hoc shell loops and are not part of the repository tests, so treat the numbers as indicative, not as a benchmark.
| Setup | Time per run |
|---|---|
bubblewrap, host /usr, all namespaces unshared, no network |
9 ms |
bubblewrap, host /usr, network shared |
8 ms |
doos host mode, --net none |
14 ms |
doos image Alpine, --net none |
16 ms |
| doos host mode, slirp | 54 ms |
| doos image Alpine, slirp | 57 ms |
| doos image Alpine, pasta | 65 ms |
Podman, Alpine, --network none |
214 ms |
| Podman, Alpine, default network | 234 ms |
Docker (rootful), Alpine, --network none |
590 ms |
| Docker (rootful), Alpine, default bridge | 456 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.
- The Docker row "none" is slower than "default" in this run; the spread between runs was not measured, so do not read a ranking into it.
- 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.
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
newuidmaphelper allows. - Tiny and auditable: 552 KiB, 3,445 lines [M], one process tree, no background service, nothing listening.
- Instant: 14 to 65 ms against 456 to 590 ms for a rootful Docker run [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,
aptstate) 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 arbitrarynginx:1.27from 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 firstattachcommand 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/containerswith 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/subuidsetup, 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
clone3handling [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 infolists 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].
Full list of what doos does not cover is at the end of RESEARCH; the exact hardening is in SPEC under Hardening.
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.