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

  1. Quick Start
  2. Docker Configuration
  3. DevContainer Setup
  4. Docker Compose
  5. Kubernetes Deployment
  6. Security Best Practices
  7. TypeScript Development
  8. Community Implementations
  9. 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-code

Docker 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=strict

DevContainer 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: bridge

Production 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 access

Kubernetes 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: 60

Security 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: 53

3. 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 support

2. 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:latest

Workshop 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 strategies

Example 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 managers

Troubleshooting

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 running

2. 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.sh

3. 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=production

4. 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 limits

Performance 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/sh

Security 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

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.