Skip to content

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.

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.

OptionTypeDefaultContract
imagestringwearesingular/aml-agent-sandbox:latestNamed image reference passed to docker run. Must be a non-empty normalized string.
maxOutputBytesnumber4 * 1024 * 1024Shared byte budget for provider command output and transfer/setup output. Must be positive.
setupstringNoneRuns through sh -lc after the container starts and the Workspace is mounted.
userstringImage defaultOptional Docker user or UID:GID passed through --user. Must be a non-empty normalized string when supplied.
workspacestringNoneFallback 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.

The image must already contain everything the workflow needs:

  • a usable sh entrypoint because AML starts a keepalive shell and runs setup through sh -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 /workspace and 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.

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.

  1. AML resolves the active Workspace directory or the configured fallback.
  2. Docker starts a named detached container with --rm, the selected image, optional runtime user, /workspace bind mount, logical guest cwd, and a keepalive sh process.
  3. If startup fails, AML attempts to remove the named container and aggregates startup and cleanup failures when both fail.
  4. The optional setup command runs after the mount is ready. A non-zero result rejects acquisition and triggers container cleanup.
  5. exec() invokes docker exec with the logical guest cwd and explicit environment values.
  6. spawn() invokes a process-group wrapper inside the container, captures output from the beginning, and can kill the remote process group.
  7. 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.
  8. Timeout or cancellation of docker exec removes the disposable container because the remote command may otherwise survive the local client process.
  9. 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.

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.

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; user only 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.

SymptomCauseWhat to check
docker: command not foundThe Docker CLI is absent from the host.Install/configure the CLI in the application environment.
Cannot connect to the Docker daemonThe daemon socket or context is unavailable.Check docker info as the same user that runs AML.
Container exits before acquisitionImage lacks sh, cannot start, or has incompatible architecture/permissions.Run the image manually with the expected user and shell.
Agent executable not foundThe 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 failsHost 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 deniedThe 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/127Command is not executable or not found inside the image.Check image PATH, executable permissions, architecture, and runtime dependencies.
Output limit exceededCombined command output exceeded maxOutputBytes.Reduce output, use spawn(), or raise the limit deliberately.
Remote command survives cancellationDocker exec cannot always clean up the child independently.AML removes the disposable container; inspect daemon logs if termination is delayed.
Files changed remotely are missingThe 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.