Add opt-in Docker-in-Docker worker image so agents can spin up disposable stacks #32
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lz/agent-nexus#32
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Motivation
Agents working on infrastructure or full-stack features routinely need to bring up disposable Docker stacks — to smoke-test a
docker composechange, to validate that a new image builds and boots, or (the immediate driver) to end-to-end-smoke a UI feature of Nexus itself against a fresh Nexus instance without touching the operator's live one.The current worker image has no Docker access. Mounting the host
/var/run/docker.sockis off the table — it makes worker→host container escape trivial, and the existingtecnativa/docker-socket-proxyallowlist (Nexus's own gateway to Docker) is intentionally narrow and host-Nexus-only.This issue proposes an opt-in Docker-in-Docker (dind) worker image that runs its own
dockerdinside a sidecar pattern, isolated from the host's Docker. The agent gets a fully-functionaldockerCLI anddocker compose, but the resulting containers live in the worker's own daemon — no host escape.Companion to #2 (Playwright MCP). The two together close the loop on agent-driven end-to-end testing: spin up a stack with dind, drive its UI with Playwright. They don't depend on each other.
Scope
nexus-worker-dind:latest, derived fromnexus-worker:latest.dockerCLI on$PATHanddocker compose(v2 plugin)./var/lib/dockertmpfs or volume per worker, ephemeral by default. Persistence is out of scope.vfsfor simplicity (no overlay-on-overlay woes); upgrade tofuse-overlayfsif vfs perf becomes a blocker.Threat model
docker0inside the worker). Containers it spawns are reachable from the worker but not from the host'snexus-workersbridge — they live behind dind's NAT inside the worker's network namespace. This is the intended isolation.docker system prunehabit. A hard quota is out of scope for v1.notify-preview— that CLI runs in the worker namespace, so when the agent mapsdind:3001 → worker:3001viadocker run -p,notify-preview 3001surfaces it to the operator unchanged.Verified facts (worth knowing before implementing)
--privilegedis required. Dind without privileged fails atiptablessetup (noCAP_NET_ADMIN). The dockerlesssysboxalternative is a custom runtime — out of scope for a stock Docker host./var/lib/dockerMUST NOT be on overlayfs. Overlay-on-overlay is broken on most kernels. Either use a named volume (ext4-backed), a tmpfs (smaller stacks fit fine), or set--storage-driver vfsexplicitly.cgroupfsmount. Recent Docker handles this automatically via--cgroupns=hostor the officialdocker:dindimage's entrypoint; if we run dockerd ourselves we need to set--exec-opt native.cgroupdriver=cgroupfsand ensure systemd isn't fighting it.dockerto work the moment it opens a session. The existingworker/entrypoint.shalready has a pre-tmux setup block (where forgejo-mcp registration lives) — dockerd boot fits there.docker composeis a plugin (not a separate binary) on Debian-derived images. Install viadocker-ce-cli+docker-compose-pluginapt packages, NOT the standalonedocker-composePython script.pnpm exec playwright install-style binary downloads can blow tmpfs/var/lib/docker. Plan storage size for the realistic agent workload (image builds with multi-hundred-MB layers).Suggested implementation
1. New worker image variant
worker/Dockerfile.dind, extending the base image:CI: add to
.forgejo/workflows/ci.ymlbuild matrix alongside the proposed Playwright variant from #2.2. Entrypoint augmentation
worker/entrypoint-dind.shruns the existing boot sequence, then before tmux setup:Then delegate to the existing entrypoint's tmux setup. The dockerd process becomes a child of pid 1; the existing entrypoint already exits after tmux idles, so dockerd inherits the standard process tree.
3. Spawn-time HostConfig
Nexus container spawn at nexus/src/lib/server/workers/service.ts:
HostConfig.Privileged: truefornexus-worker-dind:latest(and ONLY this image — never for default workers).HostConfig.SecurityOpt: ['seccomp=unconfined'](some kernels need this for the inner dockerd to set up netns)./var/lib/docker, NOT a bind mount. Ephemeral with the container.worker_imagevalidation to includenexus-worker-dind:latest.4. Image picker UI
Extend the workspace-create form (already proposed for the Playwright variant in #2) with a third radio option: "With Docker (dind)". If #2 ships first, this is purely additive — same widget, one more value. If this issue ships first, the widget is born.
5. CLAUDE.md guidance
Add a section to the agent-facing
default-CLAUDE.md:Out of scope
/var/lib/docker. Smoke-test workloads are ephemeral by nature; if persistence becomes a feature ask, add it then.--privileged. Stock Docker host doesn't have them; revisit if security posture changes.docker system prunehabit; add quotas later if abuse is observed.Verification
docker build -f worker/Dockerfile.dind -t nexus-worker-dind:latest worker/succeeds in CI.docker version— both client and server should report 24+ (or whatever ships). Rundocker run --rm hello-world— exits clean.git clone <agent-nexus repo>→cd nexus && docker build .. Should succeed (vfs is slow but correct).docker compose up -don the project's own compose file. Opennotify-preview 3001 "inner nexus". Operator should see the pending preview, approve it, and reach the inner Nexus through their browser.docker compose down -v. Container's/var/lib/dockershrinks (or doesn't, with vfs; that's fine — the worker volume is anonymous and will be GC'd with the worker).Open questions
nexus-worker-dind-playwright:latest) or — if we want fewer combinations — accept that the agent installs Playwright into a fresh container inside dind on demand. I'd start with separate variants and combine only if usage warrants.