Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mattpocock/sandcastle/llms.txt

Use this file to discover all available pages before exploring further.

The Docker provider is the most common way to run Sandcastle agents locally. It creates a Docker container for each run and bind-mounts your git worktree directly into the container, so the agent writes to your host filesystem through the mount — no file syncing required.

Installation

Docker must be available on your PATH. Docker Desktop is the recommended option for macOS and Windows. On Linux, install the Docker Engine package for your distribution.

Import

import { docker } from "@ai-hero/sandcastle/sandboxes/docker";

Basic usage

Pass docker() as the sandbox option to run():
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";

await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker(),
  promptFile: ".sandcastle/prompt.md",
});
When no options are passed, docker() derives the image name from your repo directory name and uses your host UID/GID to match the container user.

Building the image

Before running an agent, you need a container image. Use sandcastle docker build-image after sandcastle init or whenever you modify the Dockerfile:
npx sandcastle docker build-image
On Linux and macOS, the build automatically passes --build-arg AGENT_UID=$(id -u) and AGENT_GID=$(id -g) so the image’s agent user matches your host UID. This prevents permission errors on files the agent creates.
1

Customize the Dockerfile

Edit .sandcastle/Dockerfile to install the tools your agent needs (e.g., npm, git, language runtimes).
2

Rebuild the image

npx sandcastle docker build-image
3

Run your agent

await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker(),
  promptFile: ".sandcastle/prompt.md",
});

CLI commands

sandcastle docker build-image

Rebuilds the Docker image from .sandcastle/Dockerfile.
OptionDefaultDescription
--image-namesandcastle:<repo-dir-name>Docker image name
--dockerfilePath to a custom Dockerfile (build context is the current working directory)

sandcastle docker remove-image

Removes the Docker image.
OptionDefaultDescription
--image-namesandcastle:<repo-dir-name>Docker image name

Options

All options are passed to docker():
sandbox: docker({
  imageName: "sandcastle:myrepo",
  containerUid: 1000,
  containerGid: 1000,
  mounts: [
    { hostPath: "~/.npm", sandboxPath: "/home/agent/.npm", readonly: true },
  ],
  selinuxLabel: "z",
  env: { MY_TOKEN: "value" },
  network: "my-network",
})
imageName
string
Docker image name to use for the container. Defaults to sandcastle:<repo-dir-name>, derived from the basename of your repo directory.
containerUid
number
UID of the agent user inside the container image. Defaults to the host UID (process.getuid()) or 1000 if unavailable. Must match the UID baked into the image at build time. A pre-flight check catches mismatches and tells you which value to use.
containerGid
number
GID of the agent user inside the container image. Defaults to the host GID (process.getgid()) or 1000 if unavailable.
mounts
Array<{ hostPath: string; sandboxPath: string; readonly?: boolean }>
Additional host directories to bind-mount into the container. Useful for mounting package manager caches (e.g., ~/.npm, ~/.pnpm-store) or shared data directories.
  • hostPath supports absolute paths, tilde-expanded paths (~/...), and relative paths resolved from cwd.
  • sandboxPath supports absolute paths and relative paths resolved from the sandbox repo directory.
  • If hostPath does not exist on the host, sandbox creation fails with a clear error.
selinuxLabel
"z" | "Z" | false
SELinux volume label applied to bind mounts. Defaults to "z" (shared label). Use "Z" for a private label that restricts access to this container only. Pass false to disable labeling. This is a no-op on systems without SELinux (Docker Desktop on macOS/Windows, Linux without SELinux).
env
Record<string, string>
Environment variables to inject into the container at launch time. Merged with env from the agent provider and the run-level env option.
network
string | string[]
Docker network(s) to attach the container to. Pass a single string for one network or an array for multiple. When omitted, Docker’s default bridge network is used.
  • "my-network"--network my-network
  • ["net1", "net2"]--network net1 --network net2

Branch strategy

Docker is a bind-mount provider, so it defaults to the head branch strategy: the agent writes directly to your host working directory with no worktree indirection. You can override the strategy on run():
await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker(),
  branchStrategy: { type: "branch", branch: "agent/fix-42" },
  promptFile: ".sandcastle/prompt.md",
});
With { type: "head" } (the default), the worktree directory is your current working directory, which gets bind-mounted into the container. With { type: "branch" } or { type: "merge-to-head" }, Sandcastle creates a git worktree in a temporary directory and bind-mounts that instead.

Complete example

import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";

await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker({
    imageName: "sandcastle:myrepo",
    // Match the UID baked into your image if it differs from your host UID
    // containerUid: 1000,
    // containerGid: 1000,
    mounts: [
      // Mount npm cache read-only to speed up installs
      { hostPath: "~/.npm", sandboxPath: "/home/agent/.npm", readonly: true },
      // Mount a local data directory into the sandbox repo root
      { hostPath: "data", sandboxPath: "data" },
    ],
    // Shared SELinux label (default); no-op outside SELinux systems
    selinuxLabel: "z",
    env: { MY_API_KEY: process.env.MY_API_KEY! },
    // Attach to a custom Docker network (e.g., for a local database)
    network: "my-network",
  }),
  branchStrategy: { type: "branch", branch: "agent/fix-42" },
  promptFile: ".sandcastle/prompt.md",
  hooks: {
    sandbox: {
      onSandboxReady: [{ command: "npm install" }],
    },
  },
});
If the image is not found locally, sandbox creation fails immediately with a message telling you to run sandcastle docker build-image. You do not need to catch this — fix it by building the image first.

Build docs developers (and LLMs) love