Claude Code in Docker
Set up isolated Docker environments for running Claude Code safely and consistently.
What you'll accomplish
Run Claude Code in Docker containers with proper configuration and volume mounting.
Prerequisites
- Docker installed and working
- Understanding of permissions (lesson 3)
- Basic Docker knowledge
Why Use Docker with Claude Code?
Running Claude Code in Docker provides:
| Benefit | Description |
|---|---|
| Isolation | Changes can't affect your system |
| Reproducibility | Same environment everywhere |
| Safety | Use --dangerously-skip-permissions safely |
| Clean slate | Fresh environment each time |
| Team consistency | Everyone runs the same setup |
Basic Setup
Simple Dockerfile
Start with a minimal configuration:
dockerfile# Dockerfile FROM node:20-slim # Install git (needed for most projects) RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* # Install Claude Code RUN npm install -g @anthropic-ai/claude-code # Set up workspace WORKDIR /workspace # Run as non-root for security RUN useradd -m -s /bin/bash claude && chown -R claude:claude /workspace USER claude # Default command CMD ["claude"]
Build and Run
bash# Build the image docker build -t claude-code-dev . # Run interactively docker run -it \ -v $(pwd):/workspace \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ claude-code-dev
Full-Featured Setup
A production-ready Dockerfile with more tools:
dockerfile# Dockerfile FROM node:20-bookworm # System dependencies RUN apt-get update && apt-get install -y \ git \ curl \ jq \ ripgrep \ fd-find \ && rm -rf /var/lib/apt/lists/* # Install Claude Code RUN npm install -g @anthropic-ai/claude-code # Install common global tools RUN npm install -g \ typescript \ prettier \ eslint # Create non-root user RUN useradd -m -s /bin/bash claude RUN mkdir -p /home/claude/.claude && chown -R claude:claude /home/claude # Set up workspace WORKDIR /workspace RUN chown claude:claude /workspace # Switch to non-root user USER claude # Default environment ENV NODE_ENV=development # Default to interactive claude CMD ["claude", "--dangerously-skip-permissions"]
Note
Notice --dangerously-skip-permissions in the CMD. This is safe because we're in an isolated container.
Docker Compose
For complex setups, use Docker Compose:
yaml# docker-compose.yml version: '3.8' services: claude: build: . volumes: - ./project:/workspace - claude-cache:/home/claude/.claude environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} stdin_open: true tty: true volumes: claude-cache:
Running
bash# Start Claude Code docker-compose run claude # With a specific prompt docker-compose run claude claude -p "Fix the linting errors" # Build fresh docker-compose build --no-cache
Volume Mounting Strategies
Project Only (Recommended)
Mount only your project directory:
bashdocker run -it \ -v ./my-project:/workspace \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ claude-code-dev
Pros: Minimal access, maximum safety Cons: Can't access other projects
Named Volumes for Cache
Persist Claude's cache between runs:
bashdocker run -it \ -v ./my-project:/workspace \ -v claude-cache:/home/claude/.claude \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ claude-code-dev
Pros: Faster subsequent runs, settings persist Cons: Slightly more complex
Read-Only Mounts
Prevent Claude from modifying certain paths:
bashdocker run -it \ -v ./my-project:/workspace \ -v ./reference:/reference:ro \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ claude-code-dev
The :ro makes /reference read-only.
Including Configuration Files
Method 1: Copy in Dockerfile
dockerfile# Copy global settings COPY --chown=claude:claude .claude/ /home/claude/.claude/ # Copy project settings COPY --chown=claude:claude AGENTS.md /workspace/
Method 2: Mount at Runtime
bashdocker run -it \ -v ./project:/workspace \ -v ~/.claude:/home/claude/.claude \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ claude-code-dev
Method 3: Environment Variables
bashdocker run -it \ -v ./project:/workspace \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ -e CLAUDE_CODE_MODEL=claude-opus-4-5-20251101 \ claude-code-dev
Multi-Stage Build for Smaller Images
Reduce image size with multi-stage builds:
dockerfile# Build stage FROM node:20-bookworm AS builder RUN npm install -g @anthropic-ai/claude-code # Runtime stage FROM node:20-slim # Copy only what we need COPY --from=builder /usr/local/lib/node_modules /usr/local/lib/node_modules COPY --from=builder /usr/local/bin/claude /usr/local/bin/claude # Minimal runtime dependencies RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* WORKDIR /workspace RUN useradd -m claude && chown claude:claude /workspace USER claude CMD ["claude", "--dangerously-skip-permissions"]
Development Workflow
1. Create Project-Specific Docker Setup
In your project:
my-project/
├── docker/
│ ├── Dockerfile
│ └── docker-compose.yml
├── src/
├── package.json
└── AGENTS.md
2. Add Helper Scripts
bash#!/bin/bash # scripts/claude-docker.sh docker run -it \ -v "$(pwd)":/workspace \ -v claude-cache:/home/claude/.claude \ -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \ claude-code-dev "$@"
Make it executable:
bashchmod +x scripts/claude-docker.sh
Use it:
bash./scripts/claude-docker.sh # Interactive ./scripts/claude-docker.sh -p "Run tests" # With prompt
3. Add npm Script
json{ "scripts": { "claude": "docker-compose run claude", "claude:build": "docker-compose build" } }
Now use npm run claude.
CI/CD Integration
GitHub Actions
yaml# .github/workflows/claude-fix.yml name: Auto-fix with Claude on: workflow_dispatch: inputs: prompt: description: 'What should Claude do?' required: true default: 'Fix the linting errors' jobs: claude-fix: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Claude Code run: | docker run --rm \ -v ${{ github.workspace }}:/workspace \ -e ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }} \ claude-code-dev \ claude --dangerously-skip-permissions -p "${{ github.event.inputs.prompt }}" - name: Commit changes run: | git config user.name "Claude Code" git config user.email "[email protected]" git add -A git diff --staged --quiet || git commit -m "Auto-fix by Claude Code" git push
GitLab CI
yaml# .gitlab-ci.yml claude-review: image: claude-code-dev script: - claude --dangerously-skip-permissions -p "Review the code and suggest improvements" variables: ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY
Security Considerations
1. Never Mount Sensitive Directories
bash# DANGEROUS - don't do this docker run -it -v ~/:/workspace claude-code-dev # DANGEROUS - gives access to credentials docker run -it -v ~/.ssh:/home/claude/.ssh claude-code-dev
2. Use Secrets Management
yaml# docker-compose.yml with Docker secrets services: claude: build: . secrets: - anthropic_key environment: - ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_key secrets: anthropic_key: file: ./secrets/anthropic_key.txt
3. Network Isolation
Limit network access if Claude doesn't need it:
bashdocker run -it \ --network=none \ # No network access -v ./project:/workspace \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ claude-code-dev
Or restrict to specific hosts:
yamlservices: claude: networks: - claude-net networks: claude-net: driver: bridge
4. Resource Limits
Prevent runaway processes:
bashdocker run -it \ --memory=4g \ --cpus=2 \ -v ./project:/workspace \ -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ claude-code-dev
Common Issues
Frequently Asked Questions
On Linux, pass your user/group IDs:
bashdocker run -it --user $(id -u):$(id -g) ...
Or in Docker Compose:
yamlservices: claude: user: "${UID}:${GID}"
Make sure git is installed in the container and the .git folder is mounted:
bashdocker run -it -v ./project:/workspace claude-code-dev # The .git folder should be inside ./project
Changes in the container are lost unless you:
- Mount a volume for that directory
- Commit the container to a new image
- Use named volumes for persistence
Always mount your project directory as a volume.
Use multi-stage builds and slim base images:
dockerfileFROM node:20-slim # Instead of node:20
Clean up after apt-get:
dockerfileRUN apt-get update && apt-get install -y git \ && rm -rf /var/lib/apt/lists/*
Check:
- Environment variable is passed:
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY - Variable is set in your shell:
echo $ANTHROPIC_API_KEY - No quotes issues: use
"$ANTHROPIC_API_KEY"not'$ANTHROPIC_API_KEY'
Verify Your Understanding
You've completed this lesson if you can:
Next: Advanced MCP integration for connecting Claude to external tools.