Files
werkator/docs/adrs/0008-2026-09-01.bwrap-build-runtime.md
T
mhoennigandClaude Fable 5 89de3e0378 Step 21 session A: close step 17's paperwork
- PR-docs get their real numbers: bwrap-build-runtime -> PR#4,
  build-current-head-from-the-branches-view -> PR#3 (files and scenario IDs)
- tools/remote: header note marking install's clone step and build as the
  self-build prototype, superseded by session D of step 21 (usage range grown)
- ADR 0008: bubblewrap user-namespace sandbox as the third build runtime;
  step 17 and the plan README now point at 0008 (0007 was taken by build
  definitions before the step landed)
- AGENTS.md: decisions list catches up with ADR 0007 and ADR 0008
- architecture skill: BwrapBuildRunner paragraph (rootfs unpack, uid mapping,
  mount ordering, pinned keys) and the dispatcher's three-way routing
- plan README: step 21 entry now describes the werkdock/ subdirectory path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 06:21:29 +02:00

4.1 KiB

Bubblewrap User-Namespace Sandbox as the Third Build Runtime

Status:

  • proposed: 2026-08-10
  • accepted: 2026-09-01
  • rejected: -
  • superseded: -

Decision [accepted]: On hosts without root and without a Docker daemon — Hostsharing Managed Webspaces — builds run inside a bwrap (bubblewrap) user-namespace sandbox over a prepared rootfs archive, implemented by BwrapBuildRunner as the third runtime behind the BuildRunner interface. Filesystem isolation only: network, uid mapping, /proc, /dev, and /tmp are shared with the host by contract.

Note: plan step 17 announced this decision as "ADR 0007", but 0007 was taken by the build-definitions decision on 2026-08-28; it is recorded here as 0008.

Context and Problem Statement

Werkator's build sandbox was Docker (ADR 0004ff., plan step 11) or nothing (native). Managed Webspaces provide neither root nor a Docker daemon, so a Werkator instance there could only build with the host's own toolchains — no isolation, and no way to install the toolchain versions a project needs. The platform does provide unprivileged user namespaces and ships bubblewrap 0.8.0 (verified on h68, kernel 6.1), which allows mounting a self-prepared root filesystem without any privilege.

Technical Background

BwrapBuildRunner shells out to the bwrap CLI — same pattern as git and docker, no library. The rootfs comes from a project-built archive (tools/build-bwrap-rootfs.sh), unpacked on demand into .git/werkator/buildenv/<envKey>/rootfs and bound read-only at /; uid 0 inside maps to the calling user. The git metadata mounts of step 16 are reused unchanged: read-only .git, tmpfs mask over .git/werkator/, read-write worktree admin directory — so secrets stay outside the sandbox exactly as in Docker builds. bwrap creates bind mountpoints against the sandbox view, so every mountpoint must exist in (or be pre-created in) the rootfs; the runner handles that. Config: bwrap.enabled/bwrap.rootfs are pinned like the docker sandbox policy — a branch can never turn its sandbox off or swap the rootfs; docker and bwrap are mutually exclusive per build and rejected loudly, never picked silently. Floor: bubblewrap 0.8.0 has no --overlay (added in 0.9.0), so throwaway writable layers are built from tmpfs/bind mounts, not overlays.

Considered Options

  • bwrap user-namespace sandbox (prepared rootfs, filesystem isolation only)
  • proot / fakechroot (syscall- or libc-level path rewriting)
  • plain native with hand-installed toolchains (no isolation)

bwrap User-Namespace Sandbox

Good:

  • Real kernel-level mount isolation without root; works with what the platform already ships.
  • The prepared-rootfs model gives every project its own toolchain versions, like a Docker image does.
  • Shelling out to a CLI matches the existing git/docker access pattern; the attached process supports log streaming and cancellation unchanged.

Bad:

  • The rootfs archive is project infrastructure that must be built and uploaded (~1.5 GB unpacked, disk/quota checked by tools/werkator-build-prerequisites.sh).
  • Filesystem-only isolation: network and process view are the host's — acceptable here, and pinned so a branch cannot widen it, but weaker than Docker.
  • Mountpoint pre-creation and mount ordering are subtle (hardened on the real webspace; see the fix messages preserved in commit 71f1fc6).

proot / fakechroot

Rejected: syscall tracing (proot) is an order of magnitude slower and historically fragile with modern toolchains; fakechroot's LD_PRELOAD path rewriting breaks on statically linked tools and does not isolate anything the kernel enforces.

Plain Native with Hand-Installed Toolchains

Rejected: no isolation, host pollution, and toolchain versions become webspace-global instead of per project — exactly the situation the sandbox exists to end.

Decision Outcome

bwrap, as implemented in PR #4. The generic sandbox machinery (rootfs build, prerequisites check, invocation logic) is planned to be extracted into the standalone tool Werkdock (plan step 21); BwrapBuildRunner will then delegate to the werkdock CLI. That extraction changes the executor behind the config keys, not this decision.