Claude Code Containerization Deep Dive

The complete guide to containerizing Claude Code applications: from local development with DevContainers to production Kubernetes deployments

Table of Contents

  1. Introduction
  2. Local Development with Containers
  3. Production Docker Images
  4. DevContainers for Team Development
  5. Kubernetes Orchestration
  6. Container Security Architecture
  7. CI/CD Pipeline Integration
  8. Performance Optimization
  9. Monitoring and Observability
  10. Troubleshooting Guide
  11. Migration Strategies
  12. Best Practices Summary

Introduction

Containerization has become the standard for deploying Claude Code applications, providing consistency across development, staging, and production environments. This comprehensive guide covers every aspect of containerizing Claude Code, from setting up local development environments to deploying at scale in Kubernetes.

Why Containerize Claude Code?

  • Consistency: Same environment across all stages of development
  • Isolation: Secure sandbox for Claude Code operations
  • Scalability: Easy horizontal scaling with orchestration
  • Portability: Deploy anywhere containers are supported
  • Version Control: Immutable infrastructure as code

Architecture Overview

graph TB
    subgraph "Development"
        DEV[DevContainer] --> BUILD[Build Pipeline]
    end
    
    subgraph "CI/CD"
        BUILD --> TEST[Test Environment]
        TEST --> SCAN[Security Scan]
        SCAN --> REG[Container Registry]
    end
    
    subgraph "Production"
        REG --> K8S[Kubernetes Cluster]
        K8S --> MON[Monitoring Stack]
        K8S --> SEC[Security Policies]
    end

Local Development with Containers

Quick Start Development Container

Create a development environment in minutes:

# Dockerfile.dev
FROM node:22-bookworm-slim
 
# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    jq \
    build-essential \
    python3 \
    && rm -rf /var/lib/apt/lists/*
 
# Install Claude Code CLI globally
RUN npm install -g @claude-ai/claude-code
 
# Create non-root user
RUN useradd -m -s /bin/bash claude
USER claude
WORKDIR /home/claude/app
 
# Install development tools
RUN npm install -g \
    typescript \
    tsx \
    nodemon \
    prettier \
    eslint
 
# Set up environment
ENV NODE_ENV=development
ENV CLAUDE_CONFIG_DIR=/home/claude/.config/claude-code
 
# Expose common ports
EXPOSE 3000 8080 9229
 
CMD ["bash"]

Docker Compose for Local Development

# docker-compose.yml
version: '3.8'
 
services:
  claude-dev:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/home/claude/app:cached
      - node_modules:/home/claude/app/node_modules
      - claude_config:/home/claude/.config/claude-code
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - NODE_ENV=development
    ports:
      - "3000:3000"
      - "8080:8080"
      - "9229:9229" # Node.js debugging
    command: npm run dev
    
  # Local services for development
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: claude_dev
      POSTGRES_USER: claude
      POSTGRES_PASSWORD: localdev
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
      
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes
    
volumes:
  node_modules:
  claude_config:
  postgres_data:

Hot Reload Configuration

Enable hot reload for rapid development:

// package.json
{
  "scripts": {
    "dev": "nodemon --exec tsx src/index.ts",
    "dev:debug": "nodemon --exec 'node --inspect=0.0.0.0:9229 -r tsx/register' src/index.ts"
  },
  "nodemonConfig": {
    "watch": ["src"],
    "ext": "ts,js,json",
    "ignore": ["src/**/*.test.ts"],
    "delay": "1000"
  }
}

Production Docker Images

Multi-Stage Production Build

Optimize for size and security:

# Dockerfile
# Stage 1: Build
FROM node:22-bookworm-slim AS builder
 
WORKDIR /build
 
# Copy package files
COPY package*.json ./
COPY tsconfig.json ./
 
# Install dependencies
RUN npm ci --only=production && \
    npm cache clean --force
 
# Copy source code
COPY src ./src
 
# Build TypeScript
RUN npm install -g typescript && \
    tsc
 
# Stage 2: Runtime
FROM gcr.io/distroless/nodejs22-debian12
 
# Copy built application
COPY --from=builder /build/dist /app
COPY --from=builder /build/node_modules /app/node_modules
COPY --from=builder /build/package*.json /app/
 
# Set non-root user
USER nonroot
WORKDIR /app
 
# Environment configuration
ENV NODE_ENV=production
ENV NODE_OPTIONS="--enable-source-maps"
 
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
    CMD ["/nodejs/bin/node", "healthcheck.js"]
 
# Run application
CMD ["index.js"]

Security-Hardened Alpine Image

For maximum security with minimal size:

# Dockerfile.alpine
FROM node:22-alpine AS builder
 
# Install build dependencies
RUN apk add --no-cache python3 make g++
 
WORKDIR /build
COPY package*.json ./
RUN npm ci --only=production
 
COPY . .
RUN npm run build
 
# Runtime stage
FROM node:22-alpine
 
# Security updates
RUN apk update && apk upgrade && \
    apk add --no-cache dumb-init && \
    rm -rf /var/cache/apk/*
 
# Create app user
RUN addgroup -g 1001 -S appuser && \
    adduser -S -u 1001 -G appuser appuser
 
# Copy application
COPY --from=builder --chown=appuser:appuser /build/dist /app
COPY --from=builder --chown=appuser:appuser /build/node_modules /app/node_modules
 
# Security hardening
RUN chmod -R 550 /app && \
    chmod -R 770 /tmp
 
USER appuser
WORKDIR /app
 
# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "index.js"]

DevContainers for Team Development

Complete DevContainer Configuration

// .devcontainer/devcontainer.json
{
  "name": "Claude Code Development",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  
  "customizations": {
    "vscode": {
      "settings": {
        "terminal.integrated.defaultProfile.linux": "zsh",
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.codeActionsOnSave": {
          "source.fixAll.eslint": true
        },
        "typescript.updateImportsOnFileMove.enabled": "always",
        "typescript.tsserver.maxTsServerMemory": 4096,
        "git.autofetch": true,
        "git.enableSmartCommit": true
      },
      "extensions": [
        "anthropic.claude-code",
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "eamodio.gitlens",
        "ms-vscode.vscode-typescript-tslint-plugin",
        "orta.vscode-jest",
        "streetsidesoftware.code-spell-checker",
        "christian-kohler.path-intellisense",
        "aaron-bond.better-comments",
        "wayou.vscode-todo-highlight",
        "gruntfuggly.todo-tree"
      ]
    }
  },
  
  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {},
    "ghcr.io/devcontainers/features/docker-in-docker:2": {},
    "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}
  },
  
  "postCreateCommand": "npm install && npm run setup",
  "postStartCommand": "git config --global --add safe.directory /workspace",
  
  "mounts": [
    "source=claude-bashhistory,target=/home/node/.bash_history,type=volume",
    "source=claude-config,target=/home/node/.config/claude-code,type=volume"
  ],
  
  "remoteEnv": {
    "NODE_OPTIONS": "--max-old-space-size=4096",
    "CLAUDE_CONFIG_DIR": "/home/node/.config/claude-code"
  },
  
  "forwardPorts": [3000, 8080, 9229],
  "portsAttributes": {
    "3000": {
      "label": "Application",
      "onAutoForward": "notify"
    },
    "9229": {
      "label": "Node Debug",
      "onAutoForward": "silent"
    }
  },
  
  "remoteUser": "node"
}

DevContainer Dockerfile

# .devcontainer/Dockerfile
FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
 
# Install additional tools
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    && apt-get -y install --no-install-recommends \
    zsh \
    fzf \
    ripgrep \
    fd-find \
    bat \
    httpie \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*
 
# Install global npm packages
RUN npm install -g \
    @claude-ai/claude-code \
    typescript \
    tsx \
    npm-check-updates \
    concurrently \
    serve
 
# Install oh-my-zsh
USER node
RUN sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
    && echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.zshrc
 
# Set up Claude Code configuration
RUN mkdir -p ~/.config/claude-code
 
WORKDIR /workspace

Kubernetes Orchestration

Production-Ready Kubernetes Manifests

Namespace and RBAC

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: claude-code
  labels:
    name: claude-code
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
---
# rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: claude-code-sa
  namespace: claude-code
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: claude-code-role
  namespace: claude-code
rules:
- apiGroups: [""]
  resources: ["configmaps", "secrets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: claude-code-binding
  namespace: claude-code
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: claude-code-role
subjects:
- kind: ServiceAccount
  name: claude-code-sa
  namespace: claude-code

Deployment with Advanced Features

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: claude-code
  namespace: claude-code
  labels:
    app: claude-code
    version: v1.0.0
spec:
  replicas: 3
  revisionHistoryLimit: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: claude-code
  template:
    metadata:
      labels:
        app: claude-code
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
        prometheus.io/path: "/metrics"
    spec:
      serviceAccountName: claude-code-sa
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 2000
        seccompProfile:
          type: RuntimeDefault
      
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - claude-code
            topologyKey: kubernetes.io/hostname
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: workload-type
                operator: In
                values:
                - compute-optimized
      
      containers:
      - name: claude-code
        image: your-registry/claude-code:v1.0.0
        imagePullPolicy: IfNotPresent
        
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        - name: metrics
          containerPort: 9090
          protocol: TCP
        
        env:
        - name: NODE_ENV
          value: "production"
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: ANTHROPIC_API_KEY
          valueFrom:
            secretKeyRef:
              name: claude-code-secrets
              key: api-key
        
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
            ephemeral-storage: "1Gi"
          limits:
            memory: "1Gi"
            cpu: "1000m"
            ephemeral-storage: "2Gi"
        
        livenessProbe:
          httpGet:
            path: /health/live
            port: http
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        
        readinessProbe:
          httpGet:
            path: /health/ready
            port: http
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        
        startupProbe:
          httpGet:
            path: /health/startup
            port: http
          initialDelaySeconds: 0
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 30
        
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000
          capabilities:
            drop:
            - ALL
        
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: cache
          mountPath: /app/.cache
        - name: config
          mountPath: /app/config
          readOnly: true
      
      volumes:
      - name: tmp
        emptyDir: {}
      - name: cache
        emptyDir: {}
      - name: config
        configMap:
          name: claude-code-config
      
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: claude-code

Helm Chart Structure

# Chart.yaml
apiVersion: v2
name: claude-code
description: Claude Code Kubernetes Deployment
type: application
version: 1.0.0
appVersion: "1.0.0"
home: https://github.com/anthropics/claude-code
maintainers:
  - name: Your Team
    email: team@example.com
 
# values.yaml
replicaCount: 3
 
image:
  repository: your-registry/claude-code
  pullPolicy: IfNotPresent
  tag: ""  # Overrides the image tag
 
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
 
serviceAccount:
  create: true
  annotations: {}
  name: ""
 
podAnnotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "9090"
 
podSecurityContext:
  runAsNonRoot: true
  runAsUser: 1000
  fsGroup: 2000
 
securityContext:
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 1000
  capabilities:
    drop:
    - ALL
 
service:
  type: ClusterIP
  port: 80
  targetPort: 8080
  annotations: {}
 
ingress:
  enabled: true
  className: "nginx"
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/rate-limit: "100"
  hosts:
    - host: claude-code.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: claude-code-tls
      hosts:
        - claude-code.example.com
 
resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: 1000m
    memory: 1Gi
 
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
  targetMemoryUtilizationPercentage: 80
 
nodeSelector: {}
tolerations: []
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchLabels:
          app.kubernetes.io/name: claude-code
      topologyKey: kubernetes.io/hostname
 
configMap:
  data:
    LOG_LEVEL: "info"
    NODE_ENV: "production"
 
secrets:
  anthropicApiKey: ""  # Set via --set or values file
 
monitoring:
  enabled: true
  serviceMonitor:
    enabled: true
    interval: 30s
  prometheusRule:
    enabled: true

Container Security Architecture

Comprehensive Security Implementation

1. Secrets Management with External Secrets

# external-secrets.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: claude-code
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "claude-code"
          serviceAccountRef:
            name: claude-code-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: claude-code-secrets
  namespace: claude-code
spec:
  refreshInterval: 15s
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: claude-code-secrets
    creationPolicy: Owner
  data:
  - secretKey: api-key
    remoteRef:
      key: claude-code/prod
      property: anthropic_api_key
  - secretKey: database-url
    remoteRef:
      key: claude-code/prod
      property: database_url

2. Network Policies

# network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: claude-code-network-policy
  namespace: claude-code
spec:
  podSelector:
    matchLabels:
      app: claude-code
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    - podSelector:
        matchLabels:
          app: claude-code
    ports:
    - protocol: TCP
      port: 8080
  egress:
  # Allow DNS
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system
    ports:
    - protocol: UDP
      port: 53
  # Allow external HTTPS
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.0.0.0/8
        - 192.168.0.0/16
        - 172.16.0.0/12
    ports:
    - protocol: TCP
      port: 443
  # Allow internal services
  - to:
    - namespaceSelector:
        matchLabels:
          name: claude-code
    - namespaceSelector:
        matchLabels:
          name: monitoring

3. Pod Security Policy

# pod-security-policy.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: claude-code-psp
spec:
  privileged: false
  allowPrivilegeEscalation: false
  requiredDropCapabilities:
    - ALL
  volumes:
    - 'configMap'
    - 'emptyDir'
    - 'projected'
    - 'secret'
    - 'downwardAPI'
    - 'persistentVolumeClaim'
  hostNetwork: false
  hostIPC: false
  hostPID: false
  runAsUser:
    rule: 'MustRunAsNonRoot'
  seLinux:
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'RunAsAny'
  fsGroup:
    rule: 'RunAsAny'
  readOnlyRootFilesystem: true

4. Admission Controllers

# opa-policy.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: claude-code-policies
  namespace: opa-system
data:
  claude-code.rego: |
    package kubernetes.admission
    
    deny[msg] {
      input.request.kind.kind == "Pod"
      input.request.namespace == "claude-code"
      container := input.request.object.spec.containers[_]
      not container.securityContext.runAsNonRoot
      msg := sprintf("Container %v must run as non-root user", [container.name])
    }
    
    deny[msg] {
      input.request.kind.kind == "Pod"
      input.request.namespace == "claude-code"
      container := input.request.object.spec.containers[_]
      not container.securityContext.readOnlyRootFilesystem
      msg := sprintf("Container %v must have read-only root filesystem", [container.name])
    }
    
    deny[msg] {
      input.request.kind.kind == "Pod"
      input.request.namespace == "claude-code"
      container := input.request.object.spec.containers[_]
      not container.resources.limits.memory
      msg := sprintf("Container %v must have memory limits", [container.name])
    }

Container Scanning Pipeline

# .github/workflows/container-security.yml
name: Container Security Scan
 
on:
  push:
    branches: [ main, develop ]
  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 results to GitHub Security
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: 'trivy-results.sarif'
    
    - name: Run Snyk Scanner
      uses: snyk/actions/docker@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      with:
        image: claude-code:${{ github.sha }}
        args: --severity-threshold=high
    
    - name: Run Grype Scanner
      uses: anchore/scan-action@v3
      with:
        image: claude-code:${{ github.sha }}
        fail-build: true
        severity-cutoff: high

CI/CD Pipeline Integration

GitLab CI/CD Pipeline

# .gitlab-ci.yml
variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
  CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
  KUBERNETES_VERSION: 1.29.0
 
stages:
  - build
  - test
  - scan
  - deploy
 
before_script:
  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
 
build:
  stage: build
  image: docker:24.0.5
  services:
    - docker:24.0.5-dind
  script:
    - docker build --pull -t $CONTAINER_TEST_IMAGE .
    - docker push $CONTAINER_TEST_IMAGE
  only:
    - branches
 
test:
  stage: test
  image: $CONTAINER_TEST_IMAGE
  script:
    - npm test
    - npm run test:integration
  coverage: '/Coverage: \d+\.\d+%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
 
security-scan:
  stage: scan
  image: aquasecurity/trivy:latest
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $CONTAINER_TEST_IMAGE
  allow_failure: true
 
deploy-staging:
  stage: deploy
  image: bitnami/kubectl:$KUBERNETES_VERSION
  script:
    - kubectl config use-context staging
    - kubectl set image deployment/claude-code claude-code=$CONTAINER_TEST_IMAGE -n claude-code-staging
    - kubectl rollout status deployment/claude-code -n claude-code-staging
  environment:
    name: staging
    url: https://staging.claude-code.example.com
  only:
    - develop
 
deploy-production:
  stage: deploy
  image: bitnami/kubectl:$KUBERNETES_VERSION
  script:
    - docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
    - docker push $CONTAINER_RELEASE_IMAGE
    - kubectl config use-context production
    - kubectl set image deployment/claude-code claude-code=$CONTAINER_RELEASE_IMAGE -n claude-code
    - kubectl rollout status deployment/claude-code -n claude-code
  environment:
    name: production
    url: https://claude-code.example.com
  when: manual
  only:
    - main

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Build and Deploy
 
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
 
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
 
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write
    
    steps:
    - name: Checkout
      uses: actions/checkout@v4
    
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Log in to Container Registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
          type=sha
    
    - name: Build and push Docker image
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max
        platforms: linux/amd64,linux/arm64
    
    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
        format: 'sarif'
        output: 'trivy-results.sarif'
    
    - name: Upload Trivy results
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: 'trivy-results.sarif'
 
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Deploy to Kubernetes
      uses: azure/k8s-deploy@v4
      with:
        manifests: |
          k8s/deployment.yaml
          k8s/service.yaml
        images: |
          ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
        namespace: claude-code

ArgoCD GitOps Configuration

# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: claude-code
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/yourorg/claude-code-k8s
    targetRevision: HEAD
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: claude-code
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
    - CreateNamespace=true
    - PrunePropagationPolicy=foreground
    - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  revisionHistoryLimit: 10

Performance Optimization

Container Image Optimization

1. Layer Caching Strategy

# Optimized Dockerfile with layer caching
FROM node:22-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
 
FROM node:22-alpine AS build-dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci
 
FROM build-dependencies AS build
WORKDIR /app
COPY . .
RUN npm run build
 
FROM node:22-alpine AS runtime
WORKDIR /app
 
# Copy production dependencies
COPY --from=dependencies /app/node_modules ./node_modules
 
# Copy built application
COPY --from=build /app/dist ./dist
COPY --from=build /app/package*.json ./
 
# Add non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
 
USER nodejs
 
EXPOSE 8080
CMD ["node", "dist/index.js"]

2. Build Performance

# docker-compose.build.yml
version: '3.8'
 
services:
  builder:
    build:
      context: .
      dockerfile: Dockerfile
      cache_from:
        - ${REGISTRY}/claude-code:builder
        - ${REGISTRY}/claude-code:latest
      args:
        BUILDKIT_INLINE_CACHE: 1
    image: ${REGISTRY}/claude-code:builder

Kubernetes Resource Optimization

# resource-optimization.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: claude-code-quota
  namespace: claude-code
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    limits.cpu: "20"
    limits.memory: 40Gi
    persistentvolumeclaims: "10"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: claude-code-limits
  namespace: claude-code
spec:
  limits:
  - max:
      cpu: "2"
      memory: 4Gi
    min:
      cpu: 100m
      memory: 128Mi
    default:
      cpu: 500m
      memory: 512Mi
    defaultRequest:
      cpu: 250m
      memory: 256Mi
    type: Container

Performance Monitoring

# monitoring.yaml
apiVersion: v1
kind: Service
metadata:
  name: claude-code-metrics
  namespace: claude-code
  labels:
    app: claude-code
spec:
  ports:
  - name: metrics
    port: 9090
    targetPort: 9090
  selector:
    app: claude-code
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: claude-code
  namespace: claude-code
spec:
  selector:
    matchLabels:
      app: claude-code
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: claude-code-alerts
  namespace: claude-code
spec:
  groups:
  - name: claude-code.rules
    interval: 30s
    rules:
    - alert: ClaudeCodeHighMemoryUsage
      expr: |
        (container_memory_working_set_bytes{pod=~"claude-code-.*"} 
        / container_spec_memory_limit_bytes{pod=~"claude-code-.*"}) > 0.9
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "High memory usage detected"
        description: "Pod {{ $labels.pod }} memory usage is above 90%"
    
    - alert: ClaudeCodeHighCPUUsage
      expr: |
        rate(container_cpu_usage_seconds_total{pod=~"claude-code-.*"}[5m]) > 0.9
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "High CPU usage detected"
        description: "Pod {{ $labels.pod }} CPU usage is above 90%"

Monitoring and Observability

Complete Observability Stack

# observability-stack.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: claude-code-dashboards
  namespace: monitoring
data:
  claude-code-dashboard.json: |
    {
      "dashboard": {
        "title": "Claude Code Performance",
        "panels": [
          {
            "title": "Request Rate",
            "targets": [
              {
                "expr": "rate(http_requests_total{job=\"claude-code\"}[5m])"
              }
            ]
          },
          {
            "title": "Error Rate",
            "targets": [
              {
                "expr": "rate(http_requests_total{job=\"claude-code\",status=~\"5..\"}[5m])"
              }
            ]
          },
          {
            "title": "Response Time",
            "targets": [
              {
                "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job=\"claude-code\"}[5m]))"
              }
            ]
          }
        ]
      }
    }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: claude-code
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush         5
        Log_Level     info
        Daemon        off
    
    [INPUT]
        Name              tail
        Path              /var/log/containers/claude-code*.log
        Parser            docker
        Tag               claude.code.*
        Refresh_Interval  5
        Mem_Buf_Limit     5MB
    
    [FILTER]
        Name         kubernetes
        Match        claude.code.*
        Kube_URL     https://kubernetes.default.svc:443
        Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
    
    [OUTPUT]
        Name         es
        Match        *
        Host         elasticsearch.monitoring.svc.cluster.local
        Port         9200
        Index        claude-code
        Type         _doc

Distributed Tracing

# tracing.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
  namespace: claude-code
data:
  otel-collector-config.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318
    
    processors:
      batch:
        timeout: 1s
        send_batch_size: 1024
      
      resource:
        attributes:
        - key: service.name
          value: claude-code
          action: upsert
        - key: service.namespace
          from_attribute: k8s.namespace.name
          action: insert
    
    exporters:
      jaeger:
        endpoint: jaeger-collector.monitoring.svc.cluster.local:14250
        tls:
          insecure: true
      
      prometheus:
        endpoint: "0.0.0.0:8889"
    
    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch, resource]
          exporters: [jaeger]
        
        metrics:
          receivers: [otlp]
          processors: [batch]
          exporters: [prometheus]

Troubleshooting Guide

Common Container Issues

1. Container Startup Failures

# Debug container startup
docker run -it --entrypoint sh claude-code:latest
 
# Check container logs
docker logs -f <container-id>
 
# Inspect container
docker inspect <container-id>
 
# In Kubernetes
kubectl describe pod <pod-name> -n claude-code
kubectl logs <pod-name> -n claude-code --previous
kubectl exec -it <pod-name> -n claude-code -- sh

2. Permission Issues

# Fix permission issues
FROM node:22-alpine
 
# Create app directory with correct permissions
RUN mkdir -p /app && chown -R node:node /app
 
WORKDIR /app
USER node
 
# Copy with correct ownership
COPY --chown=node:node . .

3. Network Connectivity

# Test connectivity from container
docker run --rm -it nicolaka/netshoot
 
# In Kubernetes
kubectl run -it --rm debug --image=nicolaka/netshoot --restart=Never -- sh
 
# Check DNS resolution
nslookup claude-code.claude-code.svc.cluster.local
 
# Test service connectivity
curl -v http://claude-code:8080/health

Debugging Tools

# debug-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: debug-pod
  namespace: claude-code
spec:
  containers:
  - name: debug
    image: nicolaka/netshoot
    command: ["/bin/bash"]
    args: ["-c", "while true; do sleep 30; done;"]
    securityContext:
      capabilities:
        add:
        - NET_ADMIN
        - SYS_ADMIN

Migration Strategies

Migrating from Traditional Deployment

Phase 1: Containerize Application

# 1. Create Dockerfile
# 2. Build and test locally
docker build -t claude-code:test .
docker run -p 8080:8080 claude-code:test
 
# 3. Validate functionality
curl http://localhost:8080/health

Phase 2: Container Registry Setup

# Tag and push to registry
docker tag claude-code:test $REGISTRY/claude-code:v1.0.0
docker push $REGISTRY/claude-code:v1.0.0

Phase 3: Kubernetes Migration

# Deploy to staging
kubectl apply -f k8s/staging/
 
# Validate deployment
kubectl get pods -n claude-code-staging
kubectl port-forward svc/claude-code 8080:80 -n claude-code-staging
 
# Gradual rollout to production
kubectl apply -f k8s/production/
kubectl rollout status deployment/claude-code -n claude-code

Blue-Green Deployment Strategy

# blue-green-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: claude-code
  namespace: claude-code
spec:
  selector:
    app: claude-code
    version: green  # Switch between blue/green
  ports:
  - port: 80
    targetPort: 8080

Best Practices Summary

Development

  1. Use DevContainers for consistent development environments
  2. Enable hot reload for rapid iteration
  3. Mount source code as volumes in development
  4. Use Docker Compose for local service dependencies
  5. Implement health checks early in development

Security

  1. Never embed secrets in images
  2. Run as non-root user always
  3. Use minimal base images (distroless, Alpine)
  4. Scan images for vulnerabilities in CI/CD
  5. Implement network policies in Kubernetes
  6. Enable read-only root filesystem
  7. Drop all capabilities except required ones

Performance

  1. Optimize layer caching in Dockerfiles
  2. Use multi-stage builds to reduce image size
  3. Set appropriate resource requests and limits
  4. Enable horizontal pod autoscaling
  5. Use init containers for setup tasks
  6. Implement graceful shutdown handling

Operations

  1. Use GitOps for deployment management
  2. Implement comprehensive monitoring
  3. Set up distributed tracing
  4. Configure log aggregation
  5. Create runbooks for common issues
  6. Practice disaster recovery procedures

CI/CD

  1. Automate everything - builds, tests, deployments
  2. Use semantic versioning for images
  3. Implement progressive rollouts
  4. Enable automatic rollbacks
  5. Maintain separate environments
  6. Use feature flags for gradual releases

Conclusion

Containerizing Claude Code provides a robust, scalable, and secure deployment platform. By following the practices outlined in this guide, you can build a production-ready container infrastructure that supports rapid development while maintaining security and reliability.

Remember to:

  • Start simple and iterate
  • Prioritize security from the beginning
  • Monitor everything
  • Automate repetitive tasks
  • Document your decisions
  • Test disaster recovery scenarios

The container ecosystem continues to evolve rapidly. Stay updated with the latest best practices and security advisories to maintain a robust Claude Code deployment.

Additional Resources