Skip to content

abox quickstart

From nothing to a sandboxed agent run. Every command here was executed on a real machine while writing this; the outputs are what you should actually see.


0. Host prerequisites

Docker Desktop with the MCP Toolkit enabled. That is the whole list.

No npm, no Node, no @devcontainers/cli. abox drives the Docker CLI itself and bakes Claude Code into the agent image from its checksum-verified native binary.

docker version --format '{{.Server.Version}}'   # daemon reachable?
docker mcp --version                            # MCP Toolkit enabled?

If docker mcp is not found, open Docker Desktop → Settings → Beta features and enable Docker MCP Toolkit, then restart Docker Desktop.

Give Docker enough disk

This is the one setting worth checking before you start. The agent image is ~1.3 GB and the base image is ~1 GB, so an 8 GiB Docker disk fills up fast and you get no space left on device in the middle of a build.

docker run --rm --entrypoint /bin/busybox docker/mcp-gateway:v2 df -h /

If Available is under ~10 G, raise it: Docker Desktop → Settings → Resources → Disk image size, then Apply & Restart. 40–60 GB is comfortable.

Optional: 1Password

Only if you want to pull secrets from op://… references. Every other secret source (file, env var, typed at the terminal, already-in-the-store) works without it, and abox doctor will not nag you about a missing op.


1. Install abox

uv tool install abox-cli

The distribution is named abox-cli — the bare abox name on PyPI belongs to an unrelated placeholder — but the command it installs is abox. To track the repo instead of the last release, use uv tool install git+https://github.com/tr0mb1r/abox.

abox --version

You should get abox 0.1.4, from ~/.local/bin/abox. If the shell can't find it, add ~/.local/bin to your PATH (or run uv tool update-shell).

To upgrade later, re-run the install with --reinstall. To remove it: uv tool uninstall abox (uv names the tool after the command, not the distribution).

Hacking on abox itself? Install the checkout in place instead, from inside it:

uv tool install --reinstall --force .

2. Set up a project

cd ~/projects/demo-app
abox init

The interactive picker walks you through:

  • Profile — projects sharing a profile share one gateway. Start with default.
  • MCP servers — multi-select over the Docker catalog; each entry shows whether it needs a secret and whether its image is digest-pinned.
  • Tool narrowing — optional, per server.
  • Toolchains — pre-ticked from what it detected in your repo (pyproject.toml → python, go.mod → go, and so on).
  • Egress — pre-filled from those toolchains and your git remote. Everything not on this list is dropped.
  • Permission mode — start with default.

Non-interactive equivalent, useful for scripting:

abox init --yes --server duckduckgo --profile default

This writes agentbox.yaml in your repo (commit it) and renders the container artifacts into abox's own state directory (not your repo).

First run also scaffolds ~/.config/abox/config.yaml.


3. Bring it up

abox up
✔ network abox-net (created)
✔ gateway abox-gw-default healthy — Docker AI MCP Gateway 2.0.1
   http://abox-gw-default:8811/mcp (servers: none)
✔ artifacts rendered (2 masked path(s))
building agent image…
✔ agent image built: abox-agent-demo-app:3db0abeb6291

The first build takes a few minutes (base image pull, toolchains, Claude Code binary). It is cached afterwards and only rebuilds when the manifest changes.


4. Log in once

A fresh project has an empty auth volume, so the very first thing to do is an interactive session:

abox shell

Inside the container, run claude and complete the login. It persists in the per-project volume abox-claude-<hash>, so every later headless run reuses it. Exit the shell and the container is destroyed; the volume stays.

Skipping this step is the single most common "why did my run exit 1?" — a headless run with no login fails at authentication.


5. Run something

abox run "summarise what this repo does"

Each run gets a fresh container, applies the firewall, streams the transcript to runs/<ts>-<id>.jsonl, harvests the DNS log and iptables counters, then destroys the container. Your workspace and the auth volume persist.

abox run --continue "now list the risky spots"
abox run --resume <session-id> "and the tests?"

6. Check what happened

abox doctor

A healthy project looks like:

26 ok, 1 warn, 0 fail, 1 skipped

The usual warn is an unpinned gateway image; doctor prints the exact digest to paste into ~/.config/abox/config.yaml.

The part worth reading every time is the egress review queue — names the agent looked up that the firewall refused:

! egress review queue: 2 domain(s) looked up but not allowed:
    telemetry.vendor.io (x14), pastebin.com (x1)
  ↳ promote deliberately with `abox egress add <domain>`

Act on it either way — the queue is only useful while it means undecided:

abox egress add api.example.com          # allow it
abox egress ignore telemetry.vendor.io   # still blocked, stop listing it
abox logs --runs      # run index
abox logs --dns       # every lookup, marked allowed/denied
abox logs --gateway   # gateway container output, tokens redacted

Note that MCP tools are not subject to the agent's firewall: they execute in the gateway's server containers. A server like curl, filesystem, or docker therefore reaches past the agent's egress allowlist and mask overlays by design. abox doctor names those servers so the sandbox never looks tighter than it is.


Adding capability later

Whatever you already have on this host:

abox mcp import          # inventory: what can come in, and what cannot
abox mcp import --apply  # declare the importable ones
abox up

Your Docker MCP Toolkit servers come in behind the gateway and keep working with the secrets already in your Docker store. A local stdio server (a host binary) cannot cross into the sandbox without mounting it in, and import says so rather than doing it quietly. claude.ai connectors are off by default — see the README section on them for the two ways to turn them on.

A container-based MCP server:

abox mcp add github-official
abox secrets set github.personal_access_token   # prompted, never echoed
abox up

A hosted MCP server (Context7, Notion, Asana, Linear — anything internet-hosted). The gateway proxies it, so the agent gets a tool but no new network path:

abox mcp add asana                     # already in the catalog as type: remote
abox mcp oauth asana                   # host-side OAuth, token lands in the keychain

abox mcp add-remote context7 --url https://mcp.context7.com/mcp
abox up
abox gateway status --tools            # see exactly what the agent will see

A self-hosted image server, fully local — e.g. Serena (semantic code tools). It runs as a container behind the gateway. Custom images are normally digest-pinned; to avoid pushing a build to a registry just to pin it, set pin: false and abox runs your local tag on trust — it won't try to pull it, and the gateway never signature-checks images outside docker.io/mcp/*:

git clone https://github.com/oraios/serena && cd serena
docker build -t serena:local .
# ~/.config/abox/custom-servers.yaml   — a bare name -> server mapping
serena:
  image: serena:local
  pin: false                 # trust this local build; abox will not pull it
  env: { SERENA_DOCKER: "1" }
  volumes:
    - /abs/path/to/project:/workspace/project    # Serena needs the code
  command: [serena, start-mcp-server, --transport, stdio,
            --context, claude-code, --project, /workspace/project]
abox mcp add serena && abox up

doctor flags it two ways, both true: an unpinned local image (no digest, no signature — the trust is yours), and a boundary-spanning server (it runs in the gateway's container with read-write access to the mounted code, outside the agent's firewall). The stock image bakes Node and Rust language servers, not Go.

A secret the agent's own work needs (not an MCP server — the agent itself):

abox secrets set database.url                  # prompted; or --file / --env / --stdin
abox secrets attach DATABASE_URL=database.url  # now in the agent's environment
abox up

abox passes a reference the Docker daemon resolves, so no plaintext lands in any file abox writes. But the agent can read it — that is the point — so doctor flags it every run and the egress allowlist becomes the real boundary.

Domain-level egress (stop domain fronting via shared CDN addresses):

# ~/.config/abox/config.yaml
egress_proxy:
  enabled: true

Every outbound 443 connection is then filtered by TLS server name at an nginx container, not by IP — so an allowed address carrying a forged SNI is refused. abox up starts it; abox doctor shows what it turned away.

A domain the agent needs:

abox egress add api.example.com

Takes effect on the next run, since every run gets a fresh container.


Turning on bypassPermissions

Set it in agentbox.yaml when you want unattended runs:

run:
  permission_mode: bypassPermissions

abox then refuses to run unless every boundary check passes — caps present, no docker socket, no published ports, artifacts unmodified, firewall live inside the container. Check first:

abox doctor

Tearing down

abox nuke              # containers + generated artifacts; prompts before the auth volume
abox nuke --keep-auth  # keep the Claude login

The gateway survives if another project still uses its profile.


Troubleshooting

Symptom Cause Fix
no space left on device mid-build Docker disk image full raise it in Settings → Resources, or docker image prune
abox run exits 1 immediately no Claude login in the volume abox shell, run claude, log in
gateway abox-gw-<p> is running: not created gateway never started abox up (or abox gateway up)
could not pull MCP server image the gateway starts servers with --pull never abox pre-pulls; a failure here is a real registry/disk problem
mounted artifacts are unmodified: ✖ something edited the files abox mounts abox up to re-render, then find out who
git config unchanged: ✖ added core.hookspath a hook or alias appeared review it, then abox doctor --accept-git to re-baseline
doctor warns about a mask a new file matches a mask glob but isn't covered abox up to re-render the overlays
artifacts match the manifest: manifest changed you edited agentbox.yaml, or abox's defaults moved abox up
a domain keeps reappearing in the queue you have decided against it but not recorded that abox egress ignore <domain>
my usual MCP servers are missing inside the sandbox a project starts with none declared abox mcp import
my claude.ai connectors are missing off by default; they are a second, unmediated MCP path abox mcp add <name> if the catalog has it, else run.connectors: true

Where things live

<your repo>/
  agentbox.yaml          commit this — it is the sandbox declaration
  .devcontainer/         review copy; abox never reads it back

~/.config/abox/
  config.yaml            network, gateway image, profiles, defaults
  secrets.yaml           source → docker secret name (references, never values)
  custom-servers.yaml    servers outside the Docker catalog

~/.local/state/abox/
  gateways/              per-profile token, registry, generated remote catalog
  <project-hash>/
    artifacts/           runspec.json (the literal docker argv), Dockerfile,
                         init-firewall.sh, mcp.json — mounted read-only
    runs/                one JSONL transcript per run
    runs.jsonl           run index
    dns-queries.jsonl    every name the agent looked up

The artifacts abox mounts live outside your repo on purpose: an agent that rewrites the firewall script in the workspace changes nothing except a doctor finding.