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.