CLI tools (rg, jq, git, curl)
Everything else in this section is about projects. This page is about the tools you reach for a hundred times a day — and it is where shims make the most sense with the least risk.
Read the three levels first if you haven't.
Why these are the best shim candidates
The level-3 caveats that matter for python or node — shadowing a version
manager, breaking editor integrations, silently changing what a build script
resolves — mostly don't apply here. rg is rg. Nothing else in your system
depends on which rg it gets, and the containerised one has fewer powers
than the host binary it replaces.
| Tool | Default capabilities | What that means |
|---|---|---|
rg | none | Reads the working directory. Cannot write. Cannot reach the network. |
jq | none | Same — a pure filter. |
curl, wget | none | No network by default. Opt in per invocation. |
git | net, rw | Needs both: clone/fetch/push talk to a remote, commit writes. |
A tool with no capability line at all is the strongest default boks has: the working directory is mounted read-only and there is no network namespace to speak of.
Level 1
boks rg "TODO" .
boks rg -l "FIXME" src/
echo '{"name":"boks","ok":true}' | boks jq -r '.name'
boks jq -r '.[] | .name' data.json
boks --cap net curl -s https://api.github.com/zen
boks --cap net curl -s https://httpbin.org/ip | boks jq '.origin'
boks git log --oneline -5
boks git diff HEAD~1
Pipes work the way you expect — boks is a normal process in the pipeline, with stdin and stdout connected through.
Note the two-container pipe in that curl | jq line. curl gets network;
jq does not, and does not need it. Each stage of the pipeline carries only
the capability its own stage requires, which is not something a single shell
with ambient permissions can express at all.
Level 3 (there is barely a level 2 here)
Ad-hoc tools rarely need project configuration — there is no dependency file and no dev server. So they tend to jump from level 1 straight to level 3:
boks -i rg
boks -i jq
boks -i git$ rg -c "serve" main.go
7
That is a containerised ripgrep, mounted read-only, no network, invoked as if it were installed.
Exit codes are preserved
This matters more for CLI tools than anything else, because they get used in scripts and shell conditionals:
$ rg "definitely-not-in-this-file" main.go; echo $?
1
A shim reproduces the tool's own exit code exactly, so git diff --exit-code
in a pre-commit hook and if rg -q pattern; then in a script both behave the
way they always did.
When you do want a project overlay
The one common case is granting a tool something extra inside one project:
tools:
curl:
capabilities: [net]
rg:
capabilities: [rw] # e.g. for `rg --files-with-matches | xargs sed -i`
Now curl inside that project needs no flag, and curl everywhere else still
has no network. That is a genuinely better default than adding net to your
own machine-wide boks.toml, because it is scoped, committed and reviewed.
BOKS_ARGS for one-offs
BOKS_ARGS="--cap net" curl -s https://example.com
BOKS_ARGS="--cap rw" rg --passthru -r 'new' 'old' file.txtgit deserves its own paragraph
git is the one tool here where the sandbox is doing something you should
think about.
It ships with net and rw because it cannot do its job without them — which
means a shimmed git is closer to your host git in power than a shimmed
rg is.
Two things are handled for you:
-
~/.gitconfigis mounted automatically, read-only, when git is actually present in the container — so your identity and aliases come along:$ boks git config --get user.email you@example.com # the host's valueControlled by
mount_git_configin yourboks.toml, on by default. The mount is gated on git being present, so composing an unrelated tool doesn't drag a.gitconfiginto a container that has no git in it. -
SSH transport works without hand-composing. git's image ships no
sshbinary — single-purpose images — but its index entry declaresdefault_compose = ["ssh"], soboks git clone git@host:…finds one on PATH.
And one thing is not, on macOS:
- SSH agent forwarding is Linux-host only. The code that bind-mounts
$SSH_AUTH_SOCKinto the container is compiled under#[cfg(target_os = "linux")], so on macOS the variable simply isn't set inside the container even withsshcomposed — verified. Pushing over SSH from a macOS host therefore won't reach your agent; HTTPS with a token forwarded throughpass_envis the working path there.
What git still does not get is access to anything outside the current directory, so a worktree or submodule pointing elsewhere won't be visible.
For a repository you are reading rather than pushing to, none of this
matters, and boks git log/boks git diff are exactly as good as the host's.
The genuinely nice property
Running an unfamiliar tool on an unfamiliar directory is the everyday case where a sandbox pays off with no ceremony at all:
boks rg "password" . # can read this directory, and nothing else
boks jq . suspicious.json # cannot write, cannot phone home
No virtualenv, no --dry-run, no reading the tool's source first. The tool
gets the directory you pointed it at and nothing else.
Worked examples
tool-rg— ripgrep under boks.bash-script— a shell script through boks.