boxer.toml
Every boxer.toml key with its type, default and an example, where boxer reads configuration, and how devcontainer.json maps onto it.
# boxer.toml at the repository root
image = "node:24-bookworm-slim"
setup = ["npm ci"]
start = ["npm run dev"]
ready = "curl -sf http://127.0.0.1:3000/"
[network]
mode = "allowlist"
allow_hosts = ["registry.npmjs.org"]
ports = ["auto:3000"]
[tasks]
test = "npm test"Every key is optional. boxer doctor prints each resolved value and the file or variable it came
from.
Files and precedence
boxer merges these in order, each overriding the one before.
- Built-in defaults, as listed on this page.
.devcontainer/devcontainer.json, else.devcontainer.json, from the worktree root, else the repository root, so a branch that changes its devcontainer gets its own. See the mapping.$XDG_CONFIG_HOME/boxer/boxer.toml, else~/.config/boxer/boxer.toml.boxer.tomlat the repository root (the main checkout).boxer.tomlat the worktree root, when it differs from the repository root.BOXER_*environment variables.- The
[harness.<name>]table for the harness making the call.
A later file replaces a list and merges into a table key by key. An unknown key is an error, so a typo cannot weaken enforcement. An unknown top-level table is a warning, so a repository written for a newer boxer still loads. A value outside its allowed set is an error that names the allowed values.
Scope
A scope is what one sandbox belongs to.
isolation
string, default "worktree". One of repo, worktree, session, subagent. Env BOXER_ISOLATION.
What a sandbox is keyed to. session and subagent need an id from the harness or the
identity flags. Session and subagent sandboxes in one worktree
all mount that same worktree, so two dev servers or installs there still write to the same
node_modules and build directories. For dev servers, give each session its own worktree.
isolation = "session"on_missing_id
string, default "degrade". One of degrade, fail. Env BOXER_ON_MISSING_ID.
What session or subagent isolation does with no id. degrade falls back from subagent to
session to worktree and warns. fail refuses with SCOPE_UNRESOLVED.
on_missing_id = "fail"require_worktree
string, default "warn". One of off, warn, require. Env BOXER_REQUIRE_WORKTREE.
What happens in the main checkout instead of a linked worktree. warn runs and warns, require
refuses with WORKTREE_REQUIRED, off says nothing.
require_worktree = "require"worktree.manage
string, default "off". One of off, detect. Env BOXER_WORKTREE_MANAGE.
detect makes a session in the main checkout share the repository's sandbox, with a warning, until
it moves into a linked worktree. It applies only when isolation = "worktree".
[worktree]
manage = "detect"Integration
integration
string, default "outside". One of outside, inside. Env BOXER_INTEGRATION.
outside runs the harness on the host and its commands in the sandbox. inside runs the harness in
the guest. boxer shell and boxer acp set it. See running the harness inside.
integration = "inside"mode
string, default "rewrite". One of rewrite, tool, off. Env BOXER_MODE.
What a hook does with an intercepted command. rewrite turns it into boxer run -c '<command>'.
tool denies it and names boxer_run and boxer run. off lets everything through.
mode = "tool"enforcement
string, default "both". One of hook, shim, both, audit. Env BOXER_ENFORCEMENT.
Which mechanisms keep commands in the sandbox. With shim or both, a hook on a harness that
cannot rewrite (Kimi, DSH) lets the bare command reach the shim,
but only when boxer's shims are on the harness's PATH and the command names the program bare. Without
shims, or for a program named by path (/opt/homebrew/bin/npm), the hook denies it with the
boxer run line to use. With hook, the hook always denies it. audit makes hooks print
boxer: audit: would <action> on stderr and allow everything. Shims follow the same setting: under
mode = "off", enforcement = "hook" or "audit", a shim runs its program on the host.
enforcement = "hook"on_sandbox_unavailable
string, default "fail". One of fail, passthrough. Env BOXER_ON_SANDBOX_UNAVAILABLE.
What boxer run does when the sandbox cannot be provisioned. fail refuses. passthrough runs
the command on the host, unsandboxed.
on_sandbox_unavailable = "passthrough"What gets sandboxed
intercept
[]string, default below.
Programs whose commands run in the sandbox. The sandbox's PATH is the image's own, without node_modules/.bin, so run project tools as
npx vite or npm run dev rather than adding bare vite or tsc here. A program matches by
name, after skipping leading VAR=value assignments and any path, so
bin/rails and ./node_modules/.bin/next match rails and next. List names, not paths: letters,
digits, ., _, + and -, the same rule for intercept_also and passthrough.
boxer reads the whole line the way a shell would, and wraps it whole when any program in it matches. That includes:
- every command joined by
&&,||,;,|,&or a newline - commands inside
$(...), backticks and<(...), even in double quotes - the line given to
sh -c,bash -lcand the others, and toeval - the program behind
env,sudo,nohup,nice,time,timeout,xargs,command,exec,taskset,chrt,busyboxand similar wrappers - the commands inside
if,while,until,for,case,{ ... }and a function body, and the line atrapwill run - a
$(...)in an array index, quoted or not, which arithmetic runs
Text in quotes is text: git commit -m "fix npm test" stays on the host. A line boxer cannot read
goes to the sandbox: an unclosed quote, a wrapper option boxer does not know (it may take a value
boxer would misread as the program), and an alias, whose new name boxer cannot follow. A script run by path (bash build.sh,
./build.sh) is one program, its own name, and boxer does not read inside it: add the script's
name, or intercept the tools it calls through PATH shims (enforcement = "both", the default, once
boxer shim install has written them and they are on the harness's PATH).
["*"] intercepts every program not in passthrough. It writes no PATH shims, since there is no
program to name, so enforcement rests on the hook. Not settable from the environment.
intercept = ["npm", "npx", "pnpm", "yarn", "bun", "bunx", "node", "deno",
"python", "python3", "pip", "uv", "pytest", "cargo", "rustc",
"go", "make", "cmake"]Under enforcement = "shim" or "both", boxer shim install writes a file for every entry. A
node or python shim shadows the interpreter of a harness written in that language, and the
harness fails at startup. Narrow the list to build tools first.
intercept_also
[]string, default [].
Programs added to intercept, so you keep the default list and name only what your project needs on
top. A program already in intercept is not added twice. boxer doctor suggests it for a Ruby or
JVM project whose tools the default list leaves on the host. Not settable from the environment.
intercept_also = ["bundle", "rake", "rails", "rspec"]passthrough
[]string, default ["git", "gh", "ssh", "boxer", "smolvm"].
Programs that never send a line to the sandbox by themselves. A line made only of passthrough
and unlisted programs runs on the host. A line that also runs an intercepted program is wrapped
whole, so git pull && npm ci runs both in the sandbox. boxer is always treated as passthrough.
Not settable from the environment.
passthrough = ["git", "gh", "ssh", "boxer", "smolvm", "terraform"]Backend
backend
string, default "smolvm". One of smolvm, container, docker, podman. Env BOXER_BACKEND.
The runtime that hosts sandboxes. A backend that lacks a capability refuses the setting that needs
it with cause UNSUPPORTED. Only smolvm supports network.mode = "allowlist", the default, so
set network.mode with any other backend. See backends.
backend = "docker"
network.mode = "on"Guest
image
string, default "". Env BOXER_IMAGE.
The guest image: an OCI reference, or a local docker save archive or rootfs directory given as a
path starting ./, ../, / or ~/, or - for stdin. A local image is not pulled. When empty, boxer
picks the image from the first lockfile found in the worktree.
| Lockfile | Image |
|---|---|
Gemfile.lock | ruby:3-slim-bookworm |
pom.xml | maven:3-eclipse-temurin-21 |
build.gradle, build.gradle.kts | gradle:8-jdk21 |
bun.lock, bun.lockb | oven/bun:1-slim |
pnpm-lock.yaml, package-lock.json, yarn.lock | node:24-bookworm-slim |
uv.lock, requirements.txt, pyproject.toml | python:3.12-slim-bookworm |
Cargo.lock | rust:1-slim-bookworm |
go.sum, go.mod | golang:1-alpine |
| none | debian:bookworm-slim |
With integration = "inside" and no image, the default is
mirror.gcr.io/library/node:24-bookworm-slim. The Node, Python and Ruby images carry no compiler.
Add one in image_setup. See choosing an image.
image = "node:24-alpine"smolfile
string, default "". Env BOXER_SMOLFILE.
A smolvm Smolfile to build the guest from. Takes precedence over image. Other backends refuse it.
smolfile = "./Smolfile"build
string, default "".
A Dockerfile, relative to the worktree, that boxer builds and boots instead of image. The build
runs on the host every time a sandbox is created. The builder's layer cache makes an unchanged
build take a second, and a file the Dockerfile copies can change without the Dockerfile changing.
| Backend | Built by | Boots |
|---|---|---|
docker, podman | docker build, podman build | the tag boxer-<hash> |
container | container build | the tag boxer-<hash> |
smolvm | the host's docker build, then docker save | the archive, under boxer's state directory |
On smolvm, docker must be installed on the host even though the sandbox is a microVM. The last two
archives per Dockerfile are kept. The tag hashes the Dockerfile's content, so every worktree of a
repository shares one image. Build arguments are not passed. Cannot be set with smolfile. Fails
with cause BUILD_FAILED when the build does.
build = "Dockerfile"build_context
string, default "".
The directory the build sees, relative to the worktree. Empty means the directory holding the Dockerfile.
build = ".devcontainer/Dockerfile"
build_context = "."cpus
int, default 4. Env BOXER_CPUS.
Virtual CPUs for the guest.
cpus = 2memory
string, default "4G". Env BOXER_MEMORY.
Guest memory: 4G, 4096M, or a bare number of MiB. A setup step that exits 137 ran out of memory.
memory = "8G"mount_at
string, default "/workspace". Env BOXER_MOUNT_AT.
Where the worktree is mounted in the guest, an absolute path. boxer run from a subdirectory runs in the matching
subdirectory. With integration = "inside" the worktree is mounted at its host path instead.
mount_at = "/src"mounts
[]string, default [].
Extra host directories, as host:guest or host:guest:ro. A leading ~/ in the host half is
expanded. The worktree is always mounted.
mounts = ["~/.m2:/root/.m2:ro"]volumes
[]string, default [].
Named directories that outlive the sandbox, as name:/guest/path. Each is a directory on the host,
~/.local/state/boxer/volumes/<scope>/<name>, bind-mounted read-write on every backend. It survives
boxer down, boxer rm, --recreate, --rebuild and idle reclaim, so a database keeps its data.
It is deleted by boxer rm --volumes, or by boxer gc once its worktree has been gone for 7 days.
The week covers a drive that was unplugged, or a repository moved and moved back: the first sweep
that finds the worktree missing only marks the volumes, and a worktree that comes back clears the
mark. Volumes belong to the worktree's path, so a repository moved for good starts with empty ones
at its new path, and a new worktree created at an old one's path gets the old one's volumes.
An orchestrator that deletes a worktree when you archive a task takes its volumes with it a week
later. Copy out what you want to keep first. A name is letters, digits, - and _.
Volumes are not part of the environment pack.
volumes = ["pgdata:/var/lib/postgresql/data"]user
string, default "".
The guest user for setup, start and every command, as a name or uid[:gid], made of letters,
digits, _, . and -. Empty uses the image's USER. image_setup always runs as root. When the user cannot write the worktree, boxer
gives it your host uid on smolvm and recreates the sandbox once with --userns=keep-id on podman.
user = "node"Prep
[prep] runs commands on the host while the sandbox boots. See
dependencies. Because these run on the host,
prep is one of the keys held back until a person approves the configuration with
boxer trust.
prep.commands
[]string, default [].
Run on the host with sh -lc, in the worktree, once per worktree, and again when the list or the
target changes. A failure stops provisioning with PREP_FAILED. The commands get
BOXER_TARGET_OS, BOXER_TARGET_CPU, BOXER_TARGET_LIBC, BOXER_TARGET_FLAGS
(--os=linux --cpu=arm64 --libc=musl) and BOXER_WORKTREE. boxer never adds the flags to your
command.
[prep]
commands = ["npm ci $BOXER_TARGET_FLAGS"][prep] is only safe for packages that ship prebuilt platform binaries. A package that compiles at
install time (node-gyp, cgo) builds for the host and fails in the guest with invalid ELF header.
boxer doctor names such packages.
prep.target
string, default "auto".
The guest platform offered to prep.commands. auto derives it from the image, none offers
nothing, and os/cpu/libc sets it.
[prep]
target = "linux/arm64/musl"Cache
cache.enabled
bool, default false. Env none.
Mount the host's package caches into the guest, read-only. Installs still run in the guest, so only the download is saved. Installing a package the host has not cached fails, because the tool cannot write to its read-only cache. See host package caches.
[cache]
enabled = truecache.managers
[]string, default ["auto"].
Which caches. auto detects them from lockfiles. Otherwise any of npm, pnpm, yarn, bun,
uv, poetry, cargo, go.
[cache]
managers = ["pnpm", "cargo"]Setup and services
See environment for when to use each.
image_setup
[]string, default [].
Commands that change the image, run in the guest as root, once per sandbox (on smolvm, once per host). On smolvm the result is
saved in the environment pack, so the next worktree skips
them. A failure is SETUP_FAILED.
image_setup = ["apt-get update && apt-get install -y build-essential"]setup
[]string, default [].
Commands that prepare this worktree, run in the guest as user, once per worktree and again when
the list changes or on boxer up --rebuild. A pack never skips them, because the worktree is
mounted from the host. They must write into the worktree: a recreated sandbox does not run them
again, so anything installed elsewhere is lost. A failure is SETUP_FAILED, and the next command
in that sandbox runs setup again rather than taking a running sandbox as ready.
setup = ["npm ci"]start
[]string, default [].
Commands run detached every time the sandbox starts, after setup. A service that crashes is
restarted, as restart says. Their output goes to /tmp/boxer-start.log in the guest. A step that cannot be launched is
START_FAILED.
start = ["npm run dev"]ready
string, default "".
A command polled in the guest until it exits 0, before the sandbox is reported up. Empty means no wait.
ready = "curl -sf http://127.0.0.1:3000/"ready_timeout
string (Go duration), default "60s".
How long to poll ready. When it runs out, the command fails with NOT_READY and quotes the tail of
the start log.
ready_timeout = "3m"restart
string, default "on-failure". One of on-failure, never.
What happens when a start service exits non-zero. on-failure restarts it after 1 second, then
2, 4 and so on up to 30. A run that lasted 10 seconds or more resets the delay. After five quick
crashes in a row it gives up, and the start log says so. A service that exits 0 has finished and is
not restarted. never leaves a crashed service down. Every restart is a line in
/tmp/boxer-start.log. boxer restart relaunches the services by hand.
restart = "never"Environment
env
table of strings, default {}.
Set in the guest for every command. Part of the environment pack's key and saved in the pack, so
keep secrets out of it. A later file replaces a secrets or env_passthrough list whole rather
than adding to it, so a secrets = [...] in boxer.toml drops the names a devcontainer forwarded;
list them together if you need both. Names are letters, digits and _, not starting with a digit, as are the
names in secrets and env_passthrough.
[env]
NODE_ENV = "development"env_passthrough
[]string, default ["CI"].
Host variables forwarded to the guest by name at run time, when the host sets them. Values never
enter a command line, a pack or an event. They reach setup and image_setup too.
env_passthrough = ["CI", "DEBUG"]secrets
[]string, default [].
Forwarded exactly like env_passthrough. List credentials here so the file says which variables
are secrets. See secrets.
secrets = ["NPM_TOKEN"]Network
network.mode
string, default "allowlist". One of allowlist, on, off. Env BOXER_NETWORK_MODE.
allowlist lets the guest reach allow_hosts plus the registry hosts its image pull needs.
on allows all traffic. off turns the network off. allowlist needs backend = "smolvm".
[network]
mode = "on"network.allow_hosts
[]string, default [].
What the guest may reach under allowlist. Each entry is one of these.
| Entry | Example | Allows |
|---|---|---|
| a hostname | registry.npmjs.org | that exact name, and the addresses it resolves to when the sandbox starts |
| an address | 203.0.113.7, 2001:db8::1 | that address |
| a CIDR block | 10.0.0.0/8, 2001:db8::/48 | every address in the block |
| an address range | 10.0.0.10-10.0.0.40 | every address from the first to the last, inclusive |
An address, block or range is reachable on every port and needs no DNS. A hostname is resolved once,
when the sandbox starts. If its addresses change later, run boxer up --recreate. A name that is
not listed does not resolve in the guest at all.
These are refused when boxer.toml loads, with an error that names the entry:
- Wildcards, such as
*.example.com. smolvm matches exact names only, and it accepts a wildcard without allowing anything. List each host, or useallow_presets. - Ports, such as
example.com:443orexample.com:8000-8100. Every allowed host or range is reachable on every port. - A block with host bits set, such as
10.0.0.1/8. The error suggests10.0.0.0/8.
A failed command names the hosts and addresses the allowlist refused, and boxer doctor lists them
under denied_hosts.
[network]
allow_hosts = ["registry.npmjs.org", "github.com", "10.20.0.0/16", "192.168.1.10-192.168.1.20"]network.allow_presets
[]string, default [].
Package ecosystems whose hosts join allow_hosts, so you do not have to find out which CDN a
registry downloads from. Only used under allowlist. Under on or off it is ignored, without an
error, so one boxer.toml works on every backend.
| Preset | Hosts |
|---|---|
alpine | dl-cdn.alpinelinux.org |
crates | crates.io, index.crates.io, static.crates.io |
debian | deb.debian.org, security.debian.org |
github | github.com, api.github.com, codeload.github.com, objects.githubusercontent.com, raw.githubusercontent.com |
go | proxy.golang.org, sum.golang.org, storage.googleapis.com |
gradle | services.gradle.org, downloads.gradle.org, plugins.gradle.org, plugins-artifacts.gradle.org, repo.maven.apache.org |
maven | repo.maven.apache.org, repo1.maven.org |
npm | registry.npmjs.org |
pypi | pypi.org, files.pythonhosted.org |
rubygems | rubygems.org, index.rubygems.org, rubygems.global.ssl.fastly.net |
rust | static.rust-lang.org, sh.rustup.rs |
yarn | registry.yarnpkg.com, repo.yarnpkg.com, registry.npmjs.org |
[network]
allow_presets = ["npm", "github"]network.ports
[]string, default [].
Port forwards. host:guest and a bare guest are bound to 127.0.0.1; an explicit address you
write (0.0.0.0:8080:3000, [::1]:8080:3000) is kept as written, so only you can publish on every
interface. A /tcp, /udp or /sctp suffix and one-to-one ranges (8000-8002:8000-8002) are
allowed. auto:<guest> takes a free host port, so worktrees do not collide, and never takes a port
a stopped sandbox holds. boxer status shows which host port it got. When no port is free, the
command fails with NO_FREE_PORT. A port is 1 to 65535.
[network]
ports = ["auto:3000", "5432:5432"]network.dns
string, default "".
The guest resolver on smolvm. Empty uses the host's caching resolver, off uses smolvm's default
public resolvers, and an address is used as given.
[network]
dns = "10.0.0.2"URLs
[urls] names forwarded ports through portless. See
URLs.
urls.enabled
bool, default false. Env BOXER_URLS (a boolean).
Give each forwarded port a stable name, such as https://fix-ui.myapp.localhost:1355. Needs
portless on the host. Without it, boxer warns and runs as if this were off.
[urls]
enabled = trueurls.provider
string, default "portless". One of portless.
What serves the names.
[urls]
provider = "portless"urls.name
string, default "".
The base hostname, before the worktree prefix. Empty uses the repository's directory name.
[urls]
name = "shop"urls.names
table of strings, default {}.
A label per guest port, giving <label>.<name>. One forwarded port needs no label. Several
unlabelled ports are named by their number.
[urls]
names = { "3000" = "web", "8080" = "api" }Tasks
tasks.<name>
string or table, default none.
A named command line, run with boxer run --task <name>, or with the boxer_run MCP tool's task
argument. The table form takes cmd (required), description, junit (paths or globs), timeout
(a Go duration) and env. Any other key is an error. An empty cmd or a bad timeout is an error.
A task is replaced whole by a later file, not merged key by key: a later file that sets only
description for a task drops its cmd. See tasks.
[tasks]
test = "npm test"
[tasks.e2e]
cmd = "npm run e2e"
description = "the browser suite; needs the dev server"
junit = ["reports/junit.xml"]
timeout = "10m"
env = { HEADLESS = "1" }Results
See test results.
results.auto
[]string, default [].
Worktree-relative JUnit XML paths or globs to summarise after every boxer run. A task's junit
and the --junit flag take precedence.
[results]
auto = ["reports/*.xml"]results.fail_on_test_failures
bool, default false.
Exit 1 when the command exited 0 and a test in the reports failed. A non-zero exit is never changed.
[results]
fail_on_test_failures = trueLifecycle
create_on
[]string, default ["session_start", "run", "mcp"].
Signals that may create a sandbox. Any of session_start, subagent_start, run, mcp. Without
run, boxer run fails with NO_SANDBOX until boxer up has run.
create_on = ["session_start", "subagent_start", "run", "mcp"]destroy_on
[]string, default [].
Hook signals that delete the sandbox. Any of session_end, subagent_stop, never. Empty keeps
the sandbox after the session, for gc to reclaim when idle.
destroy_on = ["session_end"]warm_on_session_start
bool, default false. Env BOXER_WARM_ON_SESSION_START.
When true, the session-start hook provisions in a detached boxer up and returns at once, so the
session never waits for a create. The first intercepted command waits for it instead. When false,
the session-start hook waits for the sandbox, up to the hook's 10-minute limit.
warm_on_session_start = trueReclaiming
auto_reclaim
bool, default true.
When true, ordinary commands start the gc sweep in the
background, at most once per reclaim_every. BOXER_NO_RECLAIM=1 skips it for one process.
auto_reclaim = falsereclaim_every
string (Go duration), default "6h".
The minimum interval between automatic sweeps.
reclaim_every = "1h"idle_timeout
string (Go duration or "never"), default "2h".
A sandbox or environment pack unused this long is reclaimable. What happens to a sandbox is
idle_action. never turns the idle rule off. gc judges each sandbox by its own
repository's idle_timeout and idle_action, wherever it runs, so one repository's settings never
reclaim another's sandboxes. The current directory's value ages packs. A sandbox with a
fork child in use is not idle.
idle_timeout = "24h"idle_action
string, default "stop". One of stop, delete.
What idle reclaim does to a sandbox whose worktree still exists. stop frees its memory and keeps
its disk, so the next command starts it again with what it had installed and stored. delete frees
both, which was the only behaviour before 1.2. A sandbox whose worktree is gone is always deleted.
gc --all still deletes stopped sandboxes.
idle_action = "delete"min_free_gb
float, default 5.
The free disk space boxer keeps, in GB. Below it, boxer skips writing a pack and pulls the image instead, and
boxer doctor warns. 0 turns the check off.
min_free_gb = 20packs_keep_last
int, default 5.
Keep at most this many unused packs, whatever their age: environment, image and harness packs
together, newest first. A pack a sandbox was created from is not counted and not removed. 0 keeps
every pack the idle rule allows.
packs_keep_last = 2Harness overrides
harness.<name>
table, default none.
Overrides for one harness, applied after every file and variable. Only mode, enforcement and
isolation are accepted, each checked against the same values as the top-level key. <name> is the harness name boxer install takes, and the call selects
it with --harness or BOXER_HARNESS. Inside mode's program names work too: [harness.claude]
and [harness.claude-code] configure the same harness. A name boxer does not know is an error, so
a misspelt table cannot quietly do nothing.
[harness.kimi]
mode = "tool"
enforcement = "hook"
isolation = "session"Telemetry
See events.
telemetry.enabled
bool, default false.
Record events. Off means no log, no metrics and no network. true with sink = "none" uses file.
[telemetry]
enabled = truetelemetry.sink
string, default "none". One of none, file, stderr, otel. Env BOXER_TELEMETRY_SINK.
Where events go. Setting the variable to anything but none also sets enabled.
[telemetry]
enabled = true
sink = "stderr"telemetry.path
string, default "".
The file sink's path. Empty uses $XDG_STATE_HOME/boxer/events.jsonl, else
~/.local/state/boxer/events.jsonl. BOXER_TRACE overrides it.
[telemetry]
path = "/tmp/boxer-events.jsonl"telemetry.record_commands
bool, default false.
Keep command lines, argv, env and setup in event payloads. Otherwise each is replaced by its
length, as command_len and so on.
[telemetry]
record_commands = truetelemetry.endpoint
string, default "".
The OTLP endpoint for sink = "otel". Only a binary built with -tags otel has that sink.
[telemetry]
sink = "otel"
endpoint = "http://127.0.0.1:4318"Deprecated
These still load, so a boxer.toml that boxer 1.0 read keeps working. Each has no effect, prints a
warning, and will be removed in 2.0.
require_linked_worktree
bool, default false. Use require_worktree = "require" instead.
reuse_existing
bool, default true. A scope always reuses its sandbox. Delete the line.
Environment variables
Eighteen scalar keys can be set from the environment, for a harness that offers no other way to
configure a subprocess. Lists and tables cannot. A variable overrides every file, and a
[harness.<name>] table overrides the variable. boxer doctor names the variable as the source.
| Variable | Key |
|---|---|
BOXER_BACKEND | backend |
BOXER_ISOLATION | isolation |
BOXER_ON_MISSING_ID | on_missing_id |
BOXER_REQUIRE_WORKTREE | require_worktree |
BOXER_WORKTREE_MANAGE | worktree.manage |
BOXER_INTEGRATION | integration |
BOXER_MODE | mode |
BOXER_ENFORCEMENT | enforcement |
BOXER_ON_SANDBOX_UNAVAILABLE | on_sandbox_unavailable |
BOXER_IMAGE | image |
BOXER_SMOLFILE | smolfile |
BOXER_MOUNT_AT | mount_at |
BOXER_CPUS | cpus (an integer) |
BOXER_MEMORY | memory |
BOXER_NETWORK_MODE | network.mode |
BOXER_URLS | urls.enabled (a boolean) |
BOXER_TELEMETRY_SINK | telemetry.sink, and telemetry.enabled unless the value is none |
BOXER_WARM_ON_SESSION_START | warm_on_session_start (a boolean) |
A value that does not parse as its type is an error that names the variable, as a bad value in
boxer.toml is. BOXER_URLS holding a port map, which boxer sets inside the guest, is not read as
this setting. Variables with no key are listed in the
CLI reference.
devcontainer.json
boxer reads the part of a devcontainer file it can honour. It is the lowest file layer,
so any key boxer.toml sets wins. Comments and trailing commas are accepted.
| devcontainer.json | boxer.toml |
|---|---|
image | image |
build.dockerfile, or dockerFile | build, rewritten to be relative to the worktree |
build.context | build_context, likewise |
initializeCommand | prep.commands, on the host, as the specification says |
onCreateCommand | image_setup |
updateContentCommand, then postCreateCommand | setup |
postStartCommand | start |
workspaceFolder | mount_at |
forwardPorts | network.ports as auto:<port>. A "service:port" string uses the port |
portsAttributes.<port>.requireLocalPort | That forward kept fixed, as <port>:<port> |
portsAttributes.<port>.label | urls.names |
appPort | network.ports, fixed, as Docker's -p is |
containerEnv, remoteEnv | env. remoteEnv wins where both name a variable |
mounts | mounts, bind mounts only |
remoteUser, else containerUser | user |
hostRequirements.cpus, hostRequirements.memory | cpus, memory, raised to the minimum, never lowered |
A command given as a string or an array is one line. An object of named commands becomes one line per name, in name order.
Variables are resolved on the host in every string: ${localWorkspaceFolder} and
${localWorkspaceFolderBasename} (this worktree), ${containerWorkspaceFolder} and
${containerWorkspaceFolderBasename} (the mount), and ${localEnv:NAME} or
${localEnv:NAME:default}, also spelled ${env:NAME}. ${containerEnv:NAME} resolves inside
remoteEnv when containerEnv defines the name. Another ${containerEnv:NAME} or
${devcontainerId} gets a warning, and an env value or mount that uses it is dropped.
An entry that passes a host variable under its own name, "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}",
becomes one of secrets rather than env: env is stored in the environment pack and
passed by value, and a secret is read from the host when each command runs. Any other entry that
takes a host value is kept in env, with a warning naming it. A byte-order mark at the start of the
file is ignored.
These are refused with a warning and have no effect.
| Key | Why |
|---|---|
features | boxer does not install features. Put them in image_setup or in the Dockerfile build names |
build.args, build.target, build.cacheFrom, build.options | boxer builds the Dockerfile as it is |
dockerComposeFile | It runs several containers. boxer runs one guest per worktree |
runArgs, privileged, capAdd, securityOpt | They would change the sandbox's own boundary |
workspaceMount | boxer always mounts the worktree, at mount_at |
overrideCommand: false | boxer never runs the image's own command. Put it in start |
postAttachCommand | Nothing attaches to a sandbox |
init | The guest already reaps what it starts |
hostRequirements.gpu, unless false or "optional" | No backend passes a GPU through |
Every other key is ignored, including name, customizations, shutdownAction,
userEnvProbe, otherPortsAttributes, waitFor (use ready) and hostRequirements.storage
(min_free_gb covers disk). boxer always makes user able to write the worktree, as
updateRemoteUserUID asks.