Secrets
Passwords and keys that the committed file must not hold.
bothy.yaml is committed, so a password cannot go in it. bothy reads such values from
a second file that you do not commit. The default is .env beside bothy.yaml, and
bothy init adds it to .gitignore.
# .env
STRIPE_KEY=sk_test_123
PGPASS=hunter2
A worktree with a .env and nothing else declared gets every value in your shell and
in every service. No other declaration is necessary.
An edit to .env takes effect at your next shell prompt. bothy restarts each service
that reads a value you changed.
The file
Write one KEY=value for each line. bothy accepts these forms:
PLAIN=value
QUOTED='single quotes keep every character'
ESCAPED="double quotes take \n, \t, \\ and \""
export PREFIXED=value
EMPTY=
COMMENTED=value # a comment starts at a space and a #
# a full-line comment
The values are literal. bothy expands no variable and runs no command. To build a value
from a secret, read the secret in env. See configuration.
If a line is not one of these forms, bothy refuses to enter and gives the line number.
If a key starts with BOTHY_, bothy ignores it and gives a warning.
Another file
The secrets block is optional. Both of its fields have a default:
secrets:
source: .env
mode: implicit
source- the file to read. A relative path starts at the root of the worktree. An absolute path and a~/path also work, for a file that you keep outside the repository.mode-implicitorexplicit. See below.
Services that must not share credentials
In the default implicit mode, every service gets every value. In explicit mode, a
service gets only the secrets that its own secrets field names:
secrets:
mode: explicit
services:
web:
type: command
command: "npm run dev"
port_env: PORT
secrets: [STRIPE_KEY]
worker:
type: command
command: "npm run worker"
secrets:
DB_PASSWORD: PGPASS
web gets STRIPE_KEY. worker gets DB_PASSWORD, which holds the value of
PGPASS. web cannot read PGPASS, and worker cannot read STRIPE_KEY.
If a secret keeps its own name, write a list. If the service wants another name, write
a map. A service can also declare secrets in implicit mode, to give a value a
second name.
Your shell gets every value in both modes. The mode decides what the services get.
If a service names a secret that the file does not hold, bothy warns at every entry
and starts the service anyway. The variable is not set, so the service fails for a
reason that says so. A variable cannot be in both env and secrets for one service.
What bothy shows
bothy explain gives the name of each secret and never its value:
secrets:
source: /home/you/src/app/.env
mode: implicit -- every service gets every value
holds: PGPASS, STRIPE_KEY
environment -- your shell and every service:
STRIPE_KEY = <set, 12 characters>
^ the secret source, in implicit mode
Precedence
When two sources set the same name, the later one in this list wins:
- The secret source, in implicit mode.
- The variables of a service that this one depends on.
- The top-level
env. - The
envof the service. - The
secretsof the service. - The bothy variables, such as
BOTHY_<NAME>_PORT. - The variables that a built-in type sets for its own process. For example, the
miniotype setsROOT_ACCESS_KEYandROOT_SECRET_KEY, and theqdranttype setsQDRANT__SERVICE__HTTP_PORT. These tell the server which port and data directory to use, so nothing overrides them.
What to expect of the values
- bothy reads the secret source. bothy never writes it.
- bothy keeps no copy of the values on disk. In a Claude Code session, the hook writes
them to the file that
CLAUDE_ENV_FILEnames, with mode 0600. - If a service declares a
health_check, bothy records the environment of that service inregistry.json, also with mode 0600. - A secret is in the environment of the service. Any process of the same user can read
it from
/proc/<pid>/environ. - bothy does not encrypt the source and does not integrate with a secret manager.