Run Claude Code with full autonomy inside a security-hardened container. Your host system stays untouched.
Claude Code with --dangerously-skip-permissions is powerful. It can read, write, and execute anything without asking which can impact sensitive data on a host filesystem sych as dotfiles, SSH keys, other projects or personal data.
This container trades bubblewrap's sandbox for Docker/Podman's kernel-enforced isolation. You get more capability (no permission prompts, ever) with more safety (kernel-enforced boundaries that bubblewrap can't match on many Linux configurations).
The blast radius is controlled by what you mount. The workspace you pass to ck run is what Claude can touch. Your host filesystem, your network, your other projects -- none of that is accessible. Kernel namespace isolation enforces this, not application-level policy. Worst case, Claude trashes the project workspace you explicitly allowed it to write to. Your host system is guarded.
git clone <repo-url>
cd claude-kitchen
./install.sh # symlinks ck into ~/.local/binck run # builds image on first run, then launches Claude CodeOn first launch, Claude Code will prompt you to log in interactively. Your credentials are stored in a per-workspace named volume and persist across restarts. Alternatively, pass an OAuth token via file:
claude setup-token > ~/.claude-token
CK_CLAUDE_OAUTH_TOKEN_FILE=~/.claude-token ck runThat's it. ck auto-detects whether you have Podman or Docker and handles everything else.
./install.shThis creates a symlink at ~/.local/bin/ck pointing to the ck script in this repository. If ~/.local/bin is not in your PATH, the script tells you what to add to your shell profile.
To uninstall:
rm ~/.local/bin/ckIf you prefer not to install, run ./ck directly from the repository root.
Interactive -- default when stdin is a TTY. Full Claude Code interface in your terminal.
ck runHeadless -- pass any Claude flags through. Output streams to stdout.
ck run -p "Refactor the authentication module"Per-project workspace -- mount a specific directory instead of the current one.
CK_WORKSPACE=/path/to/project ck runShared host config -- inject your host-level skills, rules, and CLAUDE.md into the container. Useful for applying the same conventions across all projects.
CK_HOST_SKILLS=~/.claude/skills \
CK_HOST_RULES=~/.claude/rules \
CK_HOST_CLAUDE_MD=~/.claude/CLAUDE.md \
ck runClaude flags are passed through after run. For example, to continue an existing task:
ck run --resumeCredentials: Two authentication methods are supported. When both are configured, the env var takes precedence.
Option 1 -- OAuth token from file (recommended for CI/headless):
claude setup-token > ~/.claude-token
CK_CLAUDE_OAUTH_TOKEN_FILE=~/.claude-token ck runThe ck launcher reads the token from the file and passes it into the container, where it's written to .credentials.json in the named volume. You can also pass the token directly via CK_CLAUDE_OAUTH_TOKEN if you have it in a variable already.
Option 2 -- Interactive login (recommended for interactive use):
ck runOn first run, Claude Code prompts you to log in. Credentials are stored in the per-workspace named volume (ck-config-{workspace}) and persist across container restarts.
| Variable | Purpose | Default |
|---|---|---|
CK_CLAUDE_MODEL |
Override default model | claude-opus-4-6 |
CK_CLAUDE_SMALL_FAST_MODEL |
Override fast/haiku model | (unset) |
CK_CLAUDE_EFFORT |
Override reasoning effort (low/medium/high) | high |
CK_CLEAN_SESSION |
Wipe conversation history on startup | 0 |
CK_HOST_SKILLS |
Path to host skills directory to inject | (unset) |
CK_HOST_RULES |
Path to host rules directory to inject | (unset) |
CK_HOST_CLAUDE_MD |
Path to host CLAUDE.md file to inject | (unset) |
CK_WORKSPACE |
Override workspace directory to mount | Current directory |
CK_WORKSPACE_NAME |
Override workspace name in status line | Auto-derived from workspace directory name |
CK_IMAGE |
Override container image name | claude-kitchen |
CK_RUNTIME |
Force container runtime (docker or podman) |
Auto-detected |
CK_LAN_ISOLATION |
Enable per-container LAN isolation (rootful Docker only) | 0 (disabled) |
CK_CLAUDE_OAUTH_TOKEN |
OAuth token (raw value) | (unset -- uses interactive login) |
CK_CLAUDE_OAUTH_TOKEN_FILE |
Path to file containing OAuth token from claude setup-token |
(unset) |
GIT_AUTHOR_NAME |
Git identity name (fallback if no .gitconfig) | (unset) |
GIT_AUTHOR_EMAIL |
Git identity email (fallback if no .gitconfig) | (unset) |
SSH_AUTH_SOCK |
SSH agent socket (auto-forwarded if set) | (unset) |
Model and effort overrides are injected into settings.json at startup. Git config is mounted read-only from ~/.gitconfig if it exists. SSH agent socket is forwarded automatically when SSH_AUTH_SOCK is set.
For users who prefer Compose over the CLI. Podman users can use podman-compose with the same file. For daily use, ck is preferred -- it handles runtime detection and UID mapping automatically.
services:
claude:
image: claude-kitchen
stdin_open: true
tty: true
cap_drop:
- ALL
read_only: true
security_opt:
- no-new-privileges
- label=disable
tmpfs:
- /home/claude
volumes:
- ./:/workspace
- ck-config:/home/claude/.claude
- ck-tools:/opt/tools
# Optional: git identity
# - ~/.gitconfig:/home/claude/.gitconfig:ro
environment:
# Auth option 1: set CK_CLAUDE_OAUTH_TOKEN in .env or shell
# CK_CLAUDE_OAUTH_TOKEN: ${CK_CLAUDE_OAUTH_TOKEN}
# Auth option 2: omit token, log in interactively (credentials persist in ck-config volume)
# CK_CLAUDE_MODEL: claude-sonnet-4-6
# CK_CLAUDE_EFFORT: high
volumes:
ck-config:
ck-tools:Worst case: Claude trashes the workspace you mounted. Your host system is untouched.
The container enforces this through several layers of hardening:
| Measure | What It Prevents |
|---|---|
--cap-drop ALL |
All Linux capabilities removed -- process cannot do anything a normal user cannot |
--read-only |
Root filesystem is immutable -- malicious code cannot modify system binaries |
--no-new-privileges |
Blocks setuid binaries from escalating to root |
| UID mapping | Container processes run as your host UID -- Podman uses --userns=keep-id, Docker adjusts via usermod/groupmod |
| Non-root user | Container runs as unprivileged user claude |
| No Docker socket | Container has zero visibility into other containers or the daemon |
LAN isolation (CK_LAN_ISOLATION=1) |
Container cannot reach RFC 1918 addresses (your local network); internet access preserved |
--tmpfs /home/claude |
Home scratch space (.local/, .cache/, .config/) is ephemeral. The ~/.claude named volume mounted inside persists credentials, settings, and session data across restarts. |
Why --dangerously-skip-permissions is safe here: the flag removes Claude Code's internal permission prompts because Docker/Podman's kernel-enforced isolation is a strictly stronger boundary than bubblewrap. Claude gets full autonomy inside the container. The container itself is the cage. The workspace bind mount defines the blast radius.
The container configures a status line that displays during Claude Code sessions:
my-project · Opus 4 · high · ctx:23%
Fields shown: workspace name, model display name, effort level, and context window usage. When context percentage is unavailable (before the first API response), it is omitted.
The workspace name is auto-derived from the directory name of the mounted workspace. To override it:
CK_WORKSPACE_NAME="custom-name" ck runIf the host-mounted settings.json or settings.local.json includes a statusLine key, it overrides the container's default status line configuration.
By default, containers can reach hosts on your local network. To block this, enable LAN isolation:
CK_LAN_ISOLATION=1 ck runThis creates a dedicated Docker bridge network (ck-lan-isolated) and adds iptables rules to the DOCKER-USER chain that block traffic to RFC 1918 addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16). Internet access is preserved -- the container can still reach external APIs. Docker's embedded DNS at 127.0.0.11 continues working normally.
When you exit the container, ck cleans up the iptables rules and Docker network automatically.
Requirements:
- Rootful Docker (not Podman)
sudoaccess (for iptables and docker network commands)- Docker's default iptables firewall backend (not the experimental nftables backend)
Rootless Podman limitation: LAN isolation is not supported with rootless Podman. Podman uses pasta (userspace networking) where container traffic appears as host traffic -- kernel iptables rules cannot distinguish container packets from regular user packets. When CK_LAN_ISOLATION=1 is set with Podman, ck prints a warning and continues without isolation.
At startup, the entrypoint runs the following sequence:
- Creates home directory structure (tmpfs wipes it each run, so dirs must be recreated)
- Fixes
/opt/toolsvolume ownership to match the running user - Constructs
settings.jsonfrom environment variables (CK_CLAUDE_MODEL,CK_CLAUDE_SMALL_FAST_MODEL,CK_CLAUDE_EFFORT) and configures the status line command - Writes
~/.claude/statusline-wrapper.sh-- a thin wrapper that injects the workspace path into the session JSON before piping to the GSD status line renderer - Copies host skills, rules, and CLAUDE.md from staging mounts into
~/.claude/if any of theCK_HOST_*vars were set - Runs
setup_auth()-- ifCK_CLAUDE_OAUTH_TOKENis set (directly or read fromCK_CLAUDE_OAUTH_TOKEN_FILEby the launcher), writes.credentials.jsonto the named volume. If credentials already exist in the volume (from a prior interactive login), uses them as-is. Otherwise, Claude Code will offer an interactive login prompt on startup. - Wipes
~/.claude/projects/ifCK_CLEAN_SESSION=1 - Sets git identity from env vars if no
.gitconfigis mounted - Runs preflight checks: validates credentials file present and readable, settings.json valid JSON, GSD framework present, /workspace mounted and writable. Exits 1 with a diagnostic message if any check fails.
- Executes
claude --dangerously-skip-permissionswith all arguments you passed tock run
Volume architecture:
- Workspace -- bind mount (read-write). This is the blast radius.
ck-config-- named volume mounted at/home/claude/.claude. Persists GSD framework, settings, and session history across restarts. Seeded from the image on first run via Docker/Podman copy-up.ck-tools-- named volume mounted at/opt/tools. Persists packages and runtimes Claude installs during a session.- Home -- tmpfs. Ephemeral scratch space for
.local/,.cache/,.config/. Wiped on every run; the named volumes mounted inside it survive.
- Docker or Podman (
ckauto-detects) - Authentication via one of:
CK_CLAUDE_OAUTH_TOKEN_FILEpointing to a file fromclaude setup-token(recommended for CI)- Interactive login on first run (credentials persist in named volume -- recommended for interactive use)
That's it.
./ck buildTo pin specific versions:
./ck build --version 1.0.0 --gsd-version 2.0.0Build arguments:
| Build Arg | Purpose | Default |
|---|---|---|
CLAUDE_CODE_VERSION |
Pin claude-code npm package version | latest |
GSD_VERSION |
Pin get-shit-done-cc npm package version | latest |
NODE_MAJOR |
Node.js major version | 22 |