A sandbox is one container, managed through the docker CLI.
Why this exists beside ExSandbox.Mechanism.Beam
Beam confines a process with the host's kernel -- cgroup v2, user and
mount namespaces, bwrap. On macOS none of those exist, so
ExSandbox.Capability.check/1 reports every gating name unavailable and the
gate refuses before provision/1 is reached. That refusal is correct and it
leaves a developer on a Mac with nowhere to run tenant code.
This mechanism brings its own kernel. The Linux VM behind Docker Desktop has
cgroup v2 (docker info reports cgroup=2, driver cgroupfs), so the caps
are real limits inside the container regardless of what the host can do --
which is why it declares ExSandbox.Mechanism.constructed_capabilities/0
and the host probe is asked only about what is left over.
What it does NOT claim, and the measurement behind that
⚠️ No :disk_quota. MEASURED 2026-08-28, Docker Desktop engine 27.4.0,
linux/arm64, storage driver overlayfs:
docker run --rm --storage-opt size=16M alpine \
sh -c 'dd if=/dev/zero of=/big bs=1M count=64; df -h /'
→ 67108864 bytes copied, exit=0
→ overlay 54.8G 30.4G 21.6G 59% /64 MB written into a nominal 16 MB quota, exit 0. docker create --storage-opt size=1G also returns success. The option is accepted and
ignored -- the same shape as 005 R9b, where a cap was invoked and did not
hold.
So a sandbox under this mechanism can fill the host filesystem, and that
is stated rather than hidden. Requiring :disk_quota instead would refuse on
every overlayfs host, which is every Docker Desktop for Mac -- the host this
mechanism exists to serve -- and a mechanism that refuses everywhere is not a
safer mechanism, it is no mechanism.
A sandbox that names a port trades the deny-all posture for reachability
A sandbox with service_port: nil joins no network: --network none,
which is what every sandbox this mechanism created before the field existed
got, and still gets.
A sandbox that names a service_port joins the default bridge and has that
port published to the host's loopback interface. What this gives up, stated
rather than implied: the container has outbound access, so the
deny-by-default posture does not hold for it -- code inside can install
dependencies at runtime and can reach the internet. ExSandbox.Egress's
allowlist does not apply here; narrowing outbound traffic to an allowlist over
this bridge is a separate change with its own evidence.
What still constrains it, and is unchanged: the filesystem the container sees
is the image plus the one bind-mounted workspace, its process tree is its own,
and the memory and CPU caps are still applied at create time. Inbound is
narrower than the deny-all posture suggests, not wider: exactly one port,
bound to 127.0.0.1, so the application answers the platform on the same host
and answers no other machine at all.
The image comes from template_ref
ExSandbox.Sandbox.t/0 names owner_ref, mechanism_ref and context as
opaque, and template_ref deliberately not: it is the one field a mechanism
is meant to interpret as "what to create this from". Here it is a container
image reference. A sandbox that names no template gets alpine:3, which
is a floor for a workspace mount, not a recommendation.
Summary
Functions
Whether a container runtime answered on this host, right now.
Where workspace_path is mounted inside the container.
Functions
@spec runtime_available?() :: boolean()
Whether a container runtime answered on this host, right now.
⚠️ Asked of the daemon, not of the executable. Docker Desktop leaves its
client on PATH with the VM stopped, so finding the binary answers a question
nobody asked -- the same shape as Capability.check(:filesystem_confinement)
finding sandbox-exec on macOS and reporting a confinement the launch does
not build.
⚠️ Deliberately not cached. A host acquires a container runtime by the operator starting one, and the caller for this is a refusal message telling them to do exactly that; an answer cached at boot would keep saying "install a runtime" to somebody who just did.
@spec workspace_mountpoint() :: String.t()
Where workspace_path is mounted inside the container.