Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Claude Kitchen

Run Claude Code with full autonomy inside a security-hardened container. Your host system stays untouched.

Why

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.

Quickstart

git clone <repo-url>
cd claude-kitchen
./install.sh        # symlinks ck into ~/.local/bin
ck run              # builds image on first run, then launches Claude Code

On 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 run

That's it. ck auto-detects whether you have Podman or Docker and handles everything else.

Install

./install.sh

This 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/ck

If you prefer not to install, run ./ck directly from the repository root.

Usage

Interactive -- default when stdin is a TTY. Full Claude Code interface in your terminal.

ck run

Headless -- 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 run

Shared 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 run

Claude flags are passed through after run. For example, to continue an existing task:

ck run --resume

Configuration

Credentials: 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 run

The 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 run

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

Docker Compose

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:

Security Model

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.

Status Line

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 run

If the host-mounted settings.json or settings.local.json includes a statusLine key, it overrides the container's default status line configuration.

LAN Isolation

By default, containers can reach hosts on your local network. To block this, enable LAN isolation:

CK_LAN_ISOLATION=1 ck run

This 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)
  • sudo access (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.

How It Works

At startup, the entrypoint runs the following sequence:

  1. Creates home directory structure (tmpfs wipes it each run, so dirs must be recreated)
  2. Fixes /opt/tools volume ownership to match the running user
  3. Constructs settings.json from environment variables (CK_CLAUDE_MODEL, CK_CLAUDE_SMALL_FAST_MODEL, CK_CLAUDE_EFFORT) and configures the status line command
  4. 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
  5. Copies host skills, rules, and CLAUDE.md from staging mounts into ~/.claude/ if any of the CK_HOST_* vars were set
  6. Runs setup_auth() -- if CK_CLAUDE_OAUTH_TOKEN is set (directly or read from CK_CLAUDE_OAUTH_TOKEN_FILE by the launcher), writes .credentials.json to 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.
  7. Wipes ~/.claude/projects/ if CK_CLEAN_SESSION=1
  8. Sets git identity from env vars if no .gitconfig is mounted
  9. 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.
  10. Executes claude --dangerously-skip-permissions with all arguments you passed to ck 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.

Requirements

  • Docker or Podman (ck auto-detects)
  • Authentication via one of:
    • CK_CLAUDE_OAUTH_TOKEN_FILE pointing to a file from claude setup-token (recommended for CI)
    • Interactive login on first run (credentials persist in named volume -- recommended for interactive use)

That's it.

Building

./ck build

To pin specific versions:

./ck build --version 1.0.0 --gsd-version 2.0.0

Build 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

About

Get claude cookin' in a containerized environment

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages