Skip to content

Profile Configuration

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"
keyEnv: ANTHROPIC_API_KEY # env var to read from
# keyFile: ~/.ccpod/credentials/default/api-key # must be under ~/.ccpod
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.
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 $PWD, or {{profile_dir}}/Dockerfile to reference a Dockerfile inside the profile directory.

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
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>/.

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.

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.

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. 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. See Sidecar Services.

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