Ready-to-use Docker sandbox for Claude Code with Node.js (nvm) and Python (pyenv) pre-installed
9.2K
Dockerfile linksTags are prefixed with the release version at publish time (e.g.
0.7.0_node24_python3.14). See Build Matrix & Tagging for the full scheme.
node24.15.0_python3.13, node24_python3.13, node24.15_python3.13
node24.15.0_python3.11, node24_python3.11, node24.15_python3.11
node22.22.2_python3.13, node22_python3.13, node22.22_python3.13
node22.22.2_python3.11, node22_python3.11, node22.22_python3.11
node20.20.2_python3.13, node20_python3.13, node20.20_python3.13
node20.20.2_python3.11, node20_python3.11, node20.20_python3.11
A ready-to-use sandbox for running Claude Code inside Docker. It provides a non-root dev user with Node.js (via nvm), Python (via pyenv), and the Claude Code CLI pre-installed.
Debian 12 (Bookworm) slim — chosen over Alpine for glibc compatibility with pyenv-built Python and native npm packages.
| Argument | Default | Description |
|---|---|---|
USER_UID | 1000 | UID for the dev user |
USER_GID | 1000 | GID for the dev user |
NODE_VERSION | 24 | Node.js version installed via nvm |
PYTHON_VERSION | 3.14 | Python version installed via pyenv |
The entrypoint requires at least one credential to be set:
| Environment Variable | Source |
|---|---|
ANTHROPIC_API_KEY | console.anthropic.com/settings/keys |
CLAUDE_CODE_OAUTH_TOKEN | Claude Pro/Max subscription (claude login) |
If neither is set, the container exits with an error message.
1. System packages — apt-get (curl, git, vim, jq, build-essential, etc.)
2. Locale — en_US.UTF-8
3. Non-root user — dev (UID/GID from build args, handles GID collisions)
4. Workspace — /workspace owned by dev
5. Shell profiles — /etc/profile.d/nvm.sh, /etc/profile.d/pyenv.sh
6. Entrypoint — /entrypoint.sh (auth validation + exec)
7. nvm + Node.js — /home/dev/.nvm
8. pyenv + Python — /home/dev/.pyenv
9. Claude Code CLI — globally installed via npm
10. Bootstrap config — /home/dev/.claude.json (skips onboarding)
| Path | Purpose |
|---|---|
/workspace | Default working directory, mount your code here |
/home/dev/.nvm | nvm installation + Node.js versions |
/home/dev/.pyenv | pyenv installation + Python versions |
/home/dev/.claude.json | Bootstrap config (skips onboarding prompts) |
/entrypoint.sh | Auth gate — validates credentials then execs CMD |
/etc/profile.d/nvm.sh | Loads nvm in login shells |
/etc/profile.d/pyenv.sh | Loads pyenv in login shells |
ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN is set (non-empty)execs the CMD — signals (SIGTERM, SIGINT) pass directly to the processDefault CMD: bash -lc "claude --dangerously-skip-permissions"
Login shells (bash -l, docker exec -it ... bash -l) automatically source:
NVM_DIR, loads nvm and bash completionPYENV_ROOT, adds pyenv to PATH, runs pyenv initBoth scripts are safe to source when nvm/pyenv binaries are absent (no errors).
Defined in versions.json:
{
"node": ["24.15.0", "22.22.2", "20.20.2"],
"python": ["3.14", "3.13", "3.11"]
}
CI builds the Cartesian product (9 combinations). Tags follow the pattern:
{version}_node{N}_python{P}
The highest Node + highest Python combo also gets latest and the bare version tag.
Example tags for release 0.7.0:
0.7.0_node24.15.0_python3.14 <- also tagged: latest, 0.7.0
0.7.0_node24.15.0_python3.13
0.7.0_node24.15.0_python3.11
0.7.0_node22.22.2_python3.14
0.7.0_node22.22.2_python3.13
0.7.0_node22.22.2_python3.11
0.7.0_node20.20.2_python3.14
0.7.0_node20.20.2_python3.13
0.7.0_node20.20.2_python3.11
# Build locally
docker build -f image/Dockerfile \
--build-arg USER_UID=$(id -u) \
--build-arg USER_GID=$(id -g) \
-t claude-code-sandbox .
# Run with API key
docker run -it --rm \
-e ANTHROPIC_API_KEY=sk-ant-... \
-v $(pwd):/workspace \
spiriyu/claude-code-sandbox:latest
# Run with OAuth token
docker run -it --rm \
-e CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat... \
-v $(pwd):/workspace \
spiriyu/claude-code-sandbox:latest
# Drop into bash instead of Claude
docker run -it --rm \
-e ANTHROPIC_API_KEY=sk-ant-... \
-v $(pwd):/workspace \
spiriyu/claude-code-sandbox:latest bash -l
# Run tests
bash test/entrypoint.test.sh
bash test/profile.test.sh
Create a .env file with your credentials, then use this docker-compose.yml:
services:
claude-code-sandbox:
image: spiriyu/claude-code-sandbox:latest
stdin_open: true # docker run -i
tty: true # docker run -t
env_file: .env # loads ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN
volumes:
- .:/workspace
.env:
ANTHROPIC_API_KEY=sk-ant-api03-...
# Start Claude Code interactively
docker compose run --rm claude-code-sandbox
# Drop into bash instead
docker compose run --rm claude-code-sandbox bash -l
# Run a one-off command
docker compose run --rm claude-code-sandbox bash -lc "claude -p 'explain this project'"
To match your host user's UID/GID (avoids file permission issues on mounted volumes), build locally:
services:
claude-code-sandbox:
build:
context: .
dockerfile: image/Dockerfile
args:
USER_UID: ${UID:-1000}
USER_GID: ${GID:-1000}
stdin_open: true
tty: true
env_file: .env
volumes:
- .:/workspace
# Build with your UID/GID and start
UID=$(id -u) GID=$(id -g) docker compose run --rm claude-code-sandbox
Content type
Image
Digest
sha256:fa03b6891…
Size
452.1 MB
Last updated
6 months ago
docker pull spiriyu/claude-code-sandbox