Skip to content

Profile Configuration Reference

A profile is a YAML file at ~/.ccpod/profiles/<name>/profile.yml. ccpod validates it with Zod at load time — invalid files fail fast with a readable error.

name: personal
description: My personal Claude environment
config:
source: local # "local" | "git"
path: ~/.my-claude-config # local path to config dir
# source: git
# repo: https://github.com/org/claude-config
# sync: daily # "always" | "daily" | "pin"
# ref: main
image:
use: ghcr.io/yorch/ccpod:latest
# use: build
# dockerfile: "{{profile_dir}}/Dockerfile"
auth:
type: api-key # "api-key" | "oauth" | "proxy"
keyEnv: ANTHROPIC_API_KEY # env var to read from (api-key only)
# keyFile: ~/.ccpod/credentials/default/api-key # must be under ~/.ccpod (api-key only)
state: ephemeral # "ephemeral" (default) | "persistent"
ssh:
agentForward: true # forward SSH_AUTH_SOCK
mountSshDir: false # mount ~/.ssh read-only
network:
policy: full # "full" | "restricted"
allow: [] # domains/IPs allowed in restricted mode
ports:
list:
- "3000:3000" # host:container
autoDetectMcp: true # expose HTTP/SSE MCP ports from .mcp.json
plugins:
- mcp-server-brave-search # delta-installed on first run
env:
- DATABASE_URL # host env vars to forward
services:
postgres:
image: postgres:17
env:
POSTGRES_PASSWORD: dev
volumes:
- ccpod-pg-data:/var/lib/postgresql/data

/^[a-zA-Z0-9_-]{1,64}$/. Used as a directory name and Docker label. Validated at parse time.

FieldTypeNotes
sourcelocal | gitWhere to read the Claude config tree.
pathstringRequired when source: local. Tilde-expanded.
repostringRequired when source: git. HTTPS or SSH URL. Cached checkouts are verified as clean, owned Git repositories with the configured origin before reuse or reset. Unsafe destinations fail closed; a failed remote fetch may use a previously verified cached copy.
syncalways | daily | pinWhen to refresh the clone. pin never re-pulls.
refstringBranch, tag, or commit to check out. Defaults to remote HEAD.
FieldTypeNotes
usestringImage reference (e.g. ghcr.io/yorch/ccpod:latest) or the literal build.
dockerfilestringRequired when use: build. Absolute path, path relative to the profile directory (never the project — a cloned repo’s own Dockerfile is not built), or {{profile_dir}}/Dockerfile.

When use: build, both ccpod run and ccpod image build use the same tag ccpod-local-<profile>-<hash>:latest (hash derived from Dockerfile contents). Running ccpod image build pre-builds the image so ccpod run reuses it without rebuilding. Override the tag with --tag. Force a rebuild with ccpod run --rebuild.

FieldTypeNotes
typeapi-key | oauth | proxy
keyEnvstringEnv var name to read on the host (api-key only).
keyFilestringFile on the host to read (api-key only). Must be a path under ~/.ccpod/ (no .., no escape via symlink); use keyEnv for keys stored elsewhere.

For oauth, ccpod manages tokens in ~/.ccpod/credentials/<name>/.

For proxy, ccpod starts a local HTTP proxy that shares the host’s OAuth session without copying credentials into the container. The container runs claude in API-key mode with a sentinel key, and the proxy injects the real OAuth bearer token into each request. This eliminates the refresh-token rotation race that occurs when multiple containers share the same OAuth login. The proxy holds the only refresh token, serializes refreshes with a single-flight lock, and writes refreshed tokens back to the host Keychain. Requires host OAuth credentials — run claude /login on the host first. If native claude runs concurrently on the host and refreshes the same OAuth session, the proxy re-reads the host credential store to recover, but a brief window of 401s is possible.

ephemeral (default) wipes Claude history and session state when the container exits. persistent binds ~/.ccpod/state/<name>/ on the host into the container — history, projects, and todos survive across runs. Override per run with --no-state.

per-profile (default) shares state across all projects using the same profile. per-project isolates state per project — each project gets its own ~/.ccpod/state/<name>/<projectHash>/ directory, preventing cross-project state leakage. Only meaningful with state: persistent. See State Persistence for details.

A list of Claude Code plugin names to install on first run. ccpod passes them to the container entrypoint, which delta-installs only the ones not already present — subsequent runs are fast.

plugins:
- mcp-server-brave-search
- mcp-server-filesystem
FieldDefaultNotes
agentForwardtrueForwards SSH_AUTH_SOCK into the container. Rejected if the value contains :. Not supported with Podman (skipped with a warning).
mountSshDirfalseMounts ~/.ssh read-only.
FieldNotes
policyfull (bridge with internet) or restricted (allow-list only).
allowHostnames/IPs to allow in restricted mode. Resolved at container start.

See Network Policy.

FieldNotes
listManual host:container mappings.
autoDetectMcpDefault true. If .mcp.json exists at $PWD, HTTP/SSE MCP ports are exposed automatically.

A list of entries describing env vars to expose in the container. Three forms are supported:

FormBehaviour
KEYForward the host value of KEY (skipped if unset on host).
KEY=valueSet a literal value.
KEY=${HOST_VAR} / KEY=${HOST_VAR:-default}Interpolate a host variable into the value at run time. Missing vars without a :-default substitute an empty string and emit a warning.
env:
- DATABASE_URL # forward host DATABASE_URL
- NODE_ENV=development # literal
- GH_TOKEN=${GITHUB_TOKEN} # interpolate
- REGION=${AWS_REGION:-us-east-1} # interpolate with default

Interpolation syntax follows POSIX shell: ${NAME} and ${NAME:-default} only (no :?, :+, command substitution, or nesting). Names match [A-Za-z_][A-Za-z0-9_]*. The :-default portion is a literal string — variable references inside it (e.g. ${REGION:-us-${ZONE}}) are not expanded. Following POSIX semantics, a host var set to the empty string is still considered “set” and wins over :-default; only an unset var triggers the default. Interpolation is currently scoped to env values only; other string fields (image, binds, claudeArgs, etc.) take their values literally. Project .ccpod.yml env: entries and --env KEY=VALUE CLI overrides accept the same syntax.

Default []. A list of host variable names that a project’s .ccpod.yml env: is allowed to forward by bare name (e.g. - GITHUB_TOKEN). By default a project entry that names a host variable is ignored with a warning, because a cloned repo could otherwise pull any secret from your shell into the container — the same reason ${VAR} interpolation is blocked in project env.

allowProjectEnvForward:
- GITHUB_TOKEN
- NPM_TOKEN

Project KEY=literal entries are unaffected. Profile env: and --env are trusted and can forward anything.

A list of extra CLI flags passed verbatim to the claude command on every run. These are appended before any claudeArgs in the project config.

claudeArgs:
- "--dangerously-skip-permissions"

A list of shell commands run inside the container as the node user in /workspace after config is seeded and before Claude starts. Commands execute with set -e (exit on error).

init:
- npm install
- git config --global user.email "dev@example.com"

Merge behaviour: in deep mode (default) profile commands run first, project commands appended. In override mode, project commands replace profile commands entirely. When isolation: true, project init is ignored.

Default false. When true, the profile ignores all project-level config for this run:

  • .ccpod.yml settings (network, claudeArgs, services, env, ports, merge strategy)
  • Project CLAUDE.md
  • Project .claude/settings.json and other .claude/ assets
  • Project .mcp.json (MCP port auto-detection)

The profile config is used as-is. Useful for security-sensitive profiles where you want a guaranteed, unmodifiable environment regardless of the repo being run.

isolation: true

Note: isolation does not prevent profile selection — a project’s .ccpod.yml can still specify profile: my-isolated-profile to opt into it (ccpod asks you to approve a project’s profile choice the first time, and remembers it; --profile skips the prompt). CLI flags (--no-state, --env, --rebuild) continue to work.

Default: none (no preset injected). Sets a Claude Code permissions.allow preset as the lowest-priority layer — your profile and project settings.json always override it.

PresetEffect
conservativeAuto-allow Edit and Write — file edits skip prompts, Bash still prompts
moderateAuto-allow Bash, Edit, Write — no prompts for typical dev work
permissiveSets defaultMode: bypassPermissions — skips all prompts (Docker is the trust boundary)
permissions: moderate

Read, Glob, and Grep require no permission in Claude Code and are always free — no need to list them. permissive uses Claude Code’s bypassPermissions mode rather than listing individual tools, so it covers all current and future tools automatically.

The preset is injected as the lowest-priority layer. If your profile or project settings.json already sets permissions.allow, those entries are unioned with the preset (deduplicated). Explicit entries always survive.

Sidecar containers (Postgres, Redis, queues, anything with a Docker image). Reachable from the Claude container by service name on a shared network. Optional dependsOn lists other declared services to start first; dependencies are validated for missing names and cycles before Docker changes. Optional ready.timeoutSeconds (1–3600, default 60) bounds create/start and waits for a configured image healthcheck to report healthy. The image must define a healthcheck when ready is set; without ready, ccpod does not wait for health. See Sidecar Services.

Terminal window
ccpod config validate
ccpod config show # print resolved merged config