# `ExSandbox.Mechanism.Docker`
[🔗](https://github.com/FoundryStack/ex_sandbox/blob/v1.2.0/lib/ex_sandbox/mechanism/docker.ex#L1)

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 `c: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`

`t: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.

# `runtime_available?`

```elixir
@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.

# `workspace_mountpoint`

```elixir
@spec workspace_mountpoint() :: String.t()
```

Where `workspace_path` is mounted inside the container.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
