Guide

Advanced Topics

Deeper tooling for sandboxes, file sets, and power-user workflows.

Sandbox

A sandbox is an ephemeral container that runs a single command and is discarded as soon as it exits. It is the same machinery Overplane uses internally for agent runs, exposed directly through the overplane sandbox run command so you can run ad-hoc commands inside your project's image (or any image) without writing a script or remembering long docker/podman invocations.

The sandbox abstracts over both Docker and Podman, so the same command behaves identically on either engine. Every run is fresh and isolated: there is no shared state between runs, and the container is removed when the command finishes.

Always-on guarantees

These mounts are applied to every sandbox run:

  • The project repository is mounted read-only at /project. Commands can read your project tree but cannot mutate the host. In a project sandbox, the specs directory is also mounted read-only at /specs.
  • Two fresh read-write scratch directories are mounted at /output and /error. Each run gets new, empty ones; the container's working directory defaults to /output. Files a command writes there are captured when it exits, and --output DIR / --error DIR extract them to host directories after a successful run.
  • The overplane binary is mounted at /usr/local/bin/overplane. The exact binary you invoked is on PATH inside the container, with no image rebuild needed, so tooling that shells out to overplane just works.

Running ad-hoc commands

By default, sandbox run uses your project's built image (overplane-<project>:latest) and the runtime configured in overplane.yaml, so sandbox build must have built the image first. The command's output is streamed through, and its exit code becomes overplane's exit code.

# Run a command in the project image. The natural form needs no '--':
# flag parsing stops at the first non-flag token, so '-l' is ls's flag.
overplane sandbox run ls -l

# Use '--' when the command itself starts with a dash.
overplane sandbox run -- some-tool --version

With --image (and optionally --runtime), the sandbox runs against any image and does not require a project:

overplane sandbox run --image debian:bookworm-slim -- cat /etc/os-release
overplane sandbox run --image alpine --runtime podman -- sh -c 'echo hi'

Customizing the environment

There are two layers to customize: the project image, declared in overplane.yaml under sandbox: and built with overplane sandbox build, and the individual run, adjusted with sandbox run flags. Image edits take effect on the next sandbox build; the image is built in cache-friendly layers, so small changes (an extra package, a new agent version) rebuild quickly without redoing the whole toolchain.

The sandbox: section

The section is optional: a file without it behaves as if the defaults below were written out. overplane init generates a fully commented version of this block. See the configuration reference for every field and the image recipes reference for what each recipe installs.

sandbox:
  # Container engine: docker (Buildx/BuildKit 0.13+) or podman (5.x+).
  runtime: docker

  # Image the sandbox is built from. Its OS family must match setup_recipe.
  base_image: debian:bookworm-slim
  setup_recipe: debian          # debian (apt) or alpine (apk)

  # Project-specific OS packages, keyed by package family.
  extra_packages:
    debian: []

  # AI coding agents to install, in install order.
  agent_recipes:
    - {name: codex, version: latest}
    - {name: claude-code, version: latest}
    - {name: gemini-cli, version: latest}
    - {name: opencode, version: latest}

  # Optional capabilities layered into the final image.
  feature_recipes:
    - {name: playwright}

  # Extra host environment variables copied into every run.
  env_passthrough: []

Base image and toolchain

setup_recipe: debian (the default, paired with base_image: debian:bookworm-slim) provisions the image in three cached layers: base setup (UTC timezone, full apt upgrade, TLS roots), the developer toolchain, and your project's extra packages. The toolchain layer installs from official upstream sources where it matters — Go from go.dev tarballs, Rust via rustup (stable, minimal profile), Node.js from NodeSource, and uv via pip — plus these common apt packages:

build-essential  pkg-config  libssl-dev  make   git    ripgrep
rsync            jq          curl        htop   procps tini
z3               python3     python3-pip sudo   ca-certificates

setup_recipe: alpine (paired with base_image: alpine:3) provides best-effort parity using apk: build-base, git, ripgrep, rsync, jq, htop, procps, tini, z3, python3 with uv, bash, and sudo. Known gaps versus debian: Go, Node.js, and Rust install from the Alpine package repositories (musl builds), so their versions track the Alpine release rather than upstream. All four agent CLIs ship musl-compatible builds and work unchanged.

Extra OS packages

Add project-specific packages under extra_packages, keyed by package family; only the family matching your setup_recipe is used. Extras install in their own image layer, so editing this list never invalidates the cached toolchain layers:

sandbox:
  extra_packages:
    debian: [graphicsmagick, sqlite3]   # apt names with setup_recipe: debian
    alpine: [graphicsmagick, sqlite]    # apk names with setup_recipe: alpine

Enabling, disabling, and pinning agents

agent_recipes lists the agents baked into the image, chosen from Overplane's embedded registry: codex (OpenAI Codex), claude-code (Anthropic Claude Code), gemini-cli (Google Gemini CLI), and opencode (OpenCode). New projects enable all four. To disable an agent, remove its entry and rebuild; an empty list (agent_recipes: []) builds a toolchain-only sandbox with no agents. version accepts an npm dist-tag (latest, preview) or an exact version; tags resolve on each build and the resolved version is recorded in the build hash.

sandbox:
  # Only Claude Code (pinned) and Codex (tracking latest); the other
  # agents are disabled simply by not being listed.
  agent_recipes:
    - {name: claude-code, version: "2.1.3"}
    - {name: codex}   # version defaults to latest

Each configured agent's API keys are forwarded from the host automatically (for example OPENAI_API_KEY/CODEX_API_KEY for codex, ANTHROPIC_API_KEY for claude-code, GEMINI_API_KEY for gemini-cli, and OPENROUTER_API_KEY for opencode) — no env_passthrough entry needed.

Feature recipes

feature_recipes layers optional capabilities into the final image. Currently the registry offers playwright: the pinned Playwright CLI plus headless Chromium under /opt/ms-playwright on debian (distro chromium on alpine, since Playwright publishes no musl browsers). It is enabled by default in new projects; it is also the slowest layer, so skip it when you don't need browser testing:

sandbox:
  feature_recipes: []   # omit the browser-testing layer entirely

Environment passthrough

env_passthrough copies additional host environment variables into the sandbox, beyond the agent API keys above. Values listed here reach every container started by overplane sandbox run, so add secrets deliberately:

sandbox:
  env_passthrough: [NPM_TOKEN, GOPRIVATE]

Per-run customization

Everything above changes the image; sandbox run flags change a single run without rebuilding anything:

# Set variables for one run: literal, or copied from the host.
overplane sandbox run --env DEBUG=1 --env-from-host NPM_TOKEN -- make test

# Bind-mount extra host paths (read-write by default; :ro to protect them).
overplane sandbox run --mount ~/datasets:/data:ro -- ls /data

# Network mode, run identity, and working directory.
overplane sandbox run --network none --user 0:0 --workdir /project -- id

# Extract what the command wrote to /output and /error after a run.
overplane sandbox run --output ./out --error ./err -- sh -c 'date > stamp.txt'

Structured output

Pass --json to capture the result instead of streaming it. The object carries the exit code, engine, image, wall-clock duration, optional memory/CPU usage, and base64-encoded stdout/stderr, so binary output and separate streams survive intact.

{
  "cpu_ms": ...,            // omitted when the engine cannot report it
  "engine": "docker",
  "exit_code": 0,
  "image": "overplane-myproject:latest",
  "max_rss_bytes": ...,     // omitted when unavailable
  "stderr": "ZXJyCg==",     // base64("err\n")
  "stdout": "b3V0Cg==",     // base64("out\n")
  "wall_ms": 84
}
Next step Advanced Topics · 5 of 5 FileSet Content-addressable snapshots behind content-hashed, incremental builds.