boxer

Coming from your own scripts

Map the scripts and hooks you use today to boxer, run boxer next to them, and switch over gradually.

If you already give each worktree its own environment with scripts, boxer replaces most of them with configuration.

What maps to what

You do this todayboxer does it with
docker compose up per worktreesetup and start in boxer.toml, in one sandbox per worktree (services)
a script that picks a free portports = ["auto:3000"], and boxer url to find it
a post-checkout hook that starts the stackboxer install git, which starts the sandbox in every new worktree
a Claude Code or Codex hook that wraps commands in docker execboxer install claude-code or boxer install codex
a teardown script when a worktree is removedautomatic within 6 hours, or boxer rm --gone -y at once
a named Docker volume for the databasevolumes = ["pgdata:/var/lib/postgresql/data"], kept until a week after the worktree goes
an .env per worktree with its own ports[env] for fixed values, and boxer status --json for each worktree's host ports and URLs
a Dockerfile for the dev imagebuild = "Dockerfile", built on the host and shared by every worktree (Docker setup)

boxer does not replace a multi-container Compose stack as it is. It runs one sandbox per worktree, so run the services inside it. docker is not on the intercept list, so an agent that runs docker compose up still starts containers on your machine, with the same port clashes as before.

Run boxer next to your setup

boxer adds to your configuration. It does not replace it.

  • Your agent hooks stay. boxer install adds its hooks to .claude/settings.json or .codex/hooks.json next to yours. If one of your hooks also rewrites commands, remove it or limit it to commands outside boxer's intercept list. Two hooks that both rewrite the same command conflict, and which one wins depends on the agent.

  • Your git hook stays. boxer install git adds a marked block to your existing post-checkout hook, and honours core.hooksPath. It runs boxer up --detach on any checkout that changes files, which does nothing when the sandbox is already running. With husky, do not run boxer install git: husky's hooks path is a generated, gitignored wrapper. Add this line to .husky/post-checkout instead, and commit it:

    [ "$3" = "1" ] && command -v boxer >/dev/null 2>&1 && boxer up --detach >/dev/null 2>&1
  • You choose which commands go to the sandbox. intercept lists the programs that run in the sandbox. Everything else, including docker, runs on your machine as before.

boxer.toml
intercept = ["npm", "node", "pytest"]    # only these go to the sandbox

Switch over gradually

  1. Watch first. Set enforcement = "audit". The hooks print what they would do (boxer: audit: would rewrite: npm test) and run every command on your machine as before. The sandbox is still created at session start, so you can check it boots.
  2. Try it on one branch. Commit the boxer install files on a branch and work in worktrees cut from it. Other branches are unaffected.
  3. Turn it on. Remove enforcement = "audit", and retire the scripts boxer now covers.

To stop at any point, set mode = "off", or start a session with BOXER_MODE=off.

Drive boxer from your scripts

Every command that reports state takes --json, and the exit codes are stable.

$ boxer status --json           # exit 0 running, 3 stopped, 4 absent (some fields left out)
{
  "state": "running",
  "worktree": "/Users/me/src/myapp-fix-ui",
  "ports": { "3000": "61234" },
  "urls": { "3000": "https://fix-ui.myapp.localhost:1355" }
}
boxer ls --json                 # every sandbox, its worktree, branch and URLs
boxer run -c 'npm test'         # run a command in this worktree's sandbox, with its exit code

Build on boxer covers the JSON output, the Go package and writing your own integration.

On this page