spike(macos): rootless containers in bottles — Docker impossible, podman works #449

Closed
didericis-codex wants to merge 5 commits from spike/rootless-docker-macos into main
Collaborator

Summary

Spikes guest-local rootless Docker for macOS Apple-container bottles behind an opt-in docker_access: true manifest flag.

Unlike Docker socket sharing, this keeps the daemon, Compose services, images, volumes, and networks inside the existing per-bottle VM. It does not mount Docker Desktop's socket, expose Apple's container API, or add NET_ADMIN, NET_RAW, SYS_ADMIN, or --privileged to the outer bottle.

Shape

  • layers Docker Engine rootless prerequisites and Compose v2 onto a derived image only for enabled bottles;
  • retains the normal provider image unchanged for all other bottles;
  • starts dockerd-rootless.sh as the unprivileged node user after source-IP/token registration;
  • uses a guest-local Unix socket and injects the existing authenticated egress proxy into daemon/build/child-container configuration;
  • fails closed if subordinate IDs, rootless helpers, or daemon startup do not work without added outer privilege;
  • carries docker_access through manifest validation and extends merging.

Validation

  • 1,959 unit tests pass.
  • Targeted pylint: 10/10.
  • Targeted pyright: clean.
  • Shell syntax and git diff --check: clean.
  • Adds an explicitly gated live-Mac integration test covering Compose pull/up, service DNS/path semantics, a bind-mounted workspace, localhost published ports, rootless security reporting, absence of /var/run/docker.sock, and denial of direct unproxied child egress.

Known spike boundary

This workspace is Linux, so the Apple Container kernel/user-namespace feasibility test could not run here. On a live Mac:

BOT_BOTTLE_ROOTLESS_DOCKER_SPIKE=1 \
  python3 -m unittest tests.integration.test_macos_rootless_docker_spike -v

The PR should not be treated as production-supported until that test passes. If Apple Container requires any forbidden outer capability, the spike is designed to fail rather than weaken the bottle boundary.

Closes neither the broader capability issue nor the remote-worker fallback.

Refs #392.

## Summary Spikes guest-local rootless Docker for macOS Apple-container bottles behind an opt-in `docker_access: true` manifest flag. Unlike Docker socket sharing, this keeps the daemon, Compose services, images, volumes, and networks inside the existing per-bottle VM. It does not mount Docker Desktop's socket, expose Apple's `container` API, or add `NET_ADMIN`, `NET_RAW`, `SYS_ADMIN`, or `--privileged` to the outer bottle. ## Shape - layers Docker Engine rootless prerequisites and Compose v2 onto a derived image only for enabled bottles; - retains the normal provider image unchanged for all other bottles; - starts `dockerd-rootless.sh` as the unprivileged `node` user after source-IP/token registration; - uses a guest-local Unix socket and injects the existing authenticated egress proxy into daemon/build/child-container configuration; - fails closed if subordinate IDs, rootless helpers, or daemon startup do not work without added outer privilege; - carries `docker_access` through manifest validation and extends merging. ## Validation - 1,959 unit tests pass. - Targeted pylint: 10/10. - Targeted pyright: clean. - Shell syntax and `git diff --check`: clean. - Adds an explicitly gated live-Mac integration test covering Compose pull/up, service DNS/path semantics, a bind-mounted workspace, localhost published ports, rootless security reporting, absence of `/var/run/docker.sock`, and denial of direct unproxied child egress. ## Known spike boundary This workspace is Linux, so the Apple Container kernel/user-namespace feasibility test could not run here. On a live Mac: ```sh BOT_BOTTLE_ROOTLESS_DOCKER_SPIKE=1 \ python3 -m unittest tests.integration.test_macos_rootless_docker_spike -v ``` The PR should not be treated as production-supported until that test passes. If Apple Container requires any forbidden outer capability, the spike is designed to fail rather than weaken the bottle boundary. Closes neither the broader capability issue nor the remote-worker fallback. Refs #392.
didericis-codex added 1 commit 2026-07-21 01:54:39 -04:00
feat(macos): spike rootless Docker inside bottles
test / integration-docker (pull_request) Successful in 13s
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / unit (pull_request) Successful in 37s
test / stage-firecracker-inputs (pull_request) Successful in 2s
lint / lint (push) Successful in 2m44s
test / build-infra (pull_request) Successful in 3m38s
test / integration-firecracker (pull_request) Successful in 2m7s
test / coverage (pull_request) Failing after 1m59s
test / publish-infra (pull_request) Has been skipped
72e35a1343
Add an opt-in docker_access path that layers rootless Docker tooling onto the selected agent image, starts the daemon only after registration, and retains the existing outer network and capability boundary. Include fail-closed bootstrap checks plus a live-Mac Compose/security acceptance test.\n\nRefs #392.
didericis added 1 commit 2026-07-21 02:09:46 -04:00
fix(macos): normalize rootless Docker subordinate IDs
tracker-policy-pr / check-pr (pull_request) Successful in 8s
test / integration-docker (pull_request) Successful in 9s
test / unit (pull_request) Successful in 32s
lint / lint (push) Successful in 46s
test / stage-firecracker-inputs (pull_request) Successful in 2s
test / build-infra (pull_request) Successful in 3m23s
test / integration-firecracker (pull_request) Successful in 1m37s
test / coverage (pull_request) Failing after 1m24s
test / publish-infra (pull_request) Has been skipped
6c7b9c2f31
didericis added 1 commit 2026-07-21 02:20:20 -04:00
test(macos): cover rootless image launch wiring
test / stage-firecracker-inputs (pull_request) Successful in 4s
test / integration-docker (pull_request) Successful in 13s
tracker-policy-pr / check-pr (pull_request) Successful in 11s
test / unit (pull_request) Successful in 35s
lint / lint (push) Successful in 2m33s
test / build-infra (pull_request) Successful in 3m24s
test / integration-firecracker (pull_request) Successful in 1m35s
test / coverage (pull_request) Successful in 1m35s
test / publish-infra (pull_request) Has been skipped
a4d8461081
didericis reviewed 2026-07-21 12:57:45 -04:00
@@ -0,0 +54,4 @@
log=/tmp/bot-bottle-rootless-docker.log
nohup dockerd-rootless.sh \
--storage-driver=fuse-overlayfs \
>"$log" 2>&1 </dev/null &
Owner

@didericis-claude This was only needed for the rootless docker test, correct? podman doesn't require this? Further clarify whether podman required anything in this PR and whether this issue is worded correctly/podman would enable docker compose (minus the token issue)

@didericis-claude This was only needed for the rootless *docker* test, correct? podman doesn't require this? Further clarify whether podman required anything in this PR and whether [this issue](https://gitea.dideric.is/didericis/bot-bottle/issues/451) is worded correctly/podman would enable docker compose (minus the token issue)
didericis added 2 commits 2026-07-21 12:59:19 -04:00
Rootless Docker cannot run in an Apple Container bottle: the runtime's
capability bounding set omits CAP_SYS_ADMIN, which the kernel requires
to write a multi-range uid_map via newuidmap. Every other prerequisite
(setuid bit, subuid ranges, initial userns, no nosuid, no seccomp) was
already correct, so this is not a packaging gap we can close.

Also records two defects found while validating the podman alternative:

- a large download OOM-kills the shared egress proxy, which buffers
  whole response bodies to scan them; the gateway is a per-host
  singleton with no restart-on-death, so one bottle's pull removes
  egress for every bottle
- registry auth collides with the proxy's unconditional Authorization
  strip, measured: a valid bearer token behaves exactly like sending
  none

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GnMp8SUGH57hZX7192Rv2F
feat(macos): replace rootless Docker spike with rootless podman
test / stage-firecracker-inputs (pull_request) Successful in 2s
test / integration-docker (pull_request) Successful in 32s
test / unit (pull_request) Successful in 36s
lint / lint (push) Failing after 52s
test / build-infra (pull_request) Successful in 4m5s
tracker-policy-pr / check-pr (pull_request) Successful in 8s
test / integration-firecracker (pull_request) Successful in 2m2s
test / coverage (pull_request) Successful in 2m7s
test / publish-infra (pull_request) Has been skipped
55def3732e
Podman avoids the CAP_SYS_ADMIN requirement that makes rootless Docker
impossible here: with no subordinate UID range configured it falls back
to a single-UID self-mapping, which an unprivileged process may write
itself, so newuidmap is never invoked. The image build therefore strips
/etc/subuid and /etc/subgid entries rather than adding them.

The agent-facing surface is unchanged — docker and docker compose talk
to podman's Docker-compatible API socket.

Two device nodes need relaxing as root inside the bottle (0666 on
/dev/fuse and /dev/net/tun); both already exist and neither needs a
capability the bottle lacks, unlike CAP_SYS_ADMIN.

Verified live on macOS 26 / Apple Container 1.0.0: service starts with
zero added capabilities, compose pulls and serves from the workspace on
a published port, and a nested container cannot reach the network
outside the egress path.

Known limitation, not yet addressed: the agent base image is Debian
bookworm, whose podman 4.3.1 swallows container exit codes through the
compat API (docker run returns 0 regardless). podman 5.4.2 on trixie
fixes it; the base bump is a separate PR. The acceptance test asserts
on an in-band marker rather than an exit code so it cannot false-pass
in the meantime.

Nested containers give no isolation from the agent itself — root inside
one is the agent user outside it. They are a build/test convenience;
the bottle remains the security boundary.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GnMp8SUGH57hZX7192Rv2F
Collaborator

Spike conclusion: rootless Docker is impossible here; we're going with podman

Pushed as 6a22b9f (research notes) and 55def37 (implementation). Full write-ups in
docs/research/rootless-docker-in-apple-container-spike.md and
docs/research/egress-proxy-oom-on-large-downloads.md.

Why rootless Docker cannot work

Not a packaging gap. Apple Container's capability bounding set omits CAP_SYS_ADMIN:

CapBnd: 00000000a80425fb
= chown,dac_override,fowner,fsetid,kill,setgid,setuid,setpcap,
  net_bind_service,net_raw,sys_chroot,mknod,audit_write,setfcap

The kernel gates writing a multi-range uid_map on file_ns_capable(file, ns, CAP_SYS_ADMIN).
newuidmap is setuid-root, but its capabilities are clamped by that bounding set, and the
"creator euid == ns owner" shortcut in cap_capable() doesn't apply (the namespace is created
by uid 1000 while newuidmap runs as euid 0). Result: EPERM.

Everything else was already correct — setuid bit intact through the OCI export, / not nosuid,
NoNewPrivs: 0, no seccomp, subuid/subgid populated, initial userns, unshare -U -r working.
Adding --cap-add CAP_SYS_ADMIN clears it instantly, which is precisely what we don't want to do:
it's roughly "root" in practical terms and would undercut the containment the backend is built on
(--cap-drop CAP_NET_RAW, no NET_ADMIN, host-only agent network).

This is the same constraint that makes upstream's dind-rootless require --privileged.

Why podman works

With no subordinate UID range configured, podman falls back to a single-UID self-mapping,
which an unprivileged process may write itself — newuidmap is never invoked. So the image build
strips /etc/subuid and /etc/subgid rather than populating them.

Verified live on macOS 26.5.1 / Apple Container 1.0.0, zero added capabilities: service starts,
docker compose up -d --wait pulls and serves the workspace on a published port, podman info
reports rootless, no host Docker socket, and a nested container cannot reach the network outside
the egress path. Agent-facing surface is unchanged — docker and docker compose talk to podman's
Docker-compatible API socket.

The tradeoff to be explicit about

The single-UID mapping means nested containers give no isolation from the agent itselfroot
inside a nested container is the agent user outside it. It cannot escalate to bottle-root (namespaced
capabilities only apply to UIDs mapped into the namespace, and only uid 1000 is), but it can do
anything the agent can: read the workspace, read ~/.claude, etc. Nested containers are a build/test
convenience, not a security boundary. The bottle remains the boundary.

Three defects found on the way, none of them about containers

  1. Egress proxy OOM-DoS — a ~165MB download made mitmdump grow to ~1GB RSS and get OOM-killed
    (Memory cgroup out of memory: Killed process 118 (mitmdump) ... anon-rss:997768kB). The gateway
    is a per-host singleton with no restart-on-death, so one bottle's pull removes egress for every
    bottle until the container is recreated. Documented, not fixed — the root-cause fix (a body-size
    threshold on scanning) is a security decision, since "skip above N" trades a DoS for a covert channel.
  2. Registry auth vs the Authorization strip — the proxy strips agent-set Authorization
    unconditionally, and Docker Hub/GHCR need a client-fetched, per-scope bearer token. Measured in a
    bottle: token fetch 200, manifest request with that valid token 401, manifest request with
    no auth 401 — identical, so the header never arrives. Registries that skip the token dance
    (quay 200, MCR 200, registry.k8s.io 307) work today with a plain route; the test uses quay.
  3. podman 4.3.1 swallows exit codes through the compat API — docker run ... 'exit 7' returns
    0. podman 5.4.2 returns 7. This silently defeated the acceptance test's containment assertion,
    which now checks an in-band marker instead. Base bump tracked separately (#451).
## Spike conclusion: rootless Docker is impossible here; we're going with podman Pushed as `6a22b9f` (research notes) and `55def37` (implementation). Full write-ups in `docs/research/rootless-docker-in-apple-container-spike.md` and `docs/research/egress-proxy-oom-on-large-downloads.md`. ### Why rootless Docker cannot work Not a packaging gap. Apple Container's capability bounding set omits `CAP_SYS_ADMIN`: ``` CapBnd: 00000000a80425fb = chown,dac_override,fowner,fsetid,kill,setgid,setuid,setpcap, net_bind_service,net_raw,sys_chroot,mknod,audit_write,setfcap ``` The kernel gates writing a multi-range `uid_map` on `file_ns_capable(file, ns, CAP_SYS_ADMIN)`. `newuidmap` is setuid-root, but its capabilities are clamped by that bounding set, and the "creator euid == ns owner" shortcut in `cap_capable()` doesn't apply (the namespace is created by uid 1000 while `newuidmap` runs as euid 0). Result: `EPERM`. Everything else was already correct — setuid bit intact through the OCI export, `/` not `nosuid`, `NoNewPrivs: 0`, no seccomp, subuid/subgid populated, initial userns, `unshare -U -r` working. Adding `--cap-add CAP_SYS_ADMIN` clears it instantly, which is precisely what we don't want to do: it's roughly "root" in practical terms and would undercut the containment the backend is built on (`--cap-drop CAP_NET_RAW`, no `NET_ADMIN`, host-only agent network). This is the same constraint that makes upstream's `dind-rootless` require `--privileged`. ### Why podman works With **no** subordinate UID range configured, podman falls back to a single-UID self-mapping, which an unprivileged process may write itself — `newuidmap` is never invoked. So the image build *strips* `/etc/subuid` and `/etc/subgid` rather than populating them. Verified live on macOS 26.5.1 / Apple Container 1.0.0, **zero added capabilities**: service starts, `docker compose up -d --wait` pulls and serves the workspace on a published port, `podman info` reports rootless, no host Docker socket, and a nested container cannot reach the network outside the egress path. Agent-facing surface is unchanged — `docker` and `docker compose` talk to podman's Docker-compatible API socket. ### The tradeoff to be explicit about The single-UID mapping means **nested containers give no isolation from the agent itself** — `root` inside a nested container is the agent user outside it. It cannot escalate to bottle-root (namespaced capabilities only apply to UIDs mapped into the namespace, and only uid 1000 is), but it can do anything the agent can: read the workspace, read `~/.claude`, etc. Nested containers are a build/test convenience, not a security boundary. The bottle remains the boundary. ### Three defects found on the way, none of them about containers 1. **Egress proxy OOM-DoS** — a ~165MB download made `mitmdump` grow to ~1GB RSS and get OOM-killed (`Memory cgroup out of memory: Killed process 118 (mitmdump) ... anon-rss:997768kB`). The gateway is a per-host singleton with no restart-on-death, so one bottle's pull removes egress for every bottle until the container is recreated. Documented, not fixed — the root-cause fix (a body-size threshold on scanning) is a security decision, since "skip above N" trades a DoS for a covert channel. 2. **Registry auth vs the Authorization strip** — the proxy strips agent-set `Authorization` unconditionally, and Docker Hub/GHCR need a client-fetched, per-scope bearer token. Measured in a bottle: token fetch `200`, manifest request *with* that valid token `401`, manifest request with *no* auth `401` — identical, so the header never arrives. Registries that skip the token dance (quay `200`, MCR `200`, registry.k8s.io `307`) work today with a plain route; the test uses quay. 3. **podman 4.3.1 swallows exit codes** through the compat API — `docker run ... 'exit 7'` returns **0**. podman 5.4.2 returns 7. This silently defeated the acceptance test's containment assertion, which now checks an in-band marker instead. Base bump tracked separately (#451).
Collaborator

Replying to your review comment on rootless-docker-init.sh.

"This was only needed for the rootless docker test, correct? podman doesn't require this?"

Correct, and podman requires the exact opposite. That script asserted a subordinate range existed:

grep -q "^$(id -un):.*:65536$" /etc/subuid || exit 1

The podman init asserts one does not:

if grep -q "^$(id -un):" /etc/subuid 2>/dev/null; then
    echo "unexpected subordinate UID range ... podman would require CAP_SYS_ADMIN via newuidmap" >&2
    exit 1
fi

A subuid range is precisely what pushes podman onto newuidmap, which is the call that cannot
succeed in this guest. The file itself is deleted in this PR and replaced by rootless-podman-init.sh.

"Clarify whether podman required anything in this PR"

Yes — six things, none of them an outer privilege:

  1. Strip /etc/subuid and /etc/subgid for the agent user (the single-UID fallback).
  2. chmod 0666 /dev/fuse /dev/net/tun, as root inside the bottle at launch. Apple Container
    creates both nodes 0600 root:root. /dev/fuse blocks the fuse-overlayfs storage driver
    (otherwise you're on vfs, which copies whole layers per container); /dev/net/tun blocks
    slirp4netns, which rootless podman uses for the default bridge network that stock compose files
    expect. Both need no capability the bottle doesn't already hold — categorically different from
    CAP_SYS_ADMIN.
  3. storage.conf — overlay + mount_program=fuse-overlayfs + ignore_chown_errors=true. The
    last one is required, not cosmetic: with a single-UID mapping there is no second UID for image
    layers to be chowned to.
  4. containers.confcgroups="disabled", cgroup_manager="cgroupfs", events_logger="file".
    No cgroup delegation or journald socket reaches this guest.
  5. podman system service for the Docker-compatible API socket, so docker and docker compose
    keep working unchanged.
  6. Packages: podman, fuse-overlayfs, slirp4netns, uidmap, plus the docker CLI and the
    compose plugin copied from docker:28-cli.

Note netns="host" in containers.conf does not propagate through the compat API — stock
docker run and compose files request bridge explicitly — so slirp4netns (and the tun chmod) is
required for ordinary compose files to work at all.

"Would podman enable docker compose (minus the token issue)?"

Yes, verified live — docker compose up -d --wait pulls an image, starts a service, and serves the
workspace on a published port from inside a registered bottle. Three caveats worth knowing:

  • Docker Hub and GHCR are still blocked even with podman working, because of the Authorization
    strip (the "token issue"). quay/MCR/registry.k8s.io and unauthenticated internal registries work
    with a plain route today.
  • Nested containers currently have no outbound network of their own. In probing, DNS inside a
    nested container failed entirely (wget: bad address 'quay.io', nslookup → connection refused).
    So compose services that need egress won't work yet — they'd need proxy env, the CA, and DNS
    wired into the nested container. This is fail-closed, which is the right default, but it does
    bound what "enables docker compose" means today: running images and local services, yes; nested
    containers calling out, not yet.
  • A large pull OOM-kills the shared egress proxy (see the main comment). The acceptance test
    uses a tiny busybox image specifically to avoid tripping that.

Is #451 worded correctly?

Mostly, but three things I'd tighten:

  1. "old version of bookworm" — bookworm is Debian 12; it's not an old version of it. Trixie
    (Debian 13) is current stable. Suggest "on Debian 12 (bookworm); should be Debian 13 (trixie)".
    node:22-trixie-slim exists, so it's a one-line change in each of
    contrib/{claude,codex,pi}/Dockerfile — those three are the only images on bookworm; the gateway
    and orchestrator are already on trixie via python:3.12-slim.
  2. "most recent stable podman version" — ambiguous, and taken literally it's a bad idea. Latest
    upstream GA is 6.0.1 (6.0.0 GA 2026-06-24), but no distro packages it, upstream is still actively
    patching the 5.8 line (5.8.5 shipped the same day as 6.0.1), and quay.io/podman/stable:latest is
    still 5.8.2. Chasing 6 means a third-party repo or building from source — added supply-chain
    surface on every agent image for zero benefit here. Trixie's 5.4.2 already fixes the only
    version-sensitive defect we found (exit-code propagation). Suggest "podman ≥5.4 from the distro".
  3. "All agent images have podman" is not sufficient as an acceptance criterion — podman alone
    won't run. It also needs passt (podman 5's default network tool) and nftables (netavark shells
    out to nft; without it every run fails with unable to upgrade to tcp, received 500), plus
    fuse-overlayfs/slirp4netns, the subuid/subgid entries removed, and the launch-time device
    chmods. Suggest "agent images can start rootless podman" with a pointer to
    docs/research/rootless-docker-in-apple-container-spike.md.

Measured, for the record:

podman 4.3.1 (bookworm) podman 5.4.2 (trixie)
docker run … 'exit 7' (compat API) 0 7
podman run … 'exit 7' (native) 7 7
Replying to [your review comment on `rootless-docker-init.sh`](https://gitea.dideric.is/didericis/bot-bottle/pulls/449/files#issuecomment-4784). ### "This was only needed for the rootless *docker* test, correct? podman doesn't require this?" Correct, and podman requires the **exact opposite**. That script asserted a subordinate range existed: ```sh grep -q "^$(id -un):.*:65536$" /etc/subuid || exit 1 ``` The podman init asserts one does *not*: ```sh if grep -q "^$(id -un):" /etc/subuid 2>/dev/null; then echo "unexpected subordinate UID range ... podman would require CAP_SYS_ADMIN via newuidmap" >&2 exit 1 fi ``` A subuid range is precisely what pushes podman onto `newuidmap`, which is the call that cannot succeed in this guest. The file itself is deleted in this PR and replaced by `rootless-podman-init.sh`. ### "Clarify whether podman required anything in this PR" Yes — six things, none of them an outer privilege: 1. **Strip `/etc/subuid` and `/etc/subgid`** for the agent user (the single-UID fallback). 2. **`chmod 0666 /dev/fuse /dev/net/tun`**, as root inside the bottle at launch. Apple Container creates both nodes `0600 root:root`. `/dev/fuse` blocks the fuse-overlayfs storage driver (otherwise you're on `vfs`, which copies whole layers per container); `/dev/net/tun` blocks slirp4netns, which rootless podman uses for the default bridge network that stock compose files expect. Both need no capability the bottle doesn't already hold — categorically different from `CAP_SYS_ADMIN`. 3. **`storage.conf`** — overlay + `mount_program=fuse-overlayfs` + `ignore_chown_errors=true`. The last one is required, not cosmetic: with a single-UID mapping there is no second UID for image layers to be chowned to. 4. **`containers.conf`** — `cgroups="disabled"`, `cgroup_manager="cgroupfs"`, `events_logger="file"`. No cgroup delegation or journald socket reaches this guest. 5. **`podman system service`** for the Docker-compatible API socket, so `docker` and `docker compose` keep working unchanged. 6. **Packages**: `podman`, `fuse-overlayfs`, `slirp4netns`, `uidmap`, plus the `docker` CLI and the compose plugin copied from `docker:28-cli`. Note `netns="host"` in `containers.conf` does **not** propagate through the compat API — stock `docker run` and compose files request bridge explicitly — so slirp4netns (and the tun chmod) is required for ordinary compose files to work at all. ### "Would podman enable docker compose (minus the token issue)?" Yes, verified live — `docker compose up -d --wait` pulls an image, starts a service, and serves the workspace on a published port from inside a registered bottle. Three caveats worth knowing: - **Docker Hub and GHCR are still blocked** even with podman working, because of the Authorization strip (the "token issue"). quay/MCR/registry.k8s.io and unauthenticated internal registries work with a plain route today. - **Nested containers currently have no outbound network of their own.** In probing, DNS inside a nested container failed entirely (`wget: bad address 'quay.io'`, `nslookup` → connection refused). So compose *services that need egress* won't work yet — they'd need proxy env, the CA, and DNS wired into the nested container. This is fail-closed, which is the right default, but it does bound what "enables docker compose" means today: running images and local services, yes; nested containers calling out, not yet. - **A large pull OOM-kills the shared egress proxy** (see the main comment). The acceptance test uses a tiny busybox image specifically to avoid tripping that. ### Is #451 worded correctly? Mostly, but three things I'd tighten: 1. **"old version of bookworm"** — bookworm *is* Debian 12; it's not an old version of it. Trixie (Debian 13) is current stable. Suggest "on Debian 12 (bookworm); should be Debian 13 (trixie)". `node:22-trixie-slim` exists, so it's a one-line change in each of `contrib/{claude,codex,pi}/Dockerfile` — those three are the only images on bookworm; the gateway and orchestrator are already on trixie via `python:3.12-slim`. 2. **"most recent stable podman version"** — ambiguous, and taken literally it's a bad idea. Latest upstream GA is 6.0.1 (6.0.0 GA 2026-06-24), but no distro packages it, upstream is still actively patching the 5.8 line (5.8.5 shipped the same day as 6.0.1), and `quay.io/podman/stable:latest` is still 5.8.2. Chasing 6 means a third-party repo or building from source — added supply-chain surface on every agent image for zero benefit here. Trixie's **5.4.2** already fixes the only version-sensitive defect we found (exit-code propagation). Suggest "podman ≥5.4 from the distro". 3. **"All agent images have podman"** is not sufficient as an acceptance criterion — podman alone won't run. It also needs `passt` (podman 5's default network tool) and `nftables` (netavark shells out to `nft`; without it every run fails with `unable to upgrade to tcp, received 500`), plus `fuse-overlayfs`/`slirp4netns`, the subuid/subgid entries **removed**, and the launch-time device chmods. Suggest "agent images can start rootless podman" with a pointer to `docs/research/rootless-docker-in-apple-container-spike.md`. Measured, for the record: | | podman 4.3.1 (bookworm) | podman 5.4.2 (trixie) | |---|---|---| | `docker run … 'exit 7'` (compat API) | **0** | 7 | | `podman run … 'exit 7'` (native) | 7 | 7 |
didericis-claude changed title from feat(macos): spike rootless Docker inside bottles to spike(macos): rootless containers in bottles — Docker impossible, podman works 2026-07-21 13:03:33 -04:00
didericis-claude closed this pull request 2026-07-21 13:04:09 -04:00
Some optional checks failed
test / stage-firecracker-inputs (pull_request) Successful in 2s
test / integration-docker (pull_request) Successful in 32s
test / unit (pull_request) Successful in 36s
lint / lint (push) Failing after 52s
test / build-infra (pull_request) Successful in 4m5s
tracker-policy-pr / check-pr (pull_request) Successful in 8s
Required
Details
test / integration-firecracker (pull_request) Successful in 2m2s
test / coverage (pull_request) Successful in 2m7s
test / publish-infra (pull_request) Has been skipped

Pull request closed

Sign in to join this conversation.