Docker Sandbox
Sandbox provider
dockerSandbox(options?)The Docker provider starts one disposable container, bind-mounts the effective Workspace at /workspace, and exposes AML’s command and process runtime through docker exec.
- Best for
- Local containerized workflows where you own the Docker daemon and image.
- Know before using
- AML starts a named image; it does not build or harden it. A lightweight docker run adapter is not a complete hostile-code security boundary.
What Docker owns
Section titled “What Docker owns”Docker owns the disposable container and the process transport. AML selects wearesingular/aml-agent-sandbox:latest when image is omitted; the application decides whether to accept that convenience default or override it. The application still owns the Docker daemon, host configuration, identity, network policy, resource limits, project dependencies, and credential delivery. AML does not build a Dockerfile or install an ACP executable during acquisition.
The Workspace is a same-host bind mount, not an archive transfer. The mount target is always /workspace; AML maps logical root and cwd below that path. With access="read-only", AML adds :ro to the bind mount. The container can still execute commands, but writes to the mounted Workspace should fail at the mount boundary.
Options
Section titled “Options”| Option | Type | Default | Contract |
|---|---|---|---|
image | string | wearesingular/aml-agent-sandbox:latest | Named image reference passed to docker run. Must be a non-empty normalized string. |
maxOutputBytes | number | 4 * 1024 * 1024 | Shared byte budget for provider command output and transfer/setup output. Must be positive. |
setup | string | None | Runs through sh -lc after the container starts and the Workspace is mounted. |
user | string | Image default | Optional Docker user or UID:GID passed through --user. Must be a non-empty normalized string when supplied. |
workspace | string | None | Fallback host Workspace path. An active <Workspace /> materialization takes precedence. |
Per-command timeoutMs is supplied through the AML runtime command API, not the factory options. It must be a positive timer-safe integer. The provider passes command environment values to docker exec and maps the logical cwd to /workspace/....
latest follows the newest validated stable AML image. Pin an immutable version or digest when reproducibility matters.
Image requirements
Section titled “Image requirements”The image must already contain everything the workflow needs:
- a usable
shentrypoint because AML starts a keepalive shell and runs setup throughsh -lc; mkdir,rm, and the normal process utilities used by the image and Agent;- the selected ACP Agent executable and its runtime, if
<Agent />is nested in<Sandbox />; - the Agent’s model/provider configuration mechanism or client dependencies;
- project dependencies needed by commands;
- a user that can read
/workspaceand write there when the Workspace is mounted read-write.
node:26-alpine is a valid smoke-test image for basic shell/Node commands, but it is not a complete Codex, GitHub Copilot, GLM, OpenCode, or Pi Agent image merely because it contains Node. Build and publish a deliberate image for a real Agent workflow, or use the trusted setup hook for experiments.
See AML Agent Sandbox images for the full and single-Agent variants, Docker Hub and GHCR channels, pinning, and extension guidance. See alternative and custom images when replacing AML’s image.
Complete safe example
Section titled “Complete safe example”This example uses a temporary host directory and a deterministic command. It exercises the actual mount and persistence path without requiring credentials or an Agent binary:
/** @jsxImportSource @aml-jsx/sdk */import { mkdtemp, readFile } from "node:fs/promises"import { join } from "node:path"import { tmpdir } from "node:os"import { AmlRuntime, Sandbox, Script, Workspace, dockerSandbox, localWorkspace } from "@aml-jsx/sdk"
const directory = await mkdtemp(join(tmpdir(), "aml-docker-"))const runtime = new AmlRuntime({ sandboxProvider: dockerSandbox({ image: "alpine:3.22", setup: "printf prepared > setup.txt", }), workspaceProvider: localWorkspace({ directory }),})
await runtime.evaluate( <Workspace id="docker-demo" load={false} save={false}> <Sandbox access="read-write"> <Script shell="sh">printf mounted > output.txt</Script> </Sandbox> </Workspace>,)
console.log(await readFile(join(directory, "output.txt"), "utf8"))For a real Agent, replace the image with one that contains the ACP executable and use the corresponding Agent provider inside the same <Sandbox />. Do not use a public tutorial image as evidence that your Agent/Sandbox pair is deployable.
Lifecycle
Section titled “Lifecycle”- AML resolves the active Workspace directory or the configured fallback.
- Docker starts a named detached container with
--rm, the selected image, optional runtime user,/workspacebind mount, logical guest cwd, and a keepaliveshprocess. - If startup fails, AML attempts to remove the named container and aggregates startup and cleanup failures when both fail.
- The optional setup command runs after the mount is ready. A non-zero result rejects acquisition and triggers container cleanup.
exec()invokesdocker execwith the logical guest cwd and explicit environment values.spawn()invokes a process-group wrapper inside the container, captures output from the beginning, and can kill the remote process group.- Complete-file reads and writes use confined guest paths plus
docker cp; writes upload a temporary file and rename it over the destination. Agent staging uses a unique writable directory under/tmp. - Timeout or cancellation of
docker execremoves the disposable container because the remote command may otherwise survive the local client process. - Release removes the container. Because the Workspace is a same-host mount, writes are already present in the host materialization; Workspace save/revision behavior remains the Workspace provider’s responsibility.
Read-only behavior
Section titled “Read-only behavior”Docker is the built-in provider that can execute commands with a read-only Workspace mount. AML passes the mount as /workspace:ro and still permits exec() and spawn() against the image filesystem. The Agent may still need writable state outside /workspace; its process permissions and image filesystem are separate concerns.
Read-only does not mean “no side effects anywhere in the container.” It protects the mounted Workspace. Container-level filesystem, network, process, and credential behavior depends on the image and Docker daemon configuration.
Security posture
Section titled “Security posture”The adapter invokes docker run with an image, --detach, --rm, a bind mount, a working directory, and a shell entrypoint. It does not configure:
- network isolation or egress policy;
- Linux capabilities, seccomp, AppArmor, or SELinux policy;
- rootless daemon mode or automatic container-user selection;
useronly passes an explicit identity to Docker; - CPU, memory, PID, or disk limits;
- a read-only root filesystem;
- secret injection or credential redaction at the Docker daemon boundary.
Treat the Docker daemon and selected image as part of your security boundary. Pin trusted image digests, use a hardened daemon/runtime policy, choose a non-root image where appropriate, configure network and resource limits outside AML, and do not mount sensitive host directories. Do not run untrusted model-generated code with a default Docker installation and call that complete hostile-code isolation.
Failure modes and troubleshooting
Section titled “Failure modes and troubleshooting”| Symptom | Cause | What to check |
|---|---|---|
docker: command not found | The Docker CLI is absent from the host. | Install/configure the CLI in the application environment. |
| Cannot connect to the Docker daemon | The daemon socket or context is unavailable. | Check docker info as the same user that runs AML. |
| Container exits before acquisition | Image lacks sh, cannot start, or has incompatible architecture/permissions. | Run the image manually with the expected user and shell. |
| Agent executable not found | The image does not contain the selected ACP command. | Install it in the image or use a trusted setup hook; AML does not install it. |
Workspace mount fails | Host source is missing, not a directory, or inaccessible to the Docker daemon. | Verify the active/fallback path and daemon access to the host filesystem. |
| Workspace permission denied | The image user cannot access the host-owned bind mount. | Set user to the Workspace owner’s UID:GID, or align ownership in the image. |
exec exits with code 126/127 | Command is not executable or not found inside the image. | Check image PATH, executable permissions, architecture, and runtime dependencies. |
| Output limit exceeded | Combined command output exceeded maxOutputBytes. | Reduce output, use spawn(), or raise the limit deliberately. |
| Remote command survives cancellation | Docker exec cannot always clean up the child independently. | AML removes the disposable container; inspect daemon logs if termination is delayed. |
| Files changed remotely are missing | The container was released before a same-host mount write completed or the command wrote outside /workspace. | Keep writes below the mounted root and wait for command completion. |