Docker
Executes commands inside Docker containers on the local machine. Uses long-lived containers with docker exec for command execution. Targets local development, CI/CD, air-gapped deployments, and cost-sensitive scenarios where cloud sandboxes are unnecessary. For interface details, see WorkspaceSandbox interface.
InstallationDirect link to Installation
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/docker
pnpm add @mastra/docker
yarn add @mastra/docker
bun add @mastra/docker
Requires Docker Engine running on the host machine.
UsageDirect link to Usage
Add a DockerSandbox to a workspace and assign it to an agent:
import { Agent } from '@mastra/core/agent'
import { Workspace } from '@mastra/core/workspace'
import { DockerSandbox } from '@mastra/docker'
const workspace = new Workspace({
sandbox: new DockerSandbox({
image: 'node:22-slim',
}),
})
const agent = new Agent({
id: 'dev-agent',
name: 'dev-agent',
model: 'anthropic/claude-opus-4-7',
workspace,
})
Repository templatesDirect link to Repository templates
createDockerRepoTemplate builds an image that already holds a checkout of a
repository and the output of its setup command, so a session starts from
installed dependencies instead of a cold clone:
import { DockerSandbox, createDockerRepoTemplate } from '@mastra/docker'
const sandbox = new DockerSandbox({
id: sessionId,
template: createDockerRepoTemplate({
getRepositoryAccess: async () => ({
cloneUrl: 'https://github.com/octocat/hello-world.git',
}),
setupCommand: 'npm ci',
}),
})
getRepositoryAccess supplies the clone URL and, for private repositories, a
short-lived credential. The credential is passed to the clone step as a build
secret and never enters the image, its layers or the image identity. When
getRepositoryAccess is undefined, createDockerRepoTemplate returns
undefined, which selects the provider's default image without a conditional
at the call site.
The repository is cloned to <workingDirectory>/<repo> (default
/workspace), which becomes the working directory of the sandbox. Pass ref
to prepare a branch, tag or commit other than the default branch. Right before
the build the resolver runs git ls-remote to pin the ref to a commit, which
is part of the image identity: a moved branch yields a fresh image on the next
new sandbox and an unmoved one reuses the cached image. If the head can't be
resolved the resolver rejects rather than caching an unpinned clone.
setupCommand also accepts an array. Each entry runs as its own cached build
step. A command cannot contain a bare newline, which would start a new
Dockerfile instruction. End the line with \ or pass separate commands
instead.
Several repositories in one imageDirect link to Several repositories in one image
Pass repos instead of getRepositoryAccess to clone several repositories
into one image, each with its own access resolver, ref and setup command (a string or an
array, each entry its own build step):
const sandbox = new DockerSandbox({
id: sessionId,
template: createDockerRepoTemplate({
repos: [
{
getRepositoryAccess: async () => ({
cloneUrl: 'https://github.com/mastra-ai/template-docs-expert.git',
}),
setupCommand: 'npm install',
},
{
getRepositoryAccess: async () => ({
cloneUrl: 'https://github.com/mastra-ai/good-issue.git',
}),
ref: 'main',
},
],
workspaceSetupCommand: 'npm install --global @mastra/mcp-docs-server',
continueOnSetupFailure: true,
workingDirectory: '/workspace',
}),
})
Every repository is cloned to <workingDirectory>/<repo> and the working
directory becomes the sandbox cwd. Public repositories are built before
private ones, keeping the caller's order within each group. Each private
credential is passed as a build secret to that repository's clone step only,
so setup commands never see it.
workspaceSetupCommand runs at the working directory after every repository
is set up. continueOnSetupFailure (default false) lets a failing setup
command record its repository in .mastra-sandbox/setup-failed and continue
instead of failing the build.
Markers tell a session what the image already ran: .mastra-sandbox/repos/<repo>
per repository and .mastra-sandbox/workspace-setup after the workspace
commands, all under the working directory. With continueOnSetupFailure,
check .mastra-sandbox/setup-failed first.
If any entry's access or head can't be resolved, the whole resolution rejects and the sandbox falls back to its default template.
Constructor parametersDirect link to Constructor parameters
id?:
name?:
--name. Characters outside [a-zA-Z0-9_.-] are replaced with - and the result is prefixed if it would not start with an alphanumeric character.image?:
command?:
env?:
volumes?:
network?:
privileged?:
memory?:
memorySwap?:
cpuQuota?:
cpuPeriod?:
pidsLimit?:
readonlyRootfs?:
capDrop?:
capAdd?:
securityOpt?:
ulimits?:
tmpfs?:
workingDirectory?:
cwd is given. A per-command cwd always wins.workingDir?:
workingDirectory. When both are set, workingDirectory wins.labels?:
timeout?:
dockerOptions?:
instructions?:
PropertiesDirect link to Properties
id:
name:
provider:
status:
container:
processes:
Background processesDirect link to Background processes
DockerSandbox includes a built-in process manager for spawning and managing background processes. Processes run inside the container using docker exec.
const sandbox = new DockerSandbox({ id: 'dev-sandbox' })
await sandbox._start()
// Spawn a background process
const handle = await sandbox.processes.spawn('node server.js', {
env: { PORT: '3000' },
onStdout: data => console.log(data),
})
// Interact with the process and signal EOF
console.log(handle.stdout)
await handle.sendStdin('input\n')
await handle.closeStdin()
await handle.wait()
See SandboxProcessManager reference for the full API.
Environment variablesDirect link to Environment variables
Set environment variables at the container level with env. Per-command environment variables can also be passed when spawning processes:
const sandbox = new DockerSandbox({
image: 'node:22-slim',
env: {
NODE_ENV: 'production',
DATABASE_URL: 'postgres://localhost:5432/mydb',
},
})
Write filesDirect link to Write files
Upload multiple files in one call with writeFiles. Relative paths resolve under the working directory. Set an optional per-file mode to control POSIX permissions; it must be an integer between 0o001 and 0o777. When mode is omitted, new files are created with 0644.
await sandbox.writeFiles([
{ path: 'src/index.js', content: "console.log('hello')\n" },
{ path: 'run.sh', content: '#!/bin/sh\n', mode: 0o755 },
])
Docker is the only built-in sandbox that applies an explicit mode. Providers that cannot honor per-file permissions (Vercel, E2B, Daytona, Cloudflare) reject a writeFiles call that includes a mode instead of silently dropping it.
Bind mountsDirect link to Bind mounts
Mount host directories into the container using the volumes option:
const sandbox = new DockerSandbox({
image: 'node:22-slim',
volumes: {
'/my/project': '/workspace/project',
'/shared/data': '/data',
},
})
Bind mounts are applied at container creation time. The host paths must exist before the sandbox starts.
HardeningDirect link to Hardening
Use Docker-specific resource and hardening options to limit a sandbox container. The following example caps memory and process count while limiting CPU to a single core with matching cpuPeriod and cpuQuota values. It drops Linux capabilities and makes the root filesystem read-only, with /tmp mounted as writable scratch space:
const sandbox = new DockerSandbox({
image: 'node:22-slim',
memory: 512 * 1024 * 1024,
memorySwap: 512 * 1024 * 1024,
cpuPeriod: 100_000,
cpuQuota: 100_000,
pidsLimit: 256,
readonlyRootfs: true,
capDrop: ['ALL'],
capAdd: ['NET_BIND_SERVICE'],
securityOpt: ['no-new-privileges:true'],
ulimits: [{ name: 'nofile', soft: 1024, hard: 2048 }],
tmpfs: {
'/tmp': 'rw,noexec,nosuid,size=64m',
},
})
These options map directly to Docker HostConfig fields and aren't set unless you pass them.
Review these trade-offs before enabling hardening:
readonlyRootfs: In-container package installs and tools that write outside mounted paths can fail. Addtmpfsentries for writable scratch paths such as/tmp, and mount tmpfs or volumes for package-manager caches such as~/.npmwhen needed.capDrop: Dropping all capabilities disables commands that need Linux capabilities, includingpingand mount operations. FUSE-backed tools are also disabled. Add back only the capabilities your workload needs.memory: Docker treats0as unlimited. Omitmemoryor pass0only when you don't want a memory cap.memorySwap: Docker memory and swap behavior depends on the host and Docker daemon configuration. When you setmemorywithoutmemorySwap, Docker allows swap up to twice the memory limit by default. SetmemorySwapequal tomemorywhen you want to disable swap for the container; Docker also accepts-1for unlimited swap.pidsLimit: Very low values can breakdocker execworkloads because each command starts additional processes inside the long-lived container.privileged: Privileged containers bypass capability and security-option controls. Don't combineprivileged: truewith capability or security options unless the workload requires it.- Reconnection:
DockerSandboxreuses an existing container when the sandbox ID matches and warns if inspectedHostConfighardening values differ. Destroy and recreate the sandbox to apply changed hardening options. Docker can normalize inspected values, and changingmemorySwapon reconnect can trigger a warning if the original container used Docker's default swap behavior. - Docker Desktop: Resource limits apply inside the Docker Desktop virtual machine on macOS and Windows, so the VM's allocated resources can cap what containers receive.
ReconnectionDirect link to Reconnection
DockerSandbox can reconnect to existing containers by matching labels. When start() is called, it checks for a container with the mastra.sandbox.id label matching the sandbox ID. If found:
- A running container is reused directly.
- A stopped container is restarted.
// First run — creates a new container
const sandbox = new DockerSandbox({ id: 'persistent-sandbox' })
await sandbox._start()
// Later — reconnects to the existing container
const sandbox2 = new DockerSandbox({ id: 'persistent-sandbox' })
await sandbox2._start()
Docker connection optionsDirect link to Docker connection options
Connect to remote Docker hosts or use custom socket paths via dockerOptions:
// Remote Docker host
const sandbox = new DockerSandbox({
dockerOptions: {
host: '192.168.1.100',
port: 2376,
ca: fs.readFileSync('ca.pem'),
cert: fs.readFileSync('cert.pem'),
key: fs.readFileSync('key.pem'),
},
})
// Custom socket path
const sandbox = new DockerSandbox({
dockerOptions: {
socketPath: '/var/run/docker.sock',
},
})