578 lines
15 KiB
Markdown
578 lines
15 KiB
Markdown
# Configuration Reference
|
|
|
|
Sandcage uses a layered configuration system. Each layer overrides the previous one:
|
|
|
|
1. **Compiled defaults** — all fields unset
|
|
2. **Global config** (`~/.sandcage/config.toml`) — user-wide preferences (TOML format)
|
|
3. **Project config** (`.sandcage/config.toml`) — per-project settings, checked into version control (TOML format)
|
|
4. **Local config** (`.sandcage/local/config.local.toml`) — personal overrides, gitignored via `.sandcage/.gitignore` (TOML format)
|
|
|
|
Later layers win. Fields not set in a layer are inherited from the layer below.
|
|
|
|
---
|
|
|
|
## Directory Layout
|
|
|
|
```
|
|
.sandcage/
|
|
├── config.toml # project config (committed)
|
|
├── overlay/ # files bind-mounted read-only over workspace files in the container
|
|
├── local/ # gitignored via .sandcage/.gitignore
|
|
│ ├── config.local.toml
|
|
│ └── overlay/ # local overlay files (also gitignored)
|
|
└── .gitignore # contains "local/"
|
|
```
|
|
|
|
The `.sandcage/` directory itself is invisible inside the container (shadowed by a tmpfs mount).
|
|
|
|
---
|
|
|
|
## Config Source Resolution
|
|
|
|
When sandcage looks for the `.sandcage/` directory, it checks these locations in order:
|
|
|
|
1. **`SANDCAGE_CONFIG_DIR` env var** — explicit override, used as-is
|
|
2. **`.sandcage/` in the workspace** — the most common case
|
|
3. **Redirect file** — a `sandcage.toml` at the workspace root containing a `redirect` key pointing to the real `.sandcage/` directory
|
|
4. **Main worktree root** — for git worktrees, falls back to the main worktree's `.sandcage/`
|
|
|
|
The first match wins. `sandcage init` refuses to run from linked worktrees — run it from the main worktree instead.
|
|
|
|
---
|
|
|
|
## Fields
|
|
|
|
### env
|
|
|
|
Environment variables passed into the container at runtime.
|
|
|
|
- **Type:** Map of string keys to string values
|
|
- **Valid in:** Global, Project, Local
|
|
- **Behavior:** Passed as `-e KEY=VALUE` flags to `docker compose run`
|
|
|
|
```toml
|
|
# .sandcage/config.toml
|
|
[env]
|
|
RUST_LOG = "debug"
|
|
DATABASE_URL = "postgres://localhost:5432/dev"
|
|
```
|
|
|
|
```toml
|
|
# ~/.sandcage/config.toml
|
|
[env]
|
|
EDITOR = "vim"
|
|
```
|
|
|
|
---
|
|
|
|
### packages
|
|
|
|
System packages to install in the container image (apt packages).
|
|
|
|
- **Type:** List of strings
|
|
- **Valid in:** Global, Project, Local
|
|
|
|
```toml
|
|
packages = ["ripgrep", "fd-find", "postgresql-client"]
|
|
```
|
|
|
|
---
|
|
|
|
### toolchains
|
|
|
|
Language toolchain managers to enable. When enabled, the corresponding Dockerfile fragment from `images/fragments/` is included during `sandcage build`.
|
|
|
|
- **Type:** Map of string keys to boolean values
|
|
- **Valid in:** Global, Project, Local
|
|
- **Supported keys:** `uv`, `nvm`, `rust`
|
|
|
|
```toml
|
|
[toolchains]
|
|
uv = true
|
|
nvm = true
|
|
rust = true
|
|
```
|
|
|
|
---
|
|
|
|
### mounts
|
|
|
|
Additional volume mounts passed to the container. Each entry must contain a `:` separator.
|
|
|
|
- **Type:** List of strings in Docker mount format (`<source>:<target>[:<options>]`)
|
|
- **Valid in:** Global, Project, Local
|
|
- **Behavior:** Tilde (`~`) in the source path is expanded to the host home directory. Entries without `:` are skipped with a warning.
|
|
|
|
```toml
|
|
mounts = [
|
|
"/data/models:/models:ro",
|
|
"~/.gitconfig:/home/agent/.gitconfig:ro",
|
|
"/tmp/cache:/cache",
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### shell
|
|
|
|
Default shell inside the container.
|
|
|
|
- **Type:** String
|
|
- **Valid in:** Global, Project, Local
|
|
|
|
```toml
|
|
shell = "zsh"
|
|
```
|
|
|
|
---
|
|
|
|
### justfile
|
|
|
|
Path to a justfile to make available in the container.
|
|
|
|
- **Type:** File path (string)
|
|
- **Valid in:** Global, Project, Local
|
|
|
|
```toml
|
|
justfile = "./project.justfile"
|
|
```
|
|
|
|
---
|
|
|
|
### dockerfiles
|
|
|
|
Custom Dockerfile overrides for image builds. Keyed by image short name. Values can be a path to a Dockerfile or a directory containing one.
|
|
|
|
- **Type:** Map of string keys to file paths
|
|
- **Valid in:** Global, Project (project overrides require the project to be in `trusted_projects`)
|
|
- **Behavior:** Project-level Dockerfile overrides are ignored unless the project directory is listed in `trusted_projects` in the global config.
|
|
|
|
```toml
|
|
# .sandcage/config.toml (only applied if project is trusted)
|
|
[dockerfiles]
|
|
sandcage = "./docker/Dockerfile.custom"
|
|
```
|
|
|
|
```toml
|
|
# ~/.sandcage/config.toml
|
|
[dockerfiles]
|
|
sandcage = "/home/user/my-dockerfiles/sandcage/"
|
|
```
|
|
|
|
---
|
|
|
|
### trusted_projects
|
|
|
|
List of project directories allowed to use Dockerfile overrides.
|
|
|
|
- **Type:** List of file paths
|
|
- **Valid in:** Global only (a project cannot self-trust)
|
|
- **Behavior:** Read exclusively from `~/.sandcage/config.toml`. Projects with `dockerfiles` set are ignored unless their path (or a parent) appears here.
|
|
|
|
```toml
|
|
# ~/.sandcage/config.toml
|
|
trusted_projects = [
|
|
"/home/user/projects/my-trusted-project",
|
|
"/home/user/work",
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### container_workspace
|
|
|
|
Override the workspace path inside the container. Must be an absolute path (starting with `/`). Relative paths are ignored with a warning.
|
|
|
|
- **Type:** String (absolute path)
|
|
- **Valid in:** Global, Project, Local
|
|
- **Default:** `/workspace/<project-directory-name>` (derived from the host workspace folder name)
|
|
|
|
```toml
|
|
container_workspace = "/workspace/my-app"
|
|
```
|
|
|
|
---
|
|
|
|
### agent_args
|
|
|
|
Default arguments passed to agents on every invocation. Keyed by service name. These are appended after the service name but before any extra CLI arguments.
|
|
|
|
- **Type:** Map of service name to list of strings
|
|
- **Valid in:** Global, Project, Local
|
|
- **Behavior:** Only the args for the invoked service are used. When running with `--shell`, agent_args are skipped entirely.
|
|
|
|
```toml
|
|
# .sandcage/config.toml
|
|
[agent_args]
|
|
claude = ["--dangerously-skip-permissions"]
|
|
codex = ["--full-auto"]
|
|
```
|
|
|
|
```toml
|
|
# ~/.sandcage/config.toml
|
|
[agent_args]
|
|
claude = ["--dangerously-skip-permissions"]
|
|
```
|
|
|
|
---
|
|
|
|
### ssh_mode
|
|
|
|
Controls how SSH keys are made available inside the container.
|
|
|
|
- **Type:** String — one of `"volume"`, `"bind"`, or `"none"`
|
|
- **Valid in:** Global, Project, Local
|
|
- **Default:** No SSH mount (equivalent to `"none"`)
|
|
|
|
| Value | Behavior |
|
|
|----------|----------|
|
|
| `volume` | Mounts a named Docker volume (`sandcage-ssh`) at `/home/agent/.ssh` (read-only). Populate it with `sandcage setup ssh`. |
|
|
| `bind` | Bind-mounts `~/.ssh` from the host at `/home/agent/.ssh` (read-only). |
|
|
| `none` | No SSH directory is mounted. |
|
|
|
|
```toml
|
|
ssh_mode = "volume"
|
|
```
|
|
|
|
When using `volume` mode, run `sandcage setup ssh --refresh` after changing keys on the host.
|
|
|
|
---
|
|
|
|
### ssh_keys
|
|
|
|
Declares which SSH keys to copy into the volume when using `sandcage setup ssh`. Each entry specifies a host and identity file.
|
|
|
|
- **Type:** List of objects with `host` (string) and `identity_file` (string) fields
|
|
- **Valid in:** Global, Project, Local
|
|
|
|
```toml
|
|
[[ssh_keys]]
|
|
host = "github.com"
|
|
identity_file = "~/.ssh/id_ed25519"
|
|
|
|
[[ssh_keys]]
|
|
host = "gitea.internal"
|
|
identity_file = "~/.ssh/work_gitea"
|
|
```
|
|
|
|
---
|
|
|
|
### services
|
|
|
|
Override the enabled state of built-in services. Keyed by service name.
|
|
|
|
- **Type:** Map of service name to object with optional `enabled` (boolean) field
|
|
- **Valid in:** Global, Project, Local
|
|
- **Behavior:** Disabled services cannot be started with `sandcage <service>` and are excluded from generated compose files.
|
|
|
|
```toml
|
|
[services.gemini]
|
|
enabled = false
|
|
|
|
[services.codex]
|
|
enabled = false
|
|
```
|
|
|
|
```toml
|
|
# ~/.sandcage/config.toml
|
|
[services.codex]
|
|
enabled = false
|
|
```
|
|
|
|
---
|
|
|
|
### default_services
|
|
|
|
Controls which services `sandcage build` prepares by default (when no service names are given on the CLI).
|
|
|
|
- **Type:** List of strings (service names)
|
|
- **Valid in:** Global, Project, Local
|
|
|
|
```toml
|
|
default_services = ["claude", "shell"]
|
|
```
|
|
|
|
---
|
|
|
|
### image
|
|
|
|
The Docker image name used for building and running containers.
|
|
|
|
- **Type:** String
|
|
- **Valid in:** Global, Project, Local
|
|
- **Default:** `"sandcage"`
|
|
|
|
```toml
|
|
# .sandcage/config.toml
|
|
image = "my-custom-sandcage"
|
|
```
|
|
|
|
```toml
|
|
# ~/.sandcage/config.toml
|
|
image = "my-custom-sandcage"
|
|
```
|
|
|
|
---
|
|
|
|
### overlay.enabled
|
|
|
|
Controls whether overlay mounts are active. When enabled, files in `.sandcage/overlay/` (and `.sandcage/local/overlay/`) are bind-mounted read-only over the corresponding workspace files inside the container.
|
|
|
|
- **Type:** Boolean
|
|
- **Valid in:** Global, Project, Local
|
|
- **Default:** `true`
|
|
|
|
```toml
|
|
[overlay]
|
|
enabled = false
|
|
```
|
|
|
|
Local overlays (`.sandcage/local/overlay/`) take precedence over project overlays (`.sandcage/overlay/`).
|
|
|
|
---
|
|
|
|
### shadow
|
|
|
|
Shadow directories redirect build artifact and dependency directories away from the workspace bind-mount into faster, container-local storage. This prevents large directories like `target/`, `.venv/`, or `node_modules/` from thrashing the host filesystem.
|
|
|
|
- **Type:** Object with optional `backend` (string) and `dirs` (map) fields
|
|
- **Valid in:** Global, Project, Local
|
|
- **Default backend:** `"volume"` (named Docker volume)
|
|
|
|
Each entry under `[shadow.dirs]` can override the global backend and set `shared = true` to share the volume across containers for the same project.
|
|
|
|
| Backend | Behavior |
|
|
|-------------|----------|
|
|
| `volume` | Named Docker volume (`sandcage-<project>-<dir-hash>`), persisted across runs |
|
|
| `directory` | Host directory under `~/.sandcage/shadow/`, persisted |
|
|
| `tmpfs` | In-memory tmpfs mount, lost when the container stops |
|
|
|
|
```toml
|
|
[shadow]
|
|
backend = "volume"
|
|
|
|
[shadow.dirs]
|
|
".venv" = {}
|
|
"target" = { backend = "directory" }
|
|
".cargo/registry" = { shared = true }
|
|
"node_modules" = { backend = "tmpfs" }
|
|
```
|
|
|
|
Use `sandcage clean` to remove shadow volumes for a project.
|
|
|
|
---
|
|
|
|
### tools
|
|
|
|
Tool mode declarations control how specific CLI tools behave inside the container. Each entry under `[tools.<name>]` configures one tool.
|
|
|
|
- **Type:** Map of tool name to object
|
|
- **Valid in:** Project, Local
|
|
- **Fields:**
|
|
- `mode` — `"in-cage"` (default), `"spoof"`, or `"host"`
|
|
- `message` — message printed by a spoof script (only for `mode = "spoof"`)
|
|
- `allow` — list of glob patterns permitting arguments (only for `mode = "host"`)
|
|
- `deny` — list of glob patterns blocking arguments (only for `mode = "host"`)
|
|
|
|
| Mode | Behavior |
|
|
|-----------|----------|
|
|
| `in-cage` | Tool runs normally inside the container (default) |
|
|
| `spoof` | Replaced by a script that prints `message` and exits. Prevents agents from running the tool. |
|
|
| `host` | Forwarded to the host via the forwarding daemon. Requires `trusted_projects`. |
|
|
|
|
**Allow/deny patterns** use simple `*` wildcard glob matching on the full argument string. Deny patterns are checked first and override allow patterns. If any allow patterns are specified, the command must match at least one.
|
|
|
|
```toml
|
|
[tools.cargo]
|
|
mode = "spoof"
|
|
message = "Use `just build` to compile this project."
|
|
|
|
[tools.docker]
|
|
mode = "host"
|
|
allow = ["compose up *", "compose down"]
|
|
deny = ["* prune *", "* rm -f *"]
|
|
|
|
[tools.uv]
|
|
mode = "in-cage"
|
|
```
|
|
|
|
---
|
|
|
|
### tasks
|
|
|
|
Named operations that can run on the host or inside the container. Host tasks are forwarded via the forwarding daemon and thumper; in-cage tasks run directly inside the container.
|
|
|
|
- **Type:** Map of task name to object
|
|
- **Valid in:** Project, Local
|
|
- **Fields:**
|
|
- `where` — `"host"` or `"in-cage"` (where the task executes)
|
|
- `run` — the command to execute (string, split on whitespace)
|
|
|
|
Host tasks (`where = "host"`) require the project to be in `trusted_projects`. A Justfile overlay is auto-generated for host tasks, making them available as `just <task-name>` inside the container.
|
|
|
|
```toml
|
|
[tasks.build]
|
|
where = "host"
|
|
run = "cargo build --release"
|
|
|
|
[tasks.test]
|
|
where = "host"
|
|
run = "cargo test"
|
|
|
|
[tasks.lint]
|
|
where = "in-cage"
|
|
run = "clippy"
|
|
```
|
|
|
|
---
|
|
|
|
### profiles
|
|
|
|
Security profiles applied to the container via `--security-opt`. Profile files are loaded from `.sandcage/profiles/`.
|
|
|
|
- **Type:** Object with optional `apparmor` (string) and `seccomp` (string) fields
|
|
- **Valid in:** Project, Local
|
|
- **Behavior:** Paths are relative to the project root. Missing files are skipped with a warning. Applied on both the bollard and compose backends.
|
|
|
|
If a seccomp profile contains `SCMP_ACT_NOTIFY`, sandcage detects this as a fermata hook point and logs accordingly.
|
|
|
|
```toml
|
|
[profiles]
|
|
apparmor = ".sandcage/profiles/apparmor.conf"
|
|
seccomp = ".sandcage/profiles/seccomp.json"
|
|
```
|
|
|
|
### home
|
|
|
|
Named home profile for agent auth and configuration. Each profile is a separate directory under `~/.sandcage/homes/`, containing its own agent credentials, settings, and session history.
|
|
|
|
- **Type:** String (profile name)
|
|
- **Valid in:** Global, Project, Local
|
|
- **Default:** `"default"`
|
|
- **CLI override:** `--home <NAME>` on any agent or shell subcommand
|
|
- **Behavior:** Resolves to `~/.sandcage/homes/<name>/`, which is mounted as `/home/agent` inside the container. The directory is created automatically on first use. CLI flag takes precedence over config.
|
|
|
|
```toml
|
|
# Use a specific home profile for this project
|
|
home = "work"
|
|
```
|
|
|
|
This allows switching between multiple agent identities (e.g., personal and work Claude accounts) without re-authenticating:
|
|
|
|
```sh
|
|
sandcage claude --home personal # uses ~/.sandcage/homes/personal/
|
|
sandcage claude --home work # uses ~/.sandcage/homes/work/
|
|
sandcage claude # uses ~/.sandcage/homes/default/
|
|
```
|
|
|
|
**Migration:** If a legacy `~/.sandcage/home/` directory exists from before named profiles were introduced, it is automatically moved to `~/.sandcage/homes/default/` on first run.
|
|
|
|
---
|
|
|
|
### Reserved Sections
|
|
|
|
The following top-level config section is parsed and validated but **not yet active**:
|
|
|
|
- **`forward`** — port/socket forwarding rules (the forwarding daemon uses internal configuration; this section is for future user-facing forwarding config)
|
|
|
|
Setting this field will not produce errors, but it has no runtime effect yet.
|
|
|
|
---
|
|
|
|
## Services
|
|
|
|
Sandcage ships with four built-in services, all enabled by default:
|
|
|
|
| Service | Description | Config directory |
|
|
|----------|-----------------------|------------------|
|
|
| `claude` | Claude Code agent | `.claude` |
|
|
| `codex` | Codex agent | `.codex` |
|
|
| `gemini` | Gemini CLI agent | `.gemini` |
|
|
| `shell` | Interactive zsh shell | (none) |
|
|
|
|
Agent services are installed on first run into the persistent home directory (`~/.sandcage/homes/<profile>/`). They auto-update on subsequent runs.
|
|
|
|
To disable a service you do not use:
|
|
|
|
```toml
|
|
[services.codex]
|
|
enabled = false
|
|
|
|
[services.gemini]
|
|
enabled = false
|
|
```
|
|
|
|
Disabled services are excluded from Docker compose generation and cannot be invoked.
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Minimal project config
|
|
|
|
```toml
|
|
# .sandcage/config.toml
|
|
packages = ["ripgrep"]
|
|
|
|
[toolchains]
|
|
nvm = true
|
|
```
|
|
|
|
### Full-featured project config
|
|
|
|
```toml
|
|
# .sandcage/config.toml
|
|
packages = ["ripgrep", "fd-find", "postgresql-client"]
|
|
mounts = ["/data/models:/models:ro"]
|
|
shell = "zsh"
|
|
|
|
[env]
|
|
RUST_LOG = "debug"
|
|
NODE_ENV = "development"
|
|
|
|
[toolchains]
|
|
rust = true
|
|
nvm = true
|
|
|
|
[agent_args]
|
|
claude = ["--dangerously-skip-permissions"]
|
|
|
|
[services.gemini]
|
|
enabled = false
|
|
```
|
|
|
|
### Local overrides (gitignored)
|
|
|
|
```toml
|
|
# .sandcage/local/config.local.toml
|
|
ssh_mode = "volume"
|
|
mounts = ["~/.aws:/home/agent/.aws:ro"]
|
|
|
|
[env]
|
|
SECRET_API_KEY = "sk-local-dev-key"
|
|
|
|
[[ssh_keys]]
|
|
host = "github.com"
|
|
identity_file = "~/.ssh/id_ed25519"
|
|
```
|
|
|
|
### Global config
|
|
|
|
```toml
|
|
# ~/.sandcage/config.toml
|
|
shell = "zsh"
|
|
|
|
[env]
|
|
EDITOR = "vim"
|
|
|
|
[agent_args]
|
|
claude = ["--dangerously-skip-permissions"]
|
|
|
|
[services.codex]
|
|
enabled = false
|
|
|
|
trusted_projects = [
|
|
"/home/user/work/trusted-project",
|
|
]
|
|
```
|