An isolated container environment for running Claude Code on Apple's container runtime, with persistent state and support for multiple concurrent sessions.
This project builds a Debian-based container image and a launcher script for running Claude Code — including in YOLO mode (--dangerously-skip-permissions) — inside a VM-isolated container instead of directly on your host.
Built for Apple's container runtime; adaptable to Docker/Podman with modifications.
Isolation note: There is no network egress filtering — the container has unrestricted network access. Isolation comes from the VM boundary that Apple's
containerruntime provides. (An earlier host-sidepffirewall experiment was abandoned and is not part of this project.)
- Concurrent sessions — each launch gets its own numbered container (
claude-dev-1,claude-dev-2, …), so multiple Claude Code sessions can run side by side. - Persistent state — home directory, shell history, and per-repo
node_modulessurvive container restarts. - Pre-configured environment — git, gh, lazygit, micro, vim, zsh + powerlevel10k, fzf, tmux, Node 20, and Claude Code installed and ready.
- Single-script management — build, launch, and clean up with
claude-dev.sh.
Builds the claude:latest image:
debian:trixie-slimbase, timezoneAmerica/Chicago.- Dev tools:
git,gh,lazygit,micro,vim,zsh,bash,fzf,tmux,jq, pluscurl/wget/gnupg2/unzip/sudo/less/procps/man-db. - Node.js 20 unpacked from a tarball the build script downloads and copies in.
- Non-root
claudeuser with passwordlesssudo.
On first run, the entrypoint provisions the rest into the (persistent) home volume so it survives across containers without rebuilding the image:
- Installs Claude Code (
claude.ai/install.sh), Python 3,uv, and powerlevel10k if absent. - Seeds
~/.zshrc,~/.tmux.conf, and~/.claude/settings.jsonif not already present.
The launcher. Running it with no arguments:
- Builds
claude:latestif the image doesn't exist (downloading the Node tarball first). - Picks the next free container slot (
claude-dev-N). - Starts a
--rmcontainer with the current directory mounted at/workspace.
Flags:
| Flag | Effect |
|---|---|
--build |
Force-rebuild the image even if claude:latest exists. |
--prune-volumes |
Remove this repo's node_modules volumes that aren't held by a running container, then exit. |
-h, --help |
Show usage. |
Mounts:
| Host | Container | Purpose |
|---|---|---|
shared-home/ |
/home/claude |
Persistent home (configs, Claude/gh/wrangler creds). |
shared-history/ |
/commandhistory |
Persistent shell history. |
$(pwd) |
/workspace |
The repo you launch from. |
claude-<repo>-node_modules[-N] (named volume) |
/workspace/node_modules |
Per-repo node_modules, isolated from the host checkout. |
Containers get 8 GB memory, 4 CPUs, and join the claude network.
node_modules lives on its own ext4 block volume rather than the /workspace bind mount — this keeps Linux-native binaries out of your macOS checkout and speeds up installs. Because a block volume can't be attached to two live VMs at once, each container slot gets its own volume: slot 1 reuses the unnumbered claude-<repo>-node_modules, and concurrent slots get -2, -3, … (each needs a one-time npm install). --prune-volumes reclaims the numbered ones once their containers exit.
container builder start -m 8G # 4G also works; stop the builder afterward
./claude-dev.sh --build # downloads Node, builds claude:latest(Or just run ./claude-dev.sh — it builds automatically if the image is missing.)
./claude-dev.shRun it again from another terminal to start a second concurrent session.
Inside the container:
IS_SANDBOX=1 claude --dangerously-skip-permissions./claude-dev.sh --prune-volumes # run from the repo whose volumes you want to cleanshared-home/andshared-history/are host directories that persist live credentials (Claude,gh,wrangler,supabase, and anything in your shell history). They are gitignored — do not commit or share them, and don't zip the project directory for distribution. Share viagitonly.sudois passwordless inside the container for convenience; the VM boundary is the real isolation.- Network egress is unrestricted (see the isolation note above).
- Container resources: edit the
--memory/--cpusflags inclaude-dev.sh. - Image tools: add packages to the
apt-get installlist in theDockerfile. - First-run setup: edit the entrypoint heredoc in the
Dockerfile.
- Apple
containerruntime (or Docker/Podman with modifications) - Internet access for the image build and first-run tool installs
Adapted from Anthropic's reference .devcontainer:
debian:trixie-slimbase instead of a Node.js image (Node added from a tarball).- Claude Code installed at first run via the standalone installer, not baked in via npm.
- Adds lazygit, micro, powerlevel10k, tmux,
uv, and other personal preferences. - Numbered containers + per-repo
node_modulesvolumes for concurrent sessions. - No in-container firewall; relies on the runtime's VM isolation.
Provided as-is for personal use. Claude Code and related trademarks are property of Anthropic.
Based on Anthropic's reference .devcontainer, adapted for the Apple container runtime.