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 1Level 2Level 3
Installedboksboksboks + shims
In the projectnothing.boksrc.boksrc
What you typeboks -e @npm:package.json -p 3000 node server.jsboks node server.jsnode server.js
Where the knowledge livesin your fingersin a reviewed, committed filein a reviewed, committed file
Feels likea sandbox you are drivingyour project, one word longeryour 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 .boksrc breaks 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 -e in this project.
  • Published ports, scoped to the one subcommand that serves.
  • Spliced arguments — the --host 0.0.0.0 a 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 variablesset_env (authored here), pass_env (forwarded from your shell if present), dotenv (read host-side from a file).
  • dev/prod aliasesalias mints 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 python will replace a pyenv shim or a pipx symlink that is already there. Check first; boks -l reports only symlinks pointing at the boks binary, so it cannot help you undo a replacement.
  • PATH order decides who wins. A /usr/local/bin/node earlier in PATH beats 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 python through PATH now 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 python
chmod +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.

Pages in this section