Language Guides
How to use boks with the language and framework you actually work in — and how to make it feel less like a wrapper and more like your toolchain.
Every guide in this section is organised around the same three levels. Read this page once; then each language guide is just "what levels 1, 2 and 3 look like here."
The three levels
boks is one binary, but there are three quite different experiences of it, depending on how much you have set up.
| Level 1 | Level 2 | Level 3 | |
|---|---|---|---|
| Installed | boks | boks | boks + shims |
| In the project | nothing | .boksrc | .boksrc |
| What you type | boks -e @npm:package.json -p 3000 node server.js | boks node server.js | node server.js |
| Where the knowledge lives | in your fingers | in a reviewed, committed file | in a reviewed, committed file |
| Feels like | a sandbox you are driving | your project, one word longer | your toolchain |
They are cumulative, not exclusive. Level 3 is level 2 plus a symlink, and a bare level-1 command keeps working at every level. You can also mix: level 3 for the project you live in, level 1 for the repo you cloned five minutes ago.
Level 1 — boks on the command line
Prefix boks and say what you need:
boks python script.py
boks -e @pip:requirements.txt python script.py
boks -p 3000 node server.js --host 0.0.0.0
boks --cap net curl https://example.com
Everything the container is allowed to do is visible in the command that asked for it. Nothing is configured, nothing is remembered, nothing is trusted.
Use level 1 for:
- a one-off, or a tool you use twice a year
- someone else's repository, before you have read it
- CI and deploy scripts — a pipeline should state its own capabilities
rather than depend on a trust decision somebody made interactively on a
laptop. This is not just style: without a terminal the trust prompt fails
closed, so a pipeline that leans on
.boksrcbreaks the first time it runs somewhere new.
The cost: the long commands are load-bearing. Forget -p and your server
is unreachable; forget -e and your imports fail. Nothing reminds you.
Level 2 — a project .boksrc
A single YAML file, committed at the project root, describing what this project needs:
tools:
node:
env_file: package.json
subcommand_index: 1
subcommands:
serve:
ports: ["3000"]
args: ["--host", "0.0.0.0"]
Now boks node server.js serve does everything the long command did. What
level 2 gives you:
- Default environment file (
env_file) — no more-ein this project. - Published ports, scoped to the one subcommand that serves.
- Spliced arguments — the
--host 0.0.0.0a dev server needs in a container, supplied by the project instead of by your memory. - Capabilities the project genuinely needs (
net,rw, …), additive to what the tool already has. - Environment variables —
set_env(authored here),pass_env(forwarded from your shell if present),dotenv(read host-side from a file). - dev/prod aliases —
aliasmints brand-new project-scoped names bundling all of the above:
python-dev:
alias: python
env_file: requirements-dev.txt
set_env: { LOG_LEVEL: debug }
python-prod:
alias: python
env_file: requirements.txt
set_env: { LOG_LEVEL: warning }boks python-dev app.py # dev deps, debug logging
boks python-prod app.py # prod deps, warnings only
boks python app.py # untouched, exactly what it always was
What a project can never do: there is no image or command field. A
project can ask for capabilities and mint new names; it can never redefine
what python or node means, and an alias whose name collides with a real
tool is inert — the real tool wins, unconditionally. Escalation-only, never
relaxation: a .boksrc cannot mute a capability or turn off a scan.
Trust on first use. A .boksrc arrives however the project does — clone,
pull, a pull request — so it is not trusted by default. The first run shows
everything it grants and asks:
▍ ■ SECURITY .boksrc declares a project overlay ▍ python: -e requirements.txt, pass_env EXAMPLE_TOKEN, +net (via requirements.txt) ▍ ■ INPUT Apply this project's overlay? ▍ [ y ] yes [ n ] no [ o ] once ▍ ←/→ move ⏎ pick or press y n o
y remembers that exact file content — a one-character edit sends it back
through review. o applies it once. Anything else declines, and the command
runs exactly as if the file weren't there.
Note the last item on that line. +net (via requirements.txt) is a capability
the overlay never mentions: trusting an env_file means trusting whatever
installing it grants, so the review shows the transitive grants too, tagged
with which source decided. See Project Config
for the full field reference.
Use level 2 for: any project you or your team work in regularly. The
.boksrc gets code-reviewed like any other file, and a new teammate gets the
right sandbox by cloning the repo.
Level 3 — shims
A shim is a symlink in ~/.local/bin, named after the tool, pointing at the
boks binary. boks reads argv[0], sees it was called as npm rather than
boks, and runs that tool:
boks -i npm # or: boks --install npm
boks -i node
boks -l # list what you've installed
Then the prefix disappears:
npm install
npm run dev
node server.js
That is upstream's own quickstart, unedited, with a containerised toolchain underneath it. This is the point of level 3: you can follow any project's README literally, and paste commands from Stack Overflow, and your muscle memory keeps working.
.boksrc stops being a convenience here and becomes the mechanism. In
shim mode every argv token belongs to the tool — there is nowhere to put a
-p, because node -p is node's own flag. Level 2 is what makes level 3
worth having; without it a shim can only ever run the capability defaults.
For genuine one-offs, boks flags move into the environment:
BOKS_ARGS="--cap net" node build.js
BOKS_ARGS="-e @pip:requirements-dev.txt" python -m pytest
BOKS_ARGS accepts flags only — a stray non-flag token would silently become
the tool name, so boks rejects it loudly.
What you are signing up for
Level 3 is the advanced mode. It is worth being blunt about the cost:
- A shim overwrites a same-named file in
~/.local/bin.boks -i pythonwill replace a pyenv shim or a pipx symlink that is already there. Check first;boks -lreports only symlinks pointing at the boks binary, so it cannot help you undo a replacement. - PATH order decides who wins. A
/usr/local/bin/nodeearlier inPATHbeats the shim entirely, and you silently get the host's Node. - You shadow the tool for everything, not just your typing. Editor
plugins, build scripts and other tools' subprocesses that resolve
pythonthroughPATHnow get a containerised Python with no network and no access outside the current directory. - You have to know it is boks. When an install fails with a permission error, the fix is a capability, not a flag to the tool. Level 3 removes the visual reminder that you are in a sandbox — that is exactly the point, and exactly the risk.
- No overlay means bare defaults. Run the same native-looking command in a
directory with no
.boksrc, or decline the trust prompt, and it quietly runs without the ports and capabilities you have come to expect.
To remove one: rm ~/.local/bin/<name>. There is no --uninstall.
A safer middle ground: shim the project-scoped alias names rather than
the real ones. boks -i python-dev gives you a native-feeling command that
only resolves inside a project whose .boksrc mints it, and fails loudly with
Unknown tool everywhere else — instead of silently shadowing your system
interpreter.
The sandbox does not change
Whatever level you work at, the container is the same. node server.js with a
shim and boks -p 3000 node server.js --host 0.0.0.0 without one produce the
same container, the same capabilities, the same read-only filesystem and the
same vulnerability scan. Levels change ergonomics and where knowledge is
stored. They do not change the security posture.
A shortcut that isn't a level: shebangs
For a single self-contained script, the invocation can live in the file:
#!/usr/bin/env -S boks -e @pip:requirements.txt pythonchmod +x script.py
./script.py
Anyone with boks can run it with no setup, no level 2, and no shim. It does
not scale to a project with several entry points — that is what .boksrc is
for — but for a script you hand someone, it is the smallest thing that works.
Pick a level
- Trying boks out, or running someone else's code → level 1.
- CI, deploys, anything unattended → level 1, explicitly.
- Your team's project → level 2, committed.
- Your daily machine, following upstream instructions verbatim → level 3, with the caveats above understood.
Then read the guide for your language.