Claude Code Sandbox - a secure Dev Container for Claude Code CLI
Claude Code is Anthropic’s official CLI tool for coding with Claude. It runs in your terminal, reads your files, writes code, runs commands. It has a flag called --dangerously-skip-permissions that gives it full access to your shell - no confirmation prompts, no restrictions. Very productive. The name tells you everything about the risk.
I wanted to use it without worrying about it installing random packages on my machine, curling arbitrary URLs, or accidentally exposing credentials to third-party services. No third-party wrappers, no agent frameworks - just the native Claude Code CLI inside a container with controlled network access.
So I built claude-code-sandbox - a Dev Container template that puts Claude Code CLI in a locked box with network-level isolation.
The stack
The whole thing is built on three standard tools:
- Docker - container isolation (the security layer)
- Dev Containers - VS Code’s native way to develop inside containers (also supported by JetBrains, Codespaces, and others)
- Claude Code CLI - Anthropic’s native CLI tool, installed directly via npm
The network control is handled by a Squid proxy running in a second container - config files with lists of allowed domains, IP/CIDR networks, and SSH hosts.
No frameworks. No SDKs. No wrappers around the API. No LangChain, no CrewAI, no custom orchestration. Just claude running in a container that can only reach the internet through a proxy you control.
The idea
One project = one VS Code workspace = one isolated Dev Container.
Each project gets its own pair of Docker containers - a workspace where Claude Code CLI runs, and a Squid proxy that controls network access. The workspace has zero direct internet access. Every outbound request goes through the proxy, which checks it against a domain allowlist. Anything not on the list is dropped.
Host machine
└── VS Code Dev Container
├── <name>-workspace (internal network, no internet)
│ ├── Claude Code CLI (--dangerously-skip-permissions)
│ ├── Playwright (headless Chromium)
│ └── /workspace ← your code, mounted from host
└── <name>-proxy (Squid, allowlist-only)
└── proxy/ (domains, networks, SSH hosts)
This makes --dangerously-skip-permissions much safer to use. Claude Code can do whatever it wants inside the container, but it can only reach the domains you explicitly allow. It’s not bulletproof (see limitations), but it significantly reduces the blast radius.
Why Dev Containers and not something else
I looked at various approaches - iptables rules, Docker-in-Docker, custom sandboxing tools, third-party agent platforms. They all added complexity I didn’t need.
Dev Containers are an open standard that VS Code supports natively via the Dev Containers extension. You open a folder, VS Code detects the .devcontainer/ config, and offers to reopen it in a container. No custom runtimes, no CLI tools to learn. JetBrains and GitHub Codespaces support the same spec.
The entire security model is just two things:
- Docker network isolation (the workspace can’t reach the internet directly)
- A Squid proxy with allowlists for domains, networks, and SSH hosts (default: deny all)
That’s it. No agent framework, no orchestration platform, no cloud service. Just Docker doing what Docker does.
Why not the official Anthropic devcontainer? It’s a solid starting point - and it’s what inspired this project. The reference setup uses iptables + ipset for network filtering, which works at the IP level: domains are resolved to IPs and pinned. In my experience, this can silently break when CDN providers (npm, GitHub, etc.) rotate their IPs. It also requires the NET_ADMIN capability for iptables manipulation.
I wanted hostname-level control via Squid (L7 proxy): allowlist by domain name, resolved per-request, human-readable access logs, and no elevated container capabilities. And I wanted it to run from plain docker compose - no devcontainer tooling required.
Per-project memory isolation
One thing that surprised me about Claude Code CLI is how it handles memory. When you tell it to “remember this”, it saves the information to ~/.claude/projects/<project-path>/memory/. Each project has its own memory - Claude remembers your decisions, conventions, and context specifically for that project.
In the sandbox, each project gets its own Docker volume (claude-state) for this data. Memory is never shared between projects. When you switch between projects, each Claude Code instance knows only its own context.
This is stored inside the container’s volume, not in your workspace files - so it doesn’t pollute your git repo.
How it works in practice
Setup a new project:
git clone https://github.com/MyKEms/claude-code-sandbox.git
cd claude-code-sandbox
./setup.sh ~/my-project
The wizard asks a few questions (git identity, SSH agent provider, workspace path) and generates a .env file. Then open the project folder in VS Code and use Cmd+Shift+P → “Dev Containers: Reopen in Container”.
Start Claude Code:
ccd # autonomous mode: claude --dangerously-skip-permissions
cc # interactive mode: claude (with permission prompts)
Use ccd for fully autonomous work (the container isolation limits the blast radius). Use cc if you prefer to approve each action manually - the network isolation protects you either way.
On first run, authenticate via OAuth. After that, the token persists across container restarts and rebuilds (stored in the Docker volume with a stable machine identity).
Manage the proxy:
# from the host:
./scripts/proxy-ctl.sh add .your-api.com # allow a domain (HTTP/HTTPS)
./scripts/proxy-ctl.sh allow 192.168.0.0/24 # allow an IP/subnet (all ports)
./scripts/proxy-ctl.sh add-ssh .bitbucket.org # allow SSH to a domain
./scripts/proxy-ctl.sh list # show all active rules
./scripts/proxy-ctl.sh test https://api.example.com
./scripts/proxy-ctl.sh logs
Three types of rules: domains (HTTP/HTTPS only, port 80/443), networks (IP/CIDR, all ports - useful for internal services on non-standard ports like 8080 or 3000), and SSH hosts (SSH CONNECT to specific domains - GitHub and GitLab are included by default). The default domain allowlist includes ~25 entries covering Anthropic APIs, npm, GitHub, system packages, and Playwright.
The setup works primarily on macOS (Intel + Apple Silicon) but also Windows (Docker Desktop + WSL2), and Linux. SSH agent forwarding is supported via 1Password, Bitwarden, or any custom Unix socket.
Running it on a remote server
The architecture also allows hosting everything on a single remote machine instead of your laptop - handy if you’d rather have one beefy server hosting all your project sandboxes. It’s the same Dev Containers workflow, just connected via VS Code Remote-SSH:
- SSH into the server, clone the template, and run
./setup.sh ~/my-projecton the server. - From your laptop, install the Remote-SSH extension and connect to the server.
- Open the project folder in VS Code (now connected to the server) and use
Cmd+Shift+P-> “Dev Containers: Reopen in Container”.
The catch: everything Docker mounts lives on the server, not on the laptop. That means ~/.claude/, ~/.ssh/, the SSH agent socket, and the proxy allowlist all need to be on the server. claude login runs on the server (or set ANTHROPIC_API_KEY in .env there), and SSH keys go on the server too - or forward the laptop’s agent with ForwardAgent yes if you want 1Password / Bitwarden authentication coming from your local machine. Same workflow once the file locations click.
Without VS Code: just docker compose
You don’t actually need VS Code for any of this either. The infrastructure is plain docker-compose.yml - the .devcontainer/ config is just glue for VS Code’s lifecycle hooks, nothing more. For anyone interested in a tmux / neovim minimalist setup, the architecture allows skipping VS Code entirely:
cd ~/my-project
bash scripts/preflight.sh # validate .env, Docker, container names
docker compose up -d # start proxy + workspace
docker exec -it <name>-workspace bash /scripts/setup-container.sh # one-time init: git, MCP, auth
docker exec -it <name>-workspace bash # drop into the workspace
ccd
Subsequent starts are just docker compose up -d and docker exec. You lose the VS Code welcome banner and editor extensions, but everything else - proxy isolation, Claude memory persistence, SSH agent mount, aliases, watchdog mode - works identically. Pairs naturally with the remote server scenario above for a fully headless setup.
What I use it for
I run multiple projects simultaneously - each with its own container, own proxy config, own Claude memory. One project might need access to a specific API, another might be pure offline code refactoring.
The typical workflow:
- Open the project in VS Code (always in the Dev Container, never on the host)
- Run
ccdin the terminal - Give Claude a task
- Walk away or work on something else
- Come back, review the changes
One rule: always work inside the Dev Container. Don’t run Claude Code CLI on your host for a project that has a Dev Container. Don’t mix host-side and container-side sessions. The container is the workspace - keep it clean, keep it isolated.
Commands I use daily
Claude Code CLI has a lot of slash commands. These are the ones I actually use:
Session management:
/plan- start here when you’re not sure how to approach a task. Claude drafts a plan before writing any code. Saves a lot of back-and-forth/rename my-feature- name the session so you can find it later/resume- pick up a previous session. Claude remembers the full context/branch(or/fork) - branch off the current conversation. Useful when you want to explore an alternative approach and come back to the main thread/compact- compress the conversation context. I run this every 10-15 messages. Keeps costs down and helps Claude stay focused on what matters
Workflow:
/loop- tell Claude to iterate on something (like fixing test failures) until it’s done. You set the goal and the exit condition, then watch/remote-control- control the session from your phone or from claude.ai. Useful when you step away from the desk and want to check progress or give a nudge
Model and input:
/model- switch models mid-session. Opus for complex architecture, Sonnet for fast iteration/voice- push-to-talk voice dictation. When you don’t feel like typing, just talk
MCP servers:
The sandbox comes with Playwright MCP pre-configured - headless Chromium for web scraping, testing, and browser automation. Claude can open pages, click buttons, fill forms, and take screenshots without you touching a browser.
You can add more MCP servers to /workspace/.mcp.json - any tool that speaks the Model Context Protocol works.
What it doesn’t do
This is not a multi-agent platform. There’s no agent coordination, no task queuing, no shared memory between projects. It’s one Claude Code CLI instance per project, working independently inside a container.
It also doesn’t protect against everything. As Anthropic’s own docs note: no devcontainer is completely immune to all attacks. With --dangerously-skip-permissions, a malicious project could exfiltrate anything accessible inside the container - including Claude Code credentials. The network proxy limits where data can go, but it doesn’t inspect the content. Use this with trusted repositories and monitor what Claude does.
More specifically: Claude has full access to your workspace files (that’s the point). Traffic to allowed domains isn’t filtered beyond the domain check. And if Docker itself has a vulnerability, the sandbox can be bypassed. The SECURITY.md in the repo covers the full scope and limitations.
Wipe levels
Two levels of cleanup when you need a fresh start:
| Level | What happens | Claude Memory |
|---|---|---|
--soft | Clears sessions and temp files | Kept |
--hard | Destroys containers, volumes, images | Lost |
Hard wipe destroys the claude-state volume - all memory, session history, and project data are permanently lost. The script warns you and shows how to inspect what’s there before proceeding.
Links
- Repository: github.com/MyKEms/claude-code-sandbox
- Security policy: SECURITY.md
- License: MIT