cclaude (Containerized Claude Code) is a command-line tool that runs Claude Code inside an isolated container. Only the current project directory is mounted, so Claude Code cannot access anything outside the project. Claude Code state (~/.claude) is persisted on the host across sessions.
- Security isolation: Claude Code runs in a container and can only access the mounted project directory.
- Memory persistence:
~/.claude/(project memory, settings, conversation history) is preserved across container restarts. - Subscription & API key support: Works with both Claude subscription (OAuth) and API key authentication.
- Pre-installed toolchains: Go, Node.js, and Python (uv) are ready to use out of the box.
- No sandbox issues: Container-level isolation eliminates sandbox mode incompatibilities with build tools.
- Auto-detection: Automatically detects Podman or Docker; prefers Podman.
- Customizable: TOML config file with environment variable overrides. Extend the Dockerfile for additional toolchains.
- Podman or Docker
- Bash 3.2+
- Container VM with 8GB+ memory (for image build). For Podman Machine:
podman machine set --memory 8192
git clone https://github.com/nlink-jp/cclaude.git
cd cclaude
make install # installs to /usr/local/bin/cclaude
cclaude --build # builds the container imageTo install to a different directory (e.g., if /usr/local/bin is not writable):
make install PREFIX=$HOME/.local # installs to ~/.local/bin/cclaudeThe make install command places:
cclaudescript →$(PREFIX)/bin/cclaude(default:/usr/local/bin/cclaude)Dockerfile→~/.config/cclaude/Dockerfileconfig.toml→~/.config/cclaude/config.toml(if not already present)
# Build the container image (first time only)
cclaude --build
# Run Claude Code in any project directory
cd ~/my-project
cclaudeImportant: Always run
cclaudefrom the project root directory (where.git/,go.mod,package.json, etc. live). If you run it from a parent directory containing multiple projects, Claude Code may not correctly identify the project scope and could clone repositories instead of editing local files.
cclaude [options] [-- claude-args...]
| Option | Description |
|---|---|
cclaude |
Launch Claude Code for the current directory |
cclaude --build |
Build or rebuild the container image |
cclaude --shell |
Open a bash shell inside the container (debug) |
cclaude --config |
Show resolved configuration |
cclaude --version |
Print version |
cclaude --help |
Show help |
cclaude -- <args> |
Pass arguments directly to claude |
# Interactive session
cclaude
# Pass arguments to claude
cclaude -- -p "run go test ./..."
# Debug: drop into container shell
cclaude --shell
# Check resolved configuration
cclaude --configSet the ANTHROPIC_API_KEY environment variable:
export ANTHROPIC_API_KEY="sk-ant-..."
cclaudeRun cclaude without an API key. Claude Code will display an OAuth login URL in the terminal. Open the URL in your host browser to authenticate. The login state is persisted in ~/.claude/ across sessions.
Configuration file: ${XDG_CONFIG_HOME:-~/.config}/cclaude/config.toml
Environment variables override config file values.
[container]
runtime = "auto" # "podman", "docker", or "auto" (prefers podman)
image = "cclaude:latest"
# extra_mounts = ["/path/to/shared-lib"] # additional directories to mount
[network]
# forward_ports = [8080, 11434] # host → container (socat)
# publish_ports = [3000, 5173] # container → host (-p)
[toolchain]
go_version = "1.23.4"
node_version = "20"
[paths]
claude_home = "~/.claude"| Variable | Description | Default |
|---|---|---|
ANTHROPIC_API_KEY |
Claude API key (optional if using subscription) | — |
CCC_RUNTIME |
Container runtime | auto |
CCC_IMAGE |
Container image name | cclaude:latest |
CCC_EXTRA_MOUNTS |
Additional directories to mount (comma-separated) | — |
CCC_FORWARD_PORTS |
Ports forwarded host → container (e.g., 8080,11434) |
— |
CCC_PUBLISH_PORTS |
Ports published container → host (e.g., 3000,5173) |
— |
CCC_CLAUDE_HOME |
Claude home directory | ~/.claude |
CCC_GO_VERSION |
Go version for image build | 1.23.4 |
CCC_NODE_VERSION |
Node.js version for image build | 20 |
CCC_DRY_RUN=1 |
Print container command without executing | — |
The project directory is mounted at the same absolute path inside the container:
Host: /home/user/my-project
Container: /home/user/my-project (identical)
Claude Code stores project memory keyed by absolute path (e.g., ~/.claude/projects/-home-user-my-project/). By using the same path, project memory is fully compatible between host and container sessions.
The container uses its own copy of the Claude Code state, separate from the host:
Host: ~/.claude/ (host settings — untouched)
Container: ~/.config/cclaude/claude-home/ (container-specific copy)
On first run, cclaude copies the host's ~/.claude/ to ~/.config/cclaude/claude-home/. After that, the container copy is used independently. This design ensures:
- Host settings are never modified — sandbox, permissions, and plugin configuration on the host remain unchanged.
- Container has its own settings — sandbox is automatically disabled (the container itself provides isolation), and you can customize permissions independently.
- State persists across container restarts — project memory, conversation history, OAuth tokens, and plugins are retained in the container copy.
To re-sync with host settings (e.g., after changing host permissions), delete the container copy and it will be recreated on next run:
rm -rf ~/.config/cclaude/claude-home
cclaude # re-copies from host ~/.claude/Configure forward_ports to make host services accessible as localhost inside the container. This uses socat to transparently forward ports, so Claude Code and tools inside the container do not need to know they are running in a container.
[network]
forward_ports = [8080, 11434] # e.g., local LLM API, OllamaOr via environment variable:
CCC_FORWARD_PORTS="8080,11434" cclaudeWith this configuration, http://localhost:8080 inside the container reaches the host's localhost:8080.
To make services running inside the container accessible from the host (e.g., a dev server started by Claude Code):
[network]
publish_ports = [3000, 5173]Or: CCC_PUBLISH_PORTS="3000,5173" cclaude
This publishes 127.0.0.1:3000 on the host, forwarding to the container's port 3000.
Important: Dev servers inside the container must bind to
0.0.0.0, not127.0.0.1. Many tools (Vite, Next.js, etc.) default to localhost only, which is unreachable from outside the container. Use--host 0.0.0.0or equivalent option when starting the server. If Claude Code starts a dev server, instruct it to bind to0.0.0.0(e.g., via project CLAUDE.md).
Without port forwarding, host services are also available via runtime-specific hostnames:
| Runtime | Hostname |
|---|---|
| Podman | host.containers.internal |
| Docker | host.docker.internal |
Use --ssh mode to forward your host SSH agent (including 1Password) into the container. This works on all platforms by running sshd inside the container and connecting via ssh -A:
cclaude --sshThis enables git clone, git push, and other SSH operations inside the container using your host's SSH keys — without copying keys into the container.
How it works: The container starts sshd, your host's SSH public key is injected for authentication, and cclaude connects via ssh -A which forwards the agent. On exit, the container is automatically stopped and removed.
Requirements: An SSH public key in ~/.ssh/ (ed25519, RSA, or ECDSA).
Default mode (without --ssh): On Linux, SSH_AUTH_SOCK is mounted directly into the container (faster, no sshd needed). On macOS, SSH agent is not available in default mode due to VM limitations — use --ssh instead.
The default image includes Go, Node.js, and Python (uv). To add more toolchains:
- Edit
~/.config/cclaude/Dockerfile - Add your packages (e.g., Rust, Java, Ruby)
- Rebuild:
cclaude --build
# Append to Dockerfile
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"RUN apt-get update && apt-get install -y --no-install-recommends \
temurin-21-jdk \
&& rm -rf /var/lib/apt/lists/*src/cclaude.sh ← source (not executable)
dist/cclaude ← build output (make build)
Dockerfile ← container image definition
config.toml.example ← default config template
npmrc.example ← npm security hardening template
test/ ← BATS test suite
- ShellCheck — static analysis for the bash script
- bats-core — test framework
# macOS
brew install shellcheck bats-core
# Debian/Ubuntu
apt install shellcheck batsmake build # build src/cclaude.sh → dist/cclaude (with version injection)
make install # install to /usr/local/bin
make image-build # build container image
make test # shellcheck + BATS tests
make lint # shellcheck only
make clean # remove dist/Volume mounts through a Linux VM may have slower I/O than native Linux. This is a known limitation of Docker Desktop and Podman Machine on macOS. For large projects, consider using native Linux.
When using Docker (not Podman), files created inside the container may be owned by root on the host. Podman's rootless mode maps container root to your host user, avoiding this issue. If this is a problem, you can fix ownership after exiting:
sudo chown -R "$(id -u):$(id -g)" .When using Podman, --security-opt label=disable is automatically applied to allow bind-mounted volumes to work under SELinux.
In --ssh mode, sshd runs inside the container and listens on a random high port (49152–65535) bound to 127.0.0.1 only. Access is protected by an ephemeral ed25519 key pair that is generated per session and destroyed on exit. No password authentication is allowed.
Note: other users on the same host cannot connect because they do not possess the ephemeral private key, but the port is technically reachable from 127.0.0.1. If this is a concern in shared-host environments, consider additional network isolation.
This project is licensed under the MIT License.