Config reference (ffleet.toml)
Every ffleet.toml setting. This file is a per-project, personal dev-tool
preference — not team config. ffleet init writes it by default under
~/.forge-fleet/{project-id}/ffleet.toml ("home mode"), so nothing needs to be
committed into the repo it configures. In-repo ("local mode") is supported as an
explicit opt-in.
Most CLI flags mirror a key here; where they overlap the flag wins for that run
(see config precedence). The canonical,
fully-commented template is ffleet.toml.example at
the repo root.
What init sets, and everything else
ffleet init runs a short wizard for the settings most worth choosing up front —
the default coding agent, the base image (python or go variant),
the git mode, and whether sandboxes get host Docker access (DooD). Each
question is pre-selected at its default, so pressing enter through them keeps
today's behavior; your choices are written as active keys. Every other
setting is written commented-out at its default, so the generated file
doubles as a menu — uncomment and edit what you need.
init -y accepts every default without prompting. Non-default values for any
other setting are configured by editing the generated ffleet.toml.
init is create-only: it never overwrites an existing config. To change
settings later, edit the ffleet.toml directly — init prints its path when
it writes it, and re-running init on a configured project just points you back
at that file.
Global keys
| Key | Type | Default | Meaning |
|---|---|---|---|
project_name |
string | — (required) | Logical project name; used to name containers and worktrees. |
source_dir |
string | — | Absolute path to the source repo. Required in home mode; in local mode it's derived from where this file lives (set it only if the file isn't colocated with the repo). |
worktree_root |
string | — (required) | Where per-environment worktrees are created (absolute, or relative to the repo root). Inside the checkout (e.g. .forge-fleet/worktrees) or a separate folder under $HOME. |
image |
string | ghcr.io/grzegorz-aniol/forge-fleet-python:latest |
Docker image the agent container runs. Use ghcr.io/grzegorz-aniol/forge-fleet-go:latest for the Go-dev variant. |
git_mode |
string | auto |
Git integration: auto | off | worktree | clone. off needs no git repo — ffleet up then runs in-place over the project root (no worktree, no --here). clone is reserved and not implemented yet. See git options. |
git_local_main |
bool | false |
Base the worktree on the local main branch and keep completion local-only (no push/PR). See git options. |
attach |
bool | true |
Whether ffleet up attaches to the agent session after starting it. Set false for a project you normally drive in the background; --attach / --no-attach override it per run, and ffleet up -y never attaches unless --attach is given. |
docker_host_bind |
bool | false |
Let the agent inside the container drive the host Docker daemon (binds the host docker socket; agent-started containers become siblings). See DooD. |
git_allow_origin_writes |
bool | true |
When false, deny the agent the outward-facing commands (git push, gh pr create, gh pr merge, gh release create, gh workflow run, and gh api with a mutating method right after api). Sticky per environment. See denying remote writes. |
git_https_rewrite |
bool | true |
Rewrite git remotes to HTTPS inside the container (url."https://<host>/".insteadOf "git@<host>:"), so the agent needs no SSH key. Written into the container's own gitconfig only — never the repository's. See HTTPS remotes in the container. |
uv_cache_dir |
string | ~/.cache/uv |
Shared uv cache mounted into the container for faster installs. |
extra_mounts |
list | — | Extra bind mounts as source:target[:ro\|:rw]. Target must be an absolute container path; a relative source resolves against the repo root; ~ expands to the host home. An optional :ro/:rw suffix sets the mode (default read-write). |
extra_hosts |
list | — (may be omitted) | Host-to-address mappings passed to docker run --add-host, each host:address. host-gateway resolves to the host itself. |
docker_env_file |
string | — | Additional env file passed to docker run --env-file (runtime secrets/keys). A relative path resolves against this config file's own directory. |
copy_files |
list | — | Gitignored files to snapshot from the main repo into each new worktree (globs allowed), so e.g. .env appears at the same relative path inside. One-time copy at creation, not a live link. |
default |
string | — | Which coding-agent ffleet up runs when none is named on the CLI. Must be one of the [section] names below (e.g. claude, codex). |
pm |
string | github |
Issue tracker for -t templates: github (via gh CLI) | linear (needs LINEAR_API_KEY) | none (don't fetch; raw ref exposed as {id}). On any fetch failure ffleet warns and continues as pm=none. Also gates the PR column in ls and the remove --done sweep: only github consults the forge; linear/none leave the column blank and never invoke gh. |
check_image_period_days |
int or "off" |
30 |
How often ffleet up checks whether a newer image exists in the registry (see image freshness check). Any positive integer sets the period in days; "off" disables the check entirely. |
Image freshness check (check_image_period_days)
Whenever ffleet up actually builds or (re)starts a container, and at least
check_image_period_days have elapsed since the last check for this project, it
does a best-effort comparison of the configured image against its remote
counterpart. It compares two registry manifests: the one the tag resolves to
right now, and the one behind the digest the local copy was pulled under. They
differ only when the tag has actually moved, so the answer doesn't depend on
which image store (classic or containerd) your Docker daemon uses:
- Interactive session — if a newer image is found, ffleet prompts before pulling and pulls only on explicit approval, then starts the container from the freshly pulled image. The pull reports Docker's own outcome, so you can see whether anything was downloaded or the local copy was already current.
ffleet up -y/--yes— warn-only: ffleet prints a notice that a newer image exists but never prompts and never pulls. Pull it yourself withdocker pull <image>(or runffleet upinteractively).
The check never blocks or fails ffleet up: if the registry is unreachable, or
the image was built locally and so has no registry digest to compare, ffleet
silently skips the comparison and starts the container as usual. The last-check
timestamp is stored per project in ~/.forge-fleet/registry.json (not in
ffleet.toml), so a project that was never checked is treated as due on its
next up. Set
check_image_period_days = "off" to disable the whole flow.
[git] — identity injected into the container
Both keys must be set together — setting exactly one is an error. With neither, identity resolves from the environment's recorded value or your host git config; see commit identity.
| Key | Type | Meaning |
|---|---|---|
user_name |
string | Git author/committer name inside the container. |
user_email |
string | Git author/committer email inside the container. |
[claude] / [codex] — per-coding-agent config
| Key | Type | Default | Meaning |
|---|---|---|---|
dir |
string | ~/.claude / ~/.codex |
Host directory mounted as the agent's config/credentials, so it's authenticated inside the container. |
auth |
string | auto |
(Claude) Credential source ffleet injects. See the table below. |
cmd |
string | the section name | Override the binary invoked in the container (e.g. claude). |
model |
string | the agent's own default | Model the agent launches with, passed as --model <value>. Opaque — ffleet hands it to the binary unchecked. |
reasoning |
string | the agent's own default | Reasoning effort. On Claude, passed as --effort <value> (low, medium, high, xhigh, max). On Codex, which has no flag of its own for it, injected as -c model_reasoning_effort=<value>. |
extra_args |
list | — | Your own arguments for the agent, placed immediately after the binary (after model / reasoning when set) and before ffleet's own flags (e.g. ["--agent", "myagent"]). See controlling the coding assistant. |
extend_system_prompt |
bool | true |
(Claude) Let ffleet append short, factual notes about the environment to the agent's system prompt. See below. |
git_allow_origin_writes
Agents run inside the container with their own approval prompts disabled, so
once the container has push credentials nothing but the agent's judgement stops
it from publishing work you have not reviewed. Setting git_allow_origin_writes =
false mounts two small, ffleet-generated policy files read-only into the
container — /etc/claude-code/managed-settings.json and
/etc/codex/requirements.toml — which deny these command prefixes at the
agent's own permission layer:
git push · gh pr create · gh pr merge · gh release create ·
gh workflow run · gh api with a mutating method (POST, PATCH, PUT,
DELETE)
Each method is denied in the five spellings gh accepts immediately after
gh api: --method POST, --method=POST, -X POST, -X=POST and -XPOST.
The agent can still commit locally; it just cannot publish. With
extend_system_prompt on, Claude is also told
why the commands fail, so a refusal reads as a known constraint rather than a
mystery.
Caveats worth knowing:
- Both agents match by command prefix, so a command wrapped in another shell
(
sh -c "git push") is not caught. This layer raises the bar; it is not a sandbox. - A
gh apimethod flag placed after the resource path is not caught either —gh api repos/o/r/issues --method POSTslips through, for the same prefix-matching reason. Nor are lowercase method tokens (--method post). - It is independent of
git_local_main— neither implies the other. They pair naturally (a local-only base branch plus no way to publish), so set both if that is what you want.
When the key is absent or true (the default), no remote-write deny rules are
installed. Codex's read-only /etc/codex/requirements.toml is always mounted to
pin its SQLite databases to environment storage; setting this key to false
adds deny rules to that same file and also mounts Claude's deny policy. Setting
it back to true removes only the deny rules, preserving Codex SQLite isolation.
See Codex storage and image compatibility.
The setting is sticky per environment. Once an environment has been created
or started with a value, a later start with the key absent from your config keeps
the persisted value rather than resetting it to the default — dropping
git_allow_origin_writes = false from ffleet.toml does not quietly re-open
remote writes. To re-enable them for an existing environment, set
git_allow_origin_writes = true explicitly; an explicit value always wins.
git_https_rewrite
Agent containers get no SSH key. To keep git fetch/git push working for a
checkout whose origin is an SSH URL, the container's init script writes a
rewrite into its own /home/buddy/.gitconfig at every start:
[url "https://github.com/"]
insteadOf = git@github.com:
insteadOf = ssh://git@github.com/
The host is derived from the checkout's origin URL, so a GitLab or Bitbucket
remote gets its own host. The repository's git config is never touched — it is
shared with the host and every other environment, so the container's transport
choice must not leak into it.
The rewrite only changes how the container reaches the remote; it mints no
credential. Supply one yourself as a repo-scoped fine-grained token through
docker_env_file — see
secrets.
Set git_https_rewrite = false to turn the rewrite off (for example when you
deliberately mount your own SSH agent socket). Unlike
git_allow_origin_writes it is not persisted per
environment: every start re-reads it from your config.
[claude].extend_system_prompt
With this on (the default), ffleet tells Claude a few facts about the
environment it is running in, so it doesn't have to guess. Today there are two:
when docker_host_bind is enabled, Claude is told that Docker
runs in docker-outside-of-docker mode and that containers it starts are siblings
of its own container — see Docker outside Docker;
and when git_allow_origin_writes is false, Claude is told
which outward-facing commands are denied and why. Nothing is added when both are
at their defaults, and the key has no effect on Codex.
The notes are merged with any --append-system-prompt you set in extra_args,
never replacing it: both end up in a single flag, with ffleet's text first and
yours last. They are recomputed at every launch and are not stored in the
environment's session.json. Set extend_system_prompt = false to opt out
entirely — the launch command is then exactly what you configured.
[claude].auth values
| Value | Behaviour |
|---|---|
auto |
Try keychain, then token, then api-key, then credentials-file; first available wins (default). |
keychain |
macOS only; pin the Keychain-derived credential (fails fast if the lookup fails). |
token |
Pin CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token). |
api-key |
Pin ANTHROPIC_API_KEY (API billing, not subscription). |
credentials-file |
Pin the ~/.claude mount's .credentials.json (Linux only). |
external |
Skip the auth preflight entirely — you provide credentials another way (e.g. docker_env_file). |
See container configuration for how these mounts and credentials fit together.
[templates.<id>] — prompt templates
Looked up by ffleet up -t <id> <ref>: ffleet fetches issue <ref> from the
configured pm source, derives the slug from the issue title, and renders
prompt_template with the issue's fields.
| Key | Type | Meaning |
|---|---|---|
prompt_template |
string | Template rendered with {number}, {title}, {body}, {url}, {id} (raw ref), plus any extra key=value tokens passed on the CLI. |
subagent |
string | Optional subagent for envs seeded from this template. On Claude, translated to a deterministic --agent <name> flag. On Codex, a best-effort delegation instruction woven into the launch prompt (or sent into the pane on an interactive launch) — Codex has no equivalent CLI flag, so this is not guaranteed; see starting. Using it with any other coding-agent errors. The up --subagent NAME flag is the per-invocation, template-independent equivalent and overrides this key when both are set. |
Full walkthrough: templates.md.
Environment variables and dotenv
Most ffleet.toml keys have an environment-variable equivalent, and Forge Fleet
also loads a dotenv file from the current working directory if present. An env var
(or dotenv entry) overrides the matching ffleet.toml key; a CLI flag overrides
both (see config precedence).
Dotenv discovery order in the current working directory (only the first existing file is loaded):
.env.forge-fleet.forge-fleet/.env
Env var mapping:
Identity (project_name, source_dir, worktree_root) is config-only: it is
read straight from ffleet.toml and has no env-var or CLI-flag override. Run
ffleet init to create the config; without it, the env commands fail with a
no ffleet config found — run 'ffleet init' error.
| Env var | Maps to |
|---|---|
SLUG |
slug positional argument |
IMAGE_NAME |
--image |
CODING_AGENT |
--coding-agent (on up; set only when first creating an env). The legacy AGENT / --agent names still work, each with a one-time deprecation notice. |
CLAUDE_DIR |
--claude-dir |
CLAUDE_AUTH |
--claude-auth (auto, keychain, token, api-key, credentials-file, external) |
CODEX_DIR |
--codex-dir |
UV_CACHE_DIR |
--uv-cache-dir |
EXTRA_MOUNTS |
--extra-mounts (source:target,source:target) |
EXTRA_HOSTS |
--extra-hosts (host:address,host:address; each becomes a docker run --add-host, e.g. host.docker.internal:host-gateway) |
DOCKER_ENV_FILE |
--docker-env-file |
GIT_MODE |
--git-mode (auto, off, worktree, clone) |
GIT_LOCAL_MAIN |
local-branch mode when first creating an env (true/false) |
DOCKER_HOST_BIND |
--docker-host-bind (true/false) |
GIT_ALLOW_ORIGIN_WRITES |
deny the agent outward-facing git/gh commands when false (true/false); no CLI flag equivalent |
GIT_HTTPS_REWRITE |
rewrite git remotes to HTTPS inside the container (true/false); no CLI flag equivalent |
ATTACH |
ffleet up's attach default (true/false); --attach / --no-attach override it, and -y forces no-attach unless --attach is given |
FORGE_FLEET_GIT_USER_NAME |
explicit git identity name override |
FORGE_FLEET_GIT_USER_EMAIL |
explicit git identity email override |
LINEAR_API_KEY |
Linear personal API key, read directly when pm = "linear"; no CLI flag equivalent |
A leading ~ in CLAUDE_DIR, CODEX_DIR, UV_CACHE_DIR and DOCKER_ENV_FILE
is expanded to your home directory at startup — these vars are exported to every
process Forge Fleet starts, including the shell ffleet goto drops you into, so
they must hold real paths.
Example .env.forge-fleet:
IMAGE_NAME=forge-fleet:latest
CODING_AGENT=claude
CLAUDE_DIR=~/.claude
CODEX_DIR=~/.codex
UV_CACHE_DIR=~/.cache/uv
EXTRA_MOUNTS=~/.agents:/home/buddy/.agents
DOCKER_ENV_FILE=/path/to/docker.env
GIT_MODE=auto
DOCKER_HOST_BIND=false
FORGE_FLEET_GIT_USER_NAME=Jane Developer
FORGE_FLEET_GIT_USER_EMAIL=jane@example.com
# Only needed when pm = "linear":
LINEAR_API_KEY=lin_api_xxxxxxxx
EXTRA_MOUNTS format rules:
- comma-separated entries:
<source>:<target>[:ro|:rw] sourcesupports~expansion- relative
sourceis resolved from the current working directory targetmust be an absolute container path (start with/)- an optional trailing
:roor:rwsets the mount mode (default is read-write); any other trailing segment is rejected - entries are bind-mounted as
-v <source>:<target>(read-write) or-v <source>:<target>:ro(read-only) - if an extra mount uses the same target as an existing mount, the later extra mount can shadow the earlier one
Config discovery order
For a given directory, Forge Fleet locates the ffleet.toml to use in this order:
- an explicit config path (
FFLEET_CONFIGenv var) - an explicit project config dir (
FFLEET_PROJECT_DIRenv var) - the
~/.forge-fleet/registry.jsonlookup by the directory's project id - legacy in-repo search:
ffleet.toml, then.forge-fleet/ffleet.toml
The project id is derived from git structure — the main checkout's
git rev-parse --git-common-dir plus its origin remote, or a path hash when there
is no remote — never from the current directory's name. That's why every worktree of
a project resolves to the same config.
Related
- Full command/flag list: command reference.
- Why some keys only apply after
remove: config precedence.