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 with MYAPP_
  • MYAPP_PROFILE — exact match only
  • * — pass all host variables (strongly discouraged: this hands every tool your credentials, and it also lets a host PATH or PYTHONPATH override 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

VariableDescription
BOKS_ARGSBoks flags applied to the invocation (see below)
BOKS_CONFIGOverride the path to boks.toml
CIWhen 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_SOCK path 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_agent is 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