Environment Variables
Passing host variables into containers
boks forwards a small, curated set of host variables automatically — the control knobs that are safe to hand any tool because their values are levels, booleans and locales rather than credentials:
- Terminal and colour:
TERM,COLORTERM,NO_COLOR,CLICOLOR,CLICOLOR_FORCE,FORCE_COLOR - Locale and time:
LANG,LC_ALL,LC_*,TZ - CI and reproducible builds:
CI,SOURCE_DATE_EPOCH - Rust:
RUST_LOG,RUST_LOG_STYLE,RUST_BACKTRACE,RUST_LIB_BACKTRACE - Python:
PYTHONUNBUFFERED,PYTHONDONTWRITEBYTECODE,PYTHONFAULTHANDLER,PYTHONWARNINGS,PYTHONIOENCODING,PYTHONDEVMODE,PYTHONHASHSEED - Node:
NODE_ENV,NODE_DEBUG - Generic:
DEBUG
So RUST_LOG=debug boks <tool> works with no configuration.
Everything else is opt-in, deliberately. boks does not forward the rest of your
shell because a container that sees your whole environment sees your
credentials, and a deny-list cannot be completed — secret-bearing names have no
shared shape (DATABASE_URL, SENTRY_DSN, KUBECONFIG all look innocent).
Some exclusions are load-bearing rather than cautious: PYTHONSTARTUP,
PYTHONPATH, NODE_OPTIONS, BASH_ENV and LD_PRELOAD each name a host path
that gets loaded as code, and HTTP_PROXY/HTTPS_PROXY/REQUESTS_CA_BUNDLE
redirect egress or a trust anchor.
One variable, one run
For the long tail — a variable your own script reads — name it on the command line:
boks -E MY_VAR python script.py # forward the host's value
boks -E MY_VAR=hello python script.py # set a literal
Repeatable. An unset variable warns rather than forwarding an empty value.
Naming a variable here is an explicit decision, so it overrides
protected_env_vars (and says so).
Standing configuration
For something you want every run, add glob patterns in boks.toml. These are
additive to the built-in list above — you never need to restate RUST_LOG
and friends:
[global]
pass_env = [
"MYAPP_PROFILE", # exact match
"MYAPP_*", # or a prefix
]
Pattern rules:
MYAPP_*— matches any variable starting withMYAPP_MYAPP_PROFILE— exact match only*— pass all host variables (strongly discouraged: this hands every tool your credentials, and it also lets a hostPATHorPYTHONPATHoverride the ones boks sets up for composed tools)
Patterns can also be set per-tool, in addition to [global] pass_env:
[tools.python]
image = "boks.sh/python:3.12"
pass_env = ["MY_PROJECT_TOKEN"].env files
Project .env files hold local secrets and are denied to every tool by default — the working-directory mount would otherwise expose them as raw bytes. A tool that needs the values opts in via dotenv, which reads the file host-side and injects its variables into the container (the file itself stays denied):
[tools.python]
image = "boks.sh/python:3.12"
dotenv = [".env", ".env.local"]
The curated index already declares sensible dotenv defaults for runtimes (node layers .env + .env.local; python/ruby/php get .env), so boks python from a project with a .env just works. Variables already passed through via pass_env are not overridden (real-env-wins). See Tool mappings → Injecting .env files.
Protected environment variables
Some variable names are well-known secret carriers — AWS_SECRET_ACCESS_KEY,
GITHUB_TOKEN, NPM_TOKEN, and a few others (see
protected_env_vars). A broad glob
like pass_env = ["AWS_*"] or pass_env = ["*"] will not pass these
through, even though the pattern matches — they're on a deny list, the
env-var analog of
protected_dotfiles.
Full details: Security Layers → Protected environment
variables.
A tool that genuinely needs one opts in via its own pass_env, the same way
a dotfile opt-in works:
[tools.aws-cli]
image = "..."
pass_env = ["AWS_SECRET_ACCESS_KEY"] # this tool's own list grants it
Note that naming the protected variable in [global] pass_env does not
grant it — only a tool's own pass_env entry can. Set
protected_env_vars = [] to disable the deny list entirely, or list
additional names to extend it.
boks-specific variables
| Variable | Description |
|---|---|
BOKS_ARGS | Boks flags applied to the invocation (see below) |
BOKS_CONFIG | Override the path to boks.toml |
CI | When set to any value, disables TTY allocation (same as --non-interactive) |
BOKS_ARGS: flags via the environment
BOKS_ARGS holds boks flags in shell syntax, applied before the command line
is parsed. It exists for installed shims, where every argument belongs to
the tool and there is no other way to pass a boks flag:
# az is a shim (~/.local/bin/az → boks); az's image has a cached scan failure
BOKS_ARGS="--skip-scan" az account list
# quoted values work; multiple flags too
BOKS_ARGS="--cap net --port '8080:80'" myserver
boks <tool> invocations honor it as well, so one recipe works everywhere.
Precedence: an explicit command-line flag always wins over its BOKS_ARGS
counterpart. Only flags are allowed — a tool name inside BOKS_ARGS is
rejected with an error rather than silently changing what runs.
SSH agent forwarding
Set forward_ssh_agent = true in [global] (the default) to mount SSH_AUTH_SOCK into the container. This lets tools like git authenticate over SSH without exposing your private key.
[global]
forward_ssh_agent = true
Linux only. On macOS, Podman runs inside a Linux VM: even though the host's
SSH_AUTH_SOCKpath may be visible through the VM's shared filesystem, a bind-mounted Unix domain socket does not carry a live connection across that VM boundary — only a native Linux host shares a kernel with the container.forward_ssh_agentis a no-op on macOS today; an SSH-dependent command (git clone git@host:...,ssh-add -l) fails cleanly with no agent reachable rather than hanging or silently using the wrong credentials.
Git configuration
Set mount_git_config = true (the default) to mount ~/.gitconfig read-only into the container when git is actually present — i.e. when git is the primary tool, an inline-package install that pulls git in, or a composed tool (boks -e git ...). Tools like git will then use your identity and settings. The file is no longer mounted into every container unconditionally, so a run with no git involved (e.g. boks -e emacs bash) doesn't surface a stray .gitconfig. Set mount_git_config = false to suppress the mount entirely, even when git is present.
[global]
mount_git_config = true