Claude Code Containers Guide
Overview
This comprehensive guide covers running Claude Code in containerized environments, from basic Docker setups to production Kubernetes deployments. While there isn’t an official Docker Hub image, this guide provides battle-tested configurations based on the official development container and community implementations.
Table of Contents
- Quick Start
- Docker Configuration
- DevContainer Setup
- Docker Compose
- Kubernetes Deployment
- Security Best Practices
- TypeScript Development
- Community Implementations
- Troubleshooting
Quick Start
Prerequisites
- Docker Desktop installed and running
- VS Code with Remote-Containers extension
- Anthropic API key
- Node.js 20+ (for local development)
Basic Docker Setup
# Create a simple Dockerfile
cat > Dockerfile << 'EOF'
FROM node:20-slim
WORKDIR /app
RUN npm install -g @anthropic-ai/claude-code
ENV ANTHROPIC_API_KEY=your_api_key_here
CMD ["claude-code", "--no-interactive"]
EOF
# Build and run
docker build -t my-claude-code .
docker run -it --rm \
-v $(pwd):/app \
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
my-claude-codeDocker Configuration
Basic Dockerfile
# Simple container setup
FROM node:20-alpine
WORKDIR /app
RUN npm install -g @anthropic-ai/claude-code
ENV ANTHROPIC_API_KEY=""
CMD ["claude-code", "--help"]Production-Ready Dockerfile
# Multi-stage build for optimized images
FROM node:20-alpine AS base
RUN apk add --no-cache \
git \
openssh-client \
ca-certificates
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM base AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM base AS runtime
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=build --chown=nodejs:nodejs /app/dist ./dist
USER nodejs
EXPOSE 3000
CMD ["node", "dist/index.js"]Environment Variables
# Required
ANTHROPIC_API_KEY=your_api_key_here
# Optional
CLAUDE_CODE_ENV=development|production
DEVCONTAINER=true
NODE_ENV=production
CLAUDE_CODE_SAFE_MODE=true
CLAUDE_CODE_PERMISSION_MODE=strictDevContainer Setup
Complete devcontainer.json
{
"name": "TypeScript Development Container",
"build": {
"dockerfile": "Dockerfile",
"args": {
"VARIANT": "20-bookworm",
"TZ": "${localEnv:TZ:America/Los_Angeles}"
}
},
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0.5": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "20",
"nodeGypDependencies": true
},
"ghcr.io/devcontainers-contrib/features/typescript:2": {
"version": "latest"
}
},
"customizations": {
"vscode": {
"extensions": [
"anthropic.claude-code",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"eamodio.gitlens",
"ms-vscode.vscode-typescript-next",
"orta.vscode-jest",
"ZixuanChen.vitest-explorer"
],
"settings": {
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.suggest.autoImports": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
}
},
"forwardPorts": [3000, 5173, 8080],
"postCreateCommand": "npm install && npm run prepare",
"remoteUser": "node",
"runArgs": [
"--cap-add=NET_ADMIN",
"--cap-add=NET_RAW",
"--security-opt=no-new-privileges"
],
"mounts": [
"source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind,consistency=cached",
"source=claude-bashhistory,target=/commandhistory,type=volume"
],
"workspaceFolder": "/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached"
}Security-Focused Dockerfile for DevContainers
FROM mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm
# Install security tools and CLI
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends \
iptables \
ipset \
dnsutils \
netcat-openbsd \
sudo \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install the tool globally
RUN npm install -g @anthropic-ai/claude-code@latest
# Create firewall initialization script
COPY init-firewall.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/init-firewall.sh
# Configure sudo for node user
RUN echo "node ALL=(ALL) NOPASSWD: /usr/local/bin/init-firewall.sh" >> /etc/sudoers.d/node
# Switch to node user
USER node
# Initialize firewall on container start
ENTRYPOINT ["sudo", "/usr/local/bin/init-firewall.sh", "&&", "/bin/bash"]Docker Compose
Development Environment
version: '3.8'
services:
claude-code:
build:
context: .
dockerfile: Dockerfile
target: development
container_name: claude-code-dev
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- NODE_ENV=development
- DEVCONTAINER=true
volumes:
- .:/workspace:cached
- node_modules:/workspace/node_modules
- claude-config:/home/node/.claude
- claude-history:/home/node/.history
networks:
- claude-net
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_ADMIN
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: claude
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- claude-net
volumes:
node_modules:
claude-config:
claude-history:
postgres-data:
networks:
claude-net:
driver: bridgeProduction Compose
version: '3.8'
services:
claude-code:
image: claude-code:production
container_name: claude-code-prod
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
volumes:
- ./workspace:/workspace:ro
networks:
- isolated
security_opt:
- no-new-privileges:true
- apparmor:docker-default
cap_drop:
- ALL
read_only: true
user: "1000:1000"
tmpfs:
- /tmp
- /var/run
networks:
isolated:
driver: bridge
internal: true # No external network accessKubernetes Deployment
Production Deployment Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: claude-code-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: claude-code
template:
metadata:
labels:
app: claude-code
spec:
serviceAccountName: claude-code-sa
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: claude-code
image: myregistry/claude-code:latest
ports:
- containerPort: 3000
env:
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: claude-secrets
key: api-key
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/.cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: claude-code-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: claude-code-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60Security Best Practices
1. Secrets Management
// secrets-manager.ts
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
export class SecureConfig {
private client: SecretsManagerClient;
constructor() {
this.client = new SecretsManagerClient({ region: process.env.AWS_REGION });
}
async getApiKey(): Promise<string> {
const command = new GetSecretValueCommand({
SecretId: "claude-code/api-key",
});
const response = await this.client.send(command);
return response.SecretString || "";
}
}
// Usage in container
const config = new SecureConfig();
const apiKey = await config.getApiKey();
process.env.ANTHROPIC_API_KEY = apiKey;2. Network Policies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: claude-code-network-policy
spec:
podSelector:
matchLabels:
app: claude-code
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production
ports:
- protocol: TCP
port: 3000
egress:
- to:
- namespaceSelector:
matchLabels:
name: production
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
app: postgres
- to:
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 53
- protocol: UDP
port: 533. Container Security Scanning
# .github/workflows/security-scan.yml
name: Container Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t claude-code:${{ github.sha }} .
- name: Run Trivy scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: claude-code:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'TypeScript Development
TypeScript Project Structure
claude-code-typescript/
├── .devcontainer/
│ ├── devcontainer.json
│ └── Dockerfile
├── src/
│ ├── index.ts
│ ├── services/
│ │ ├── claude.service.ts
│ │ └── container.service.ts
│ └── types/
│ └── claude.types.ts
├── tests/
│ ├── unit/
│ └── integration/
├── docker-compose.yml
├── Dockerfile
├── tsconfig.json
├── vitest.config.ts
└── package.json
TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"types": ["node", "vitest/globals"],
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@services/*": ["src/services/*"],
"@types/*": ["src/types/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "coverage"]
}Claude Service Implementation
// src/services/claude.service.ts
import { ClaudeCode } from '@anthropic-ai/claude-code';
import { Container } from 'dockerode';
export interface ClaudeContainerOptions {
apiKey: string;
workspaceMount?: string;
networkMode?: 'bridge' | 'host' | 'none';
securityOpts?: string[];
}
export class ClaudeContainerService {
private docker: Docker;
private container?: Container;
constructor(private options: ClaudeContainerOptions) {
this.docker = new Docker();
}
async start(): Promise<void> {
const containerOptions = {
Image: 'claude-code:latest',
Env: [
`ANTHROPIC_API_KEY=${this.options.apiKey}`,
'NODE_ENV=production'
],
HostConfig: {
Binds: this.options.workspaceMount ?
[`${this.options.workspaceMount}:/workspace`] : [],
NetworkMode: this.options.networkMode || 'bridge',
SecurityOpt: this.options.securityOpts || [
'no-new-privileges:true'
],
CapDrop: ['ALL'],
CapAdd: ['NET_ADMIN']
}
};
this.container = await this.docker.createContainer(containerOptions);
await this.container.start();
}
async execute(command: string): Promise<string> {
if (!this.container) {
throw new Error('Container not started');
}
const exec = await this.container.exec({
Cmd: ['claude-code', ...command.split(' ')],
AttachStdout: true,
AttachStderr: true
});
const stream = await exec.start({ hijack: true });
return new Promise((resolve, reject) => {
let output = '';
stream.on('data', (chunk: Buffer) => {
output += chunk.toString();
});
stream.on('end', () => resolve(output));
stream.on('error', reject);
});
}
async stop(): Promise<void> {
if (this.container) {
await this.container.stop();
await this.container.remove();
}
}
}Testing with Vitest
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import path from 'path';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['node_modules/', 'dist/']
},
setupFiles: ['./tests/setup.ts']
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@services': path.resolve(__dirname, './src/services'),
'@types': path.resolve(__dirname, './src/types')
}
}
});// tests/integration/claude-container.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { ClaudeContainerService } from '@services/claude.service';
describe('Claude Container Integration', () => {
let service: ClaudeContainerService;
beforeAll(async () => {
service = new ClaudeContainerService({
apiKey: process.env.ANTHROPIC_API_KEY!,
workspaceMount: './test-workspace',
networkMode: 'none'
});
await service.start();
});
afterAll(async () => {
await service.stop();
});
it('should execute Claude commands in container', async () => {
const result = await service.execute('--version');
expect(result).toContain('claude-code');
});
it('should handle file operations securely', async () => {
const result = await service.execute('read test.ts');
expect(result).toBeDefined();
});
});Community Implementations
1. claude-docker (VishalJ99)
A comprehensive Docker wrapper with GPU support:
# Installation
git clone https://github.com/VishalJ99/claude-docker
cd claude-docker
./install.sh
# Usage
claude-docker --continue # Resume conversation
claude-docker --rebuild # Rebuild image
claude-docker --gpus all # GPU support2. ClaudeBox (RchGrav)
Pre-configured development profiles with project isolation:
- Multiple instance support
- Security features with network isolation
- Development profile management
3. claude-code Container (Zeeno-atl)
# Pull and run
docker pull ghcr.io/Zeeno-atl/claude-code:latest
docker run -it --rm \
-v "$(pwd):/app" \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
ghcr.io/Zeeno-atl/claude-code:latestWorkshop Examples
Example 1: Basic Container Setup
# Workshop Exercise 1: Basic Container
# Goal: Create and run a simple container
# Step 1: Create Dockerfile
cat > Dockerfile << 'EOF'
FROM node:20-alpine
WORKDIR /workspace
RUN npm install -g @anthropic-ai/claude-code
COPY . .
CMD ["claude-code", "--help"]
EOF
# Step 2: Build the container
docker build -t workshop-claude .
# Step 3: Run with API key
docker run -it --rm \
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
workshop-claude
# Discussion points:
# - Why use Alpine Linux?
# - Security implications of root user
# - Volume mounting strategiesExample 2: TypeScript Development Container
// Workshop Exercise 2: TypeScript Service
// Goal: Build a service with TypeScript
// src/workshop-service.ts
import { spawn } from 'child_process';
export class WorkshopClaudeService {
private apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async analyzeCode(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
const claude = spawn('claude-code', [
'analyze',
filePath
], {
env: {
...process.env,
ANTHROPIC_API_KEY: this.apiKey
}
});
let output = '';
claude.stdout.on('data', (data) => {
output += data.toString();
});
claude.on('close', (code) => {
if (code === 0) {
resolve(output);
} else {
reject(new Error(`Process exited with code ${code}`));
}
});
});
}
}
// Usage example
async function workshop() {
const service = new WorkshopClaudeService(process.env.ANTHROPIC_API_KEY!);
const analysis = await service.analyzeCode('./src/index.ts');
console.log('Code Analysis:', analysis);
}Example 3: Kubernetes Workshop
# Workshop Exercise 3: Deploy to Kubernetes
# Goal: Deploy app to local k8s cluster
# Step 1: Create namespace
apiVersion: v1
kind: Namespace
metadata:
name: workshop
---
# Step 2: Create secret
apiVersion: v1
kind: Secret
metadata:
name: claude-secret
namespace: workshop
type: Opaque
data:
api-key: <base64-encoded-api-key>
---
# Step 3: Deploy application
apiVersion: apps/v1
kind: Deployment
metadata:
name: claude-workshop
namespace: workshop
spec:
replicas: 1
selector:
matchLabels:
app: claude-workshop
template:
metadata:
labels:
app: claude-workshop
spec:
containers:
- name: app
image: workshop-claude:latest
env:
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: claude-secret
key: api-key
resources:
limits:
memory: "1Gi"
cpu: "500m"Example 4: Security Workshop
// Workshop Exercise 4: Secure Container Practices
// Goal: Implement security best practices
// secure-container.ts
import { readFileSync } from 'fs';
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
export class SecureClaudeContainer {
private algorithm = 'aes-256-gcm';
private key: Buffer;
constructor() {
// In production, load from secure key management
this.key = randomBytes(32);
}
encryptApiKey(apiKey: string): { encrypted: string; iv: string; tag: string } {
const iv = randomBytes(16);
const cipher = createCipheriv(this.algorithm, this.key, iv);
let encrypted = cipher.update(apiKey, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex')
};
}
decryptApiKey(encryptedData: { encrypted: string; iv: string; tag: string }): string {
const decipher = createDecipheriv(
this.algorithm,
this.key,
Buffer.from(encryptedData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encryptedData.tag, 'hex'));
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
// Workshop discussion:
// 1. Why encrypt API keys at rest?
// 2. Key rotation strategies
// 3. Hardware security modules (HSM)
// 4. Kubernetes secrets vs external secret managersTroubleshooting
Common Issues and Solutions
1. Container Fails to Start
# Check Docker logs
docker logs claude-code-container
# Common solutions:
# - Verify API key is set correctly
# - Check file permissions
# - Ensure port isn't already in use
# - Verify Docker daemon is running2. Network Connectivity Issues
# Test DNS resolution
docker exec claude-code-container nslookup api.anthropic.com
# Check firewall rules
docker exec claude-code-container iptables -L -v -n
# Verify allowed domains in init-firewall.sh3. TypeScript Build Failures
// Common TypeScript issues in containers
// Issue: Module resolution fails
// Solution: Use explicit paths in tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// Issue: Memory limits in container
// Solution: Set Node.js memory limits
ENV NODE_OPTIONS="--max-old-space-size=4096"
// Issue: Build cache invalidation
// Solution: Use Docker build cache mounts
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=production4. Kubernetes Deployment Issues
# Debug pod issues
kubectl describe pod claude-code-pod -n production
kubectl logs claude-code-pod -n production
# Common issues:
# - ImagePullBackOff: Check registry credentials
# - CrashLoopBackOff: Check application logs
# - Pending: Check resource requests/limits
# - OOMKilled: Increase memory limitsPerformance Optimization
# Optimize container startup time
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=production
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["dist/index.js"]Quick Reference
Essential Docker Commands
# Build image
docker build -t claude-code .
# Run interactively
docker run -it --rm \
-v $(pwd):/workspace \
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
claude-code
# Run with compose
docker-compose up -d
# Access container
docker exec -it claude-code /bin/shSecurity Checklist
- ✅ Run as non-root user
- ✅ Drop all capabilities, add only required
- ✅ Use read-only filesystem where possible
- ✅ Network isolation for production
- ✅ Scan images for vulnerabilities
- ✅ Use secrets management for API keys
References and Resources
Official Documentation
Community Resources
Related Documentation
- TypeScript SDK Guide - For custom container applications
- Multi-Agent Systems - Scale with containers
- Security Patterns - Container security best practices
Summary
This guide provides comprehensive coverage of running the tool in containers:
- Docker: From basic setups to production-ready multi-stage builds
- DevContainers: Full VS Code integration with all features
- Kubernetes: Scalable deployments with security and monitoring
- Security: Best practices for API key management and network isolation
- TypeScript: Building custom applications with the SDK
Whether you’re developing locally or deploying to production, these configurations provide a solid foundation for containerized environments.