Secrets & keys
Agents often need secrets inside the environment — API keys, tokens, a project
.env. Forge Fleet gives you three mechanisms, each suited to a different
shape of secret. Pick by what the secret is and how the code expects to read
it.
| Mechanism | Config key | Secret arrives as | Best for |
|---|---|---|---|
| Env file | docker_env_file |
Environment variables in the container | API keys/tokens the process reads from the environment (ANTHROPIC_API_KEY, LINEAR_API_KEY, DB URLs) |
| File snapshot | copy_files |
A copy of a file placed in the worktree | A project .env / local config file the app loads from disk |
| Bind mount | extra_mounts |
A live-mounted host path in the container | Directories you want shared as-is (e.g. a shared cache, ~/.agents) |
1. docker_env_file — inject environment variables
Point at an env file; its KEY=value lines are passed to the container via
docker run --env-file, so they're present as environment variables for every
process inside.
# A relative path resolves against this config file's own directory.
docker_env_file = "docker.env"
$ ffleet up SLUG --docker-env-file ./docker.env
# docker.env
ANTHROPIC_API_KEY=sk-ant-…
LINEAR_API_KEY=lin_api_…
DATABASE_URL=postgres://…
Use this for runtime secrets read from the environment. It's also the natural
home for LINEAR_API_KEY when using Linear templates, and the
companion to [claude].auth = "external", where you supply credentials yourself
instead of letting ffleet's preflight find them.
The env file itself lives on the host and is never committed into the repo — keep it out of version control.
2. copy_files — snapshot a file into the worktree
Some apps don't read the environment; they load a file from disk (a .env, a
config/local.json). copy_files snapshots gitignored files from your main
checkout into each new worktree, at the same relative path.
copy_files = [".env", "config/local.json"]
Key properties:
- One-time copy at creation, not a live link — later edits on either side don't sync.
- The agent works on the copy, so it can't clobber your real file.
- Entries are relative to the main checkout; globs are allowed.
Prefer this over a mount for env/local-config files. Inside the container the
file appears at the same relative path inside the workspace (e.g. .env at
the workspace root).
3. extra_mounts — bind-mount a host path
When you want the container to see a host directory or credential as-is, mount it. This is a live bind mount, not a copy.
extra_mounts = [
"~/.agents:/home/buddy/.agents",
"~/.cache/pip:/home/buddy/.cache/pip",
]
Format is source:target[:mode]: the target must be an absolute container path,
~ expands to the host home, and a relative source resolves against the shell's
current directory when ffleet up runs — so prefer absolute or ~-relative
sources. Use this for shared caches and tool directories — anything you want
shared and kept in sync, accepting that the container has live access to the
real path.
Don't mount
~/.ssh. Earlier versions of this guide suggested~/.ssh:/home/buddy/.sshas the way to give an agent push access. That mount hands the sandbox your real, long-lived private key: usable against every host and repository that key is authorized for, for as long as the environment exists — not scoped to one repo, one session, or one operation. Use the token recipe below instead.
Git push credentials
Agent containers get no SSH key. Instead, the container rewrites git remotes to
HTTPS in its own gitconfig — see
git_https_rewrite — so all it needs
is a token in the environment:
- Create a fine-grained personal access token scoped to the single repository, with only the permissions the agent needs (for GitHub: Contents: Read and write, plus Pull requests: Read and write if it opens PRs). Give it a short expiry.
- Put it in the env file you already pass with
docker_env_file:
# docker.env
GITHUB_TOKEN=github_pat_…
GH_TOKEN=github_pat_…
gh picks up GH_TOKEN/GITHUB_TOKEN directly, and gh auth setup-git (run
inside the container) makes plain git push use it too.
Unlike a mounted key, this credential is limited to one repository, carries only the permissions you granted, and expires. Revoking it is a single click and does not touch anything else you own.
Choosing
- The value is a credential the process reads from the environment → env file.
- It's a file the app loads from disk, and the agent shouldn't touch your
original →
copy_files. - It's a directory you want shared live, edits included →
extra_mounts. - It's push access to your repo → a repo-scoped fine-grained token in the env file, never a mounted SSH key.
Related
- Container configuration — how credential dirs
(
~/.claude,~/.codex) authenticate the agent. - Config reference — the exact key semantics.