Skip to content

spike(devcontainer): Phase-0 feasibility gate — rootless nested A0 boot - #8

Merged
omar-nahhas merged 6 commits into
mainfrom
spike/devcontainer-phase0
Jun 20, 2026
Merged

omar-nahhas merged 6 commits into
mainfrom
spike/devcontainer-phase0

Conversation

@omar-nahhas

Copy link
Copy Markdown
Contributor

What

Phase-0 feasibility spike for the plugin quality & structure standard SPEC. It proves the single riskiest assumption in that design (Critical-4 in SPEC-REVIEW-001): that a devkit devcontainer (Playwright + rootless podman) can boot the Agent Zero container nested and unprivileged — no --privileged, no host docker.sock — and that the harness can reach + authenticate against it.

This is the gate for everything else (DEC-018 / DEC-031). If it's green, the devcontainer model is de-risked; if not, we take the documented self-hosted-runner fallback before Phase 1.

Contents

  • devcontainer/Containerfile — official Playwright base (node + browsers + libs) + rootless podman toolchain (fuse-overlayfs, uidmap, slirp4netns, crun), configured rootless for pwuser.
  • devcontainer/spike/run-acceptance.sh — (1) nested rootless container smoke test, (2) boot the real A0 image nested + wait for /login, (3) log in + authenticated fetch. A0 image is a parameter (DEC-019).
  • .github/workflows/devcontainer-spike.yml — builds the image and runs the acceptance on ubuntu-latest, rootless outer + rootless inner.

Evidence so far

The core mechanism was already confirmed on a real Linux host (rootless podman-in-podman, no --privileged):

podman run --device /dev/fuse --security-opt label=disable --user podman \
  quay.io/podman/stable  podman run --rm docker.io/library/alpine echo OK   # → OK

This PR lets the GitHub-hosted runner be the authoritative proof (booting the full A0 image nested).

Scope

Spike only — the full install → verify-installed → uninstall → verify-uninstalled cycle layers on top via the existing Playwright harness (Phase 2). Not wiring the reusable e2e workflow or bootstrap here.

🤖 Generated with Claude Code

omar-nahhas-agent and others added 6 commits June 19, 2026 23:42
Proves the Critical-4 assumption behind the plugin-quality-standard SPEC:
a devkit devcontainer (Playwright + rootless podman) can boot the Agent Zero
container NESTED and UNPRIVILEGED (no --privileged, no host docker.sock) and
the harness can reach + authenticate against it.

- devcontainer/Containerfile     Playwright base + rootless podman toolchain
- devcontainer/spike/run-acceptance.sh   nested boot + login acceptance
- .github/workflows/devcontainer-spike.yml   runs it on ubuntu-latest

Mechanism already confirmed on a real Linux host; this lets the GH runner be
the authoritative proof. Ref: SPEC DEC-018/031, Appendix E.6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n can't nest

Spike finding: the Playwright/Ubuntu-jammy base + apt podman 3.4.4 fails
rootless nesting ("cannot setup namespace using newuidmap"). podman/stable
(podman 5.8.2, pre-configured for rootless nesting) nests cleanly — verified
locally inside the built image (NESTED_IN_DEVKIT_OK, unprivileged).

Playwright browser deps deferred to Phase 1 (Fedora install); the Phase-0
gate is nesting, which needs no browser. Ref: SPEC DEC-039.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… enforcing)

CI proved the gate: on GH-hosted ubuntu-latest the devcontainer nests a
rootless container (rootless:true, step 1/3 ✓). Step 2 failed only on
"short-name resolution enforced but cannot prompt without a TTY" — podman/stable
requires fully-qualified names. Pin docker.io/agent0ai/agent-zero:latest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agnostics

CI cd3db4e proved nesting works on GH ubuntu-latest AND A0 booted nested
rootless (run_ui RUNNING, Flask serving) — but cold-CI first boot (pull +
initialize.sh clone + pip install) reached services right as the 300s window
expired. Bump BOOT_TIMEOUT→720s, job timeout→40m, and dump curl -v + podman
logs on timeout for clearer diagnosis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…its FATAL

CI run 156e82a: A0 booted nested rootless (run_ui RUNNING, Flask serving) but
sshd exits 255 under rootless podman; A0's supervisor event-listener (no args)
kills the whole instance on ANY process FATAL → A0 died ~8s after boot →
/login connection-refused for the rest of the window.

Replace /usr/sbin/sshd with a no-op before initialize.sh so run_sshd stays
RUNNING and never FATALs. SSH isn't used by the HTTP e2e. Watchdog stays armed
for genuinely-critical procs. Ref: SPEC DEC-040 / Appendix F.6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t-net

CI run 23ae917: A0 stayed up (sshd fix worked) but its UI couldn't bind
0.0.0.0:80 — privileged port, rootless mapped-root denied. Inner container
defaults to host netns so a --sysctl on the inner run is rejected.

Verified-working model (reproduced locally, REACHED_80_OK): outer devcontainer
run with --sysctl net.ipv4.ip_unprivileged_port_start=0, inner A0 with
--network=host, reach at localhost:80. Ref: SPEC DEC-041.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@omar-nahhas
omar-nahhas merged commit de54c49 into main Jun 20, 2026
1 check passed
@omar-nahhas
omar-nahhas deleted the spike/devcontainer-phase0 branch June 20, 2026 01:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant