boxer

Examples

Working boxer.toml files for Next.js, Vite, Python, Go, monorepos, devcontainers and other backends, and a Go program that embeds boxer.

The repository's examples/ directory has one for each common setup:

examplekindshows
nextjsconfiga Next.js dev server per worktree, [urls], allowedDevOrigins, tasks
nextjs-devcontainerconfigthe same from devcontainer.json plus a short boxer.toml, with users, port labels and variables
viterunnableVite with --strictPort, a name per worktree
python-fastapirunnableuv, with a tool in image_setup and the project in setup
go-apirunnablea Go service, tasks for test and vet
monorepoconfigtwo services in one sandbox, named ports, JUnit-reporting tasks
host-prepconfiginstalling on the host with [prep], and when not to
docker-backendconfigthe same project on docker or podman, and what that gives up
go-embedprogrampkg/boxer running one command in several worktrees at once
kindwhat it is
runnablea project you can run as is
configa boxer.toml to drop into an existing project
programa Go program that uses boxer as a library

Every boxer.toml there except monorepo's has been run on a real sandbox.

Run an example

A runnable example must be its own git repository, because boxer keys a sandbox to a worktree:

cp -R examples/go-api /tmp/go-api && cd /tmp/go-api
git init -q && git add -A && git commit -qm init
boxer up && curl -sk "$(boxer url)"

The snippets below are trimmed. The files have comments and more tasks. Each directory's README.md lists its gotchas.

Next.js

image = "mirror.gcr.io/library/node:24-alpine"
setup = ["npm ci --no-audit --no-fund"]
start = ["npx next dev --webpack -p 3000 -H 0.0.0.0"]
ready = "node -e \"fetch('http://127.0.0.1:3000/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))\""
ready_timeout = "240s"

[network]
allow_hosts = ["registry.npmjs.org"]
ports = ["auto:3000"]

[urls]
enabled = true

[tasks.test]
cmd = "npm test"
description = "the unit tests"
// next.config.ts
const nextConfig = { allowedDevOrigins: ["127.0.0.1", "localhost", "*.localhost"] };
export default nextConfig;

Run boxer up, then boxer url. A second worktree gets a second server at https://<branch>.<repo>.localhost:1355, with its own node_modules and .next.

Python with uv

image = "mirror.gcr.io/library/python:3.12-slim"
image_setup = ["pip install --no-cache-dir uv"]   # a tool: once, kept in the pack
setup = ["uv sync"]                               # the project: once per worktree, into .venv
start = ["uv run uvicorn main:app --host 0.0.0.0 --port 8000"]
ready = "python -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/').status == 200 else 1)\""
ready_timeout = "120s"

[network]
allow_hosts = ["pypi.org", "files.pythonhosted.org"]
ports = ["auto:8000"]

[urls]
enabled = true
  • uv goes in image_setup because installing it changes the image. It runs once and is cached for every worktree.
  • The project's dependencies go in setup because they land in this worktree's .venv.

See Install dependencies.

Go

image = "mirror.gcr.io/library/golang:1.24-alpine"
start = ["go run ."]
ready = "wget -q -O /dev/null http://127.0.0.1:8080/"
ready_timeout = "120s"

[network]
ports = ["auto:8080"]

[urls]
enabled = true

[tasks.test]
cmd = "go test ./..."
description = "the Go tests"

With module dependencies, add go mod download to setup and "proxy.golang.org" and "sum.golang.org" to allow_hosts. Go keeps modules outside the worktree, where a recreated sandbox loses them, so point its caches into the worktree and ignore that directory in git:

[env]
GOMODCACHE = "/workspace/.cache/gomod"
GOCACHE    = "/workspace/.cache/gobuild"
GOFLAGS    = "-modcacherw"

From a devcontainer

boxer reads .devcontainer/devcontainer.json first. Add a short boxer.toml beside it for what devcontainer has no key for. The main one is the allowlist, because boxer's default network reaches only the image registry:

[network]
allow_hosts = ["registry.npmjs.org"]

[urls]
enabled = true

Embedding boxer in Go

go-embed uses pkg/boxer to open a sandbox per worktree. It runs one shell line in all of them at once and prints each exit code. Run it from a boxer checkout:

go run ./examples/go-embed -- 'npm test' ~/code/app-wt1 ~/code/app-wt2

Each directory must be a git worktree. Each reads its own boxer.toml. Building on boxer and the Go reference cover the API.

Rules every example follows

  • The server binds 0.0.0.0. A forwarded port arrives on the guest's interface. A server on 127.0.0.1 never sees it.
  • Ports are auto:. A fixed host port makes the second worktree fail to start.
  • ready probes the server. boxer up returns when the server answers.
  • The allowlist names only the package registry. boxer allows the image registry itself. A failed install names any host that is missing.

On this page