Repository navigation
[upstream issue #512] Sandbox backends: which agent sandboxes can replace LayerFS's Docker container? Local stand-ins first #11
Description
[upstream issue Ephemeral-AI-Lab#512] Sandbox backends: which agent sandboxes can replace LayerFS's Docker container? Local stand-ins first
- Upstream: Sandbox backends: which agent sandboxes can replace LayerFS's Docker container? Local stand-ins first Ephemeral-AI-Lab/layerfs#512
- Type: issue
- State: OPEN
- Updated: 2026-10-30
- Upstream labels: -
Original body (excerpt)
Status (2026-10-30)
Every environment hosted real LayerFS. Route A (no Docker at all) passed end to end in Apple Container, Microsandbox and Firecracker. The proposed backend is in draft PR Ephemeral-AI-Lab#515.
| Environment | FUSE + privilege split | Store | Real LayerFS | Comments |
|---|---|---|---|---|
| Microsandbox | pass | per-VM ext4 disk; survives VM deletion | route B pass; route A pass | results, route A |
| Daytona OSS (local) | pass | loop ext4 inside the sandbox | route B pass (arm64 runner patched locally) | iteration 1, iteration 2 |
| Apple Container | pass with --cap-add SYS_ADMIN |
named ext4 volume, one VM per project | route B pass; route A pass | iteration 1, iteration 2, route A |
| Lima VM (EC2 stand-in) | pass | ext4 | route B pass | results |
| Firecracker guest (E2B/Vercel stand-in) | pass | second ext4 drive | route A pass; route B probe only | probes, route A |
What holds everywhere:
- One VM (or one privileged sandbox) per project, not per Sandbox. VMs don't share a kernel, so the Store goes on the project VM's own ext4 disk, and its daemons run inside it, each in its own mount namespace.
- Commands through
setpriv(uid:gid, no capabilities, no-new-privileges). Unmount is denied. - User namespaces must be blocked by the backend. No platform blocks them by default. Use
user.max_user_namespaces=0in a per-project VM, or a seccomp filter in the command launcher where the kernel is shared, as on Daytona. - The daemon endpoint can be reached privately on each platform: a loopback port forward, the SSH gateway, an exec stdio bridge, or vsock.
Open: hosted providers' own kernels and policies; a real backend in place of the test-only stand-in (provider adapter, persistence of backend state, seccomp and cgroups); the SDK outside the VM (the worker route).
Goal
LayerFS should be able to run on more than one Sandbox backend. Today it has one, Docker (core/crates/layerfs-sandbox/src/backend/docker/). This experiment finds out which agent-sandbox platforms could become a backend that replaces the Docker container and does every SDK operation the Docker backend does today. Start with environments that run on a developer Mac, then move to the hosted providers they stand in for.
(Revised 2026-10-30, same day: the first version asked mainly whether LayerFS's current Docker containers could run nested inside a provider. That is now a secondary route.)
Background
Today, for each Sandbox LayerFS starts one Docker container. Its daemon mounts the Workspace inside it and the agent's commands run there too. The container gives LayerFS:
- a real Linux kernel with FUSE: the daemon opens
/dev/fuseand callsmount(2)itself (core/crates/layerfs-fuse/src/session/startup.rs); - a privilege split: the daemon runs as root with
CAP_SYS_ADMINand mounts withallow_other, while commands run as a separate nonroot uid:gid with no capabilities,no-new-privileges, and a seccomp filter that denies user namespaces. So a command cannot unmount the Workspace or copy the mount into a namespace of its own (container.rs,topology.rs,core/docs/architecture/76-native-mount-session.md); - the Store on a local ext4, xfs or btrfs volume, shared by the project's daemons on one kernel. On Linux, SDK Store calls also run in the caller's own process.
Two routes per platform
- Route A, a new backend (the main question): the platform's sandbox is the Sandbox. The daemon mounts directly in it and there is no Docker. This needs new backend code, and the platform must provide the privilege split and every operation in the contract below.
- Route B, nested Docker (secondary): LayerFS runs unchanged and starts its usual container inside the platform's VM. No code changes, but the platform must allow Docker inside with these flags.
Desk research (2026-10-30)
| Provider | Finding |
|---|---|
| EC2 | the only confirmed fit |
| E2B, Vercel Sandbox, Modal VM runtime, Daytona Linux VM | likely fits: microVMs with root and documented FUSE |
| Fargate, Lambda, Azure Container Apps, Modal's default gVisor runtime | not viable |
The Mirage project (strukto-ai/mirage) needs the same --cap-add SYS_ADMIN --device /dev/fuse for container mounts.
The backend contract (what the Docker backend does at HEAD)
| SDK call | Backend steps |
|---|---|
sandbox.create |
B1 create, B2 attach Store volume, B3 upload, B4 start, B5 startup log, B6 endpoint |
workspace.mount / commit / status / unmount |
daemon calls over B6's TCP endpoint (no backend step) |
workspace.exec |
B7 exec |
sandbox.get_log |
B5 log tail |
SDK restart, sandbox.reclaim |
B8 reopen by ID, B10 discover |
sandbox.close |
B9 stop then delete, Store volume kept |
| Store calls (fs, branch, project) | S1: open the Store file directly from a Linux process on the same kernel |
Each platform is checked item by item:
- B1 Create from a pinned image (Docker uses
sha256:digest) or an
equivalent pinned template, with labels; one acknowledged full unique ID; no
adoption by name. - B2 Shared Store volume: one persistent local ext4/xfs/btrfs volume,
read-write at a fixed path (/layerfs-store), attached to several sandboxes
of one project at the same time on ONE Linux kernel (SQLite locking plus a
Linux directory lock). Record the filesystem type and whether two sandboxes
can share it. If each sandbox is its own VM, they cannot share a kernel:
record that, and whether several daemons can run inside one VM instead. - B3 Upload the daemon executable (0755) and private config (0600) before
start (Docker: archive into the stopped container). A file upload API, or a
pinned image that already contains them. - B4 Start the daemon as root with CAP_SYS_ADMIN and /dev/fuse, an init
that reaps orphans, and the privilege split inlfs-route-a-probe.sh. - B5 Startup log: read the daemon's stdout stream to see its listener
marker; a bounded tail of its log after a failed start. - B6 Endpoint: the daemon's TCP port reachable from the SDK host. Docker
publishes it on host
…(正文截断;完整内容见上游链接)
Sync note
这是只读镜像,不是上游仓库的替代。上游 issue/PR 仍是唯一真源;本镜像不复制评论、不复制代码分支。
Activity