Skip to main content
Everything here was hit for real while producing this manual. Each caveat says what happens, why, and what to do about it.

A box has open OUTBOUND network BY DEFAULT

A box gets a network namespace of its own, but a recipe that says nothing about egress gets full outbound access through it — so an unconfined box can phone home, and code you do not trust must not be run in one on the strength of the filesystem boundary alone. What the namespace does give you is separation from your host: the box cannot reach a service on your host’s loopback, and a port it binds is its own. Confine the outbound side in the recipe: egress: none (no network at all), or an egress: mapping with allow/deny (a CONNECT gate for every protocol) and an optional http_proxy content chain. See Egress. Each mode has its own host dependencies. egress: none needs nothing. Proxy egress needs bun and openssl on the host PATH and a forwarder binary — embedded in the CLI, or supplied by a program embedding dabs as a module (recipe schema). On Linux (the bwrap driver) egress: open needs pasta — install the passt package — and dabs running as an unprivileged user; a boot that can have neither refuses and says so rather than handing the box your host’s network. pasta must be snapshot 2025_05_03 or newer, for the address flags dabs passes: Debian trixie+ and Fedora 41+ package a new enough one, Ubuntu’s and Alpine’s current packages refuse those flags, and there you build from passt.top at the version contrib/recipes/dabseption.Dockerfile pins.

Boxes accumulate

Every dabs recipe … --no-command is a new box, and nothing reaps it for you. Agents are the worst offenders: a driver that boots a box per task — or aborts mid-task before its own dabs rm — leaves live boxes (and their processes) piling up behind it. Hundreds of leftover boxes will eventually destabilize whatever they run on. If you are wiring an agent to dabs, make cleanup structural, not polite:
  • reap in a teardown path that runs even when the task fails (dabs rm <box> -y), never only at the happy end of a script;
  • sweep periodically: dabs ls and reap what nothing points at, dabs rm --clean-worktrees for reviewed worktrees, dabs rm --inactive for empty markers;
  • one box per agent, and the spawner owns the reap.
copy:/scratchbox recipes have the same shape with workdir nodes: every boot snapshots the cwd into a new node that deliberately survives the box. There is no --clean-workdirs sweep; list and reap them by name.

Pristine again means a NEW box

Writes inside a box persist for that instance’s lifetime. Re-running a recipe does not reset an old box — boot a new one and reap the old.

Boxes are copies, not mounts, of their image

The image froze the program at the last dabs build. Edited your Dockerfile’s inputs? A boot auto-rebuilds only when the Dockerfile or a bundled image’s files change — a change only in COPY . context files is not detected. Run dabs prune, then build.

The box only contains what the Dockerfile installed

Slim images lack tools you may assume (ps, clear, pagers). If a job needs one, it belongs in the Dockerfile, not worked around. Two consequences seen in practice:
  • git log inside a minimal box opens a pager and appears to hang a driven terminal — use git --no-pager.
  • A box left up (--no-command, --detach) has a PID 1 that never reaps children, so processes killed inside it — and a --detach command that exits on its own — linger as zombies until the box itself is reaped.

Appending to a recipe is literal

dabs recipe <name> <cmd…> and dabs recipe -- <cmd…> append your tokens to the recipe’s command argv. Against command: [sh], -- echo hi runs sh echo hi (sh tries to open a file named echo). The confirmation shows the exact final command — read it before you say yes. And a default: naming an agent turns a bare append into arguments to that agent; set no default unless one recipe is the obvious choice.

--detach does not mean “boot and run nothing”

--no-command boots a box and runs nothing. --detach boots one and STARTS the recipe’s command in the background, with its output going to the node’s own tmp/detached.log rather than your terminal. They are different asks, and passing both is an error. --detach also needs a driver whose box carries a process of its own (docker, apple). dabs ASKS the driver before it provisions anything, so a driver that cannot hold one refuses with its own reason and points at the pair that does work everywhere:
A recipe with no command of its own has nothing to detach, which is a different refusal — and it names the flag that boots the box anyway:
The --detach screens are verified by walkthroughs/test_detach.py.

Instance names don’t say which recipe made them

The instance is named after the recipe’s image. Recipes sharing an image share a name prefix (claude, fresh-claude both boot a claude-… box), so dabs ls cannot tell you which recipe made a box. Name your images distinctively if the distinction matters.

Ambiguity errors speak two vocabularies

For the same ambiguous prefix, rm lists node ids while exec lists instance names — they refer to the same boxes. Cross-reference with dabs ls, which shows both.

Places dabs won’t work

  • dabs recipe refuses to make a project, worktree, or scratch node from inside ~/.dabs — marking dabs’s own node store as a project would re-render dabs’s tree inside itself. The exception is a dabs worktree’s checkout: a boot from in there parents the box on that worktree (and mounts its parent .git), so working inside one is a supported place to run from.
  • Keep build contexts under your home directory; contexts under system temp dirs have failed dabs build on macOS with cache-key errors.
  • (Nested/bwrap setups) dabs’s state cannot sit on overlayfs — bwrap cannot stack an overlay on one. In docker, put $HOME on a non-overlay volume.

Warning noise on staged images

A box booting from a pre-staged image (no builder present) prints image shell: no build record — rebuilding / no builder here to refresh it — serving it as-is on every boot. It means “serving the image as-is” — everything is fine.

Before 1.0, vocabulary moves

No backwards-compatibility guarantees before 1.0. Deprecations are docs-first (the old word keeps working while new work avoids it): fleet → drivers, consent → confirmation, ephemeral → held (with permanent $…_EPHEMERAL aliases), gone/no place/boxes with no node marked unstable. Agents migrating across versions should read the project’s GLOSSARY and CHANGELOG.