Run Claude Code in a sandboxed Docker container with network restrictions, persistent config, and batteries-included dev tooling.
Built with this setup and running in production: growity.ai and egorsky.com.
Running an AI coding agent with full shell access on your host machine is a trust exercise. This project puts Claude Code inside a locked-down container where:
- Claude state survives restarts — credentials, project memory, conversation history, and settings are mounted from the host (
./node/), so you can rebuild or restart the container without losing context. - Network egress is restricted — only GitHub, npm, Anthropic APIs, and a handful of other services are reachable. Everything else is blocked by iptables.
- Your host filesystem is isolated — only the workspace directory you choose gets mounted.
- The environment is reproducible — same tools, same versions, every time.
# Build the image and run interactively
make docker
# Or run in the background
make docker.run.dOn startup the container launches Claude Code directly. A cl alias is also available in the shell for subsequent runs.
| Category | Tools |
|---|---|
| Runtime | Node.js 20, Go 1.24.4 |
| AI | Claude Code CLI (@anthropic-ai/claude-code) |
| Shell | Zsh with Oh My Zsh + Powerlevel10k, Bash |
| Git | Git, delta (better diffs), GitHub CLI (gh) |
| Editors | nano, vim |
| DB | PostgreSQL client (psql) |
| Utilities | jq, fzf, make, unzip, gow (Go watcher) |
| Network | iptables, ipset, dig, ping |
Environment variables are loaded in order by the Makefile:
.env.default.properties # checked in — sensible defaults
.env.properties # git-ignored — your local overrides
You can point to a different config folder:
CONF=./conf/prod make docker.run| Variable | Default | Description |
|---|---|---|
WORKSPACE |
/workspace |
Container workspace path |
WORKSPACE_DIR |
(empty) | Host subdirectory to mount as the workspace |
DOCKER_LABEL |
claude |
Docker image label for tagging |
ANTHROPIC_API_KEY |
— | Your Anthropic API key |
ANTHROPIC_BASE_URL |
— | Custom API endpoint (e.g. for proxies) |
Set secrets in .env.properties (never commit this file):
ANTHROPIC_API_KEY=sk-ant-...The container runs an iptables-based firewall (init-firewall.sh) that drops all outbound traffic by default and only allows connections to:
- GitHub — API, web, and git protocol (IPs fetched dynamically from GitHub's
/metaendpoint) - npm —
registry.npmjs.org - Anthropic —
api.anthropic.com,statsig.anthropic.com - VS Code — marketplace and update servers
- Other — Sentry, Statsig, DNS (UDP 53), SSH (TCP 22), localhost, host network
The firewall self-verifies on startup: it confirms that example.com is unreachable and api.github.com is reachable. The container requires NET_ADMIN and NET_RAW capabilities.
Open this repo in VS Code and select Reopen in Container to get a fully configured dev environment with:
- Claude Code, ESLint, Prettier, and GitLens extensions
- Format-on-save with Prettier
- Zsh as default terminal
- Persistent bash history and Claude config across rebuilds
- Firewall initialized automatically on start
See devcontainer.json for details.
Mount different project directories using the shortcut targets:
make back # mounts ../back
make front # mounts ../front
make docs # mounts ../docs
make markups # mounts ../markupsOr mount any directory:
WORKSPACE_DIR=../my-project make docker.runmake docker # build + clean + run (default)
make docker.build # build the image (tagged with git SHA)
make docker.run # interactive run
make docker.run.d # detached run
make docker.clean # remove untagged project images
make docker.ls # list project images
make help # show all available commands
For personal shortcuts that shouldn't be committed, create a local.makefile:
# DO NOT EVER INCLUDE IN GIT
# local.makefile — personal shortcuts, not tracked by the project makefile.
#
# Since Make only reads `makefile` (or `Makefile`) by default, you need to
# tell it about this file explicitly. The easiest way is a tiny shell helper:
#
# mk() { make --makefile=local.makefile "$@"; }
#
# Drop that line in your ~/.bashrc or ~/.zshrc, then use it like:
#
# mk back # mount ../back and start Claude Code
# mk front # mount ../front
# mk docs # mount ../docs
#
# You can also invoke it directly without the helper:
#
# make -f local.makefile back
back:
$(MAKE) docker.run WORKSPACE_DIR=back
front:
$(MAKE) docker.run WORKSPACE_DIR=front
ceo:
$(MAKE) docker.run