Run AML in a Sandbox image
Run a deterministic <Script /> inside the image selected by
dockerSandbox(), then read the file it created after the disposable container is
released.
This recipe deliberately omits image first. Docker Sandbox therefore uses AML’s recommended
wearesingular/aml-agent-sandbox:latest image. The same default applies to Daytona and Modal when their provider factory
receives no image or snapshot override.
Prerequisites
Section titled “Prerequisites”- Node.js
>=26, ESM TypeScript/TSX execution, and@aml-jsx/sdk; - Docker running locally and able to pull
wearesingular/aml-agent-sandbox:latest; - no model credentials—the recipe executes a deterministic Script rather than an Agent session.
Complete source
Section titled “Complete source”import { mkdtemp, readFile } from "node:fs/promises"import { tmpdir } from "node:os"import { join } from "node:path"
import { AmlRuntime, Sandbox, Script, Workspace, dockerSandbox, localWorkspace } from "@aml-jsx/sdk"
const directory = await mkdtemp(join(tmpdir(), "aml-image-cookbook-"))const user = typeof process.getuid === "function" && typeof process.getgid === "function" ? `${process.getuid()}:${process.getgid()}` : undefined
const sandbox = dockerSandbox({ // Docker bind mounts retain host ownership. Use the host identity when the // platform exposes one; the image itself remains non-root by default. ...(user === undefined ? {} : { user }),})
await new AmlRuntime({ sandboxProvider: sandbox, workspaceProvider: localWorkspace({ directory }),}).evaluate( <Workspace id="image-cookbook" load={false} save={false}> <Sandbox access="read-write"> <Script command="sh" args={[ "-lc", "node --version > runtime.txt && npm --version >> runtime.txt && python --version >> runtime.txt 2>&1 && pip --version >> runtime.txt && jq --version >> runtime.txt", ]} /> </Sandbox> </Workspace>)
console.log(await readFile(join(directory, "runtime.txt"), "utf8"))Run it
Section titled “Run it”Save the source as recipe.tsx in a project configured as shown in Getting started, then run:
npx vite-node recipe.tsxThe first run may pull the image. A successful run prints the installed Node.js, npm, Python, pip, and jq versions from inside the container. runtime.txt survives because Docker bind-mounts the local Workspace; the container itself is removed.
How image selection works
Section titled “How image selection works”dockerSandbox()receives noimage, so it selectswearesingular/aml-agent-sandbox:latest.<Workspace />materializes the temporary host directory and the Docker provider mounts it at/workspace.<Sandbox />acquires a disposable container and<Script />runs only through that active runtime.- Docker removes the container after the subtree settles. The local Workspace remains application-owned.
The <Sandbox /> component has no image prop. Image identity belongs to the provider factory because Local has no
image, Daytona can use a snapshot, and Modal loads images through its registry integration.
Pin or replace the image
Section titled “Pin or replace the image”latest is useful for evaluation and examples. Pin a stable version or digest for repeatable deployments:
const sandbox = dockerSandbox({ image: "wearesingular/aml-agent-sandbox:X.Y.Z", ...(user === undefined ? {} : { user }),})An application can instead extend AML’s image with project dependencies or replace it entirely:
const sandbox = dockerSandbox({ image: "ghcr.io/your-organization/project-agent@sha256:IMAGE_DIGEST", ...(user === undefined ? {} : { user }),})The override must contain the programs the workflow launches. An Agent workflow also needs its ACP/native Agent executable chain; a generic Node or Python image is not automatically Agent-ready.
Extend a single-Agent image
Section titled “Extend a single-Agent image”If an application always uses one Agent, derive its project image from that Agent’s variant instead of full. This Dockerfile adds the SQLite CLI to OpenCode’s image:
FROM wearesingular/aml-agent-sandbox:X.Y.Z-opencode
USER rootRUN apt-get update \ && apt-get install -y --no-install-recommends sqlite3 \ && rm -rf /var/lib/apt/lists/*
USER amlWORKDIR /workspaceBuild it locally:
docker build --tag example/aml-opencode:1 .Then pair the derived image with opencodeAgent():
import { AmlRuntime, dockerSandbox, opencodeAgent } from "@aml-jsx/sdk"
const runtime = new AmlRuntime({ agentProvider: opencodeAgent({ env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY! }, }), sandboxProvider: dockerSandbox({ image: "example/aml-opencode:1", }),})The image supplies OpenCode and sqlite3; opencodeAgent() supplies Agent configuration, and the Sandbox provider supplies container lifecycle and process execution. Keep credentials out of the Dockerfile and inject them at runtime.