Skip to content

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 with docker pull <image> (or run ffleet up interactively).

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 api method flag placed after the resource path is not caught either — gh api repos/o/r/issues --method POST slips 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):

  1. .env.forge-fleet
  2. .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]
  • source supports ~ expansion
  • relative source is resolved from the current working directory
  • target must be an absolute container path (start with /)
  • an optional trailing :ro or :rw sets 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:

  1. an explicit config path (FFLEET_CONFIG env var)
  2. an explicit project config dir (FFLEET_PROJECT_DIR env var)
  3. the ~/.forge-fleet/registry.json lookup by the directory's project id
  4. 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.