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
- Introduction
- Local Development with Containers
- Production Docker Images
- DevContainers for Team Development
- Kubernetes Orchestration
- Container Security Architecture
- CI/CD Pipeline Integration
- Performance Optimization
- Monitoring and Observability
- Troubleshooting Guide
- Migration Strategies
- 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 /workspaceKubernetes 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-codeDeployment 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-codeHelm 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: trueContainer 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_url2. 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: monitoring3. 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: true4. 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: highCI/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:
- mainGitHub 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-codeArgoCD 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: 10Performance 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:builderKubernetes 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: ContainerPerformance 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 _docDistributed 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 -- sh2. 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/healthDebugging 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_ADMINMigration 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/healthPhase 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.0Phase 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-codeBlue-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: 8080Best Practices Summary
Development
- Use DevContainers for consistent development environments
- Enable hot reload for rapid iteration
- Mount source code as volumes in development
- Use Docker Compose for local service dependencies
- Implement health checks early in development
Security
- Never embed secrets in images
- Run as non-root user always
- Use minimal base images (distroless, Alpine)
- Scan images for vulnerabilities in CI/CD
- Implement network policies in Kubernetes
- Enable read-only root filesystem
- Drop all capabilities except required ones
Performance
- Optimize layer caching in Dockerfiles
- Use multi-stage builds to reduce image size
- Set appropriate resource requests and limits
- Enable horizontal pod autoscaling
- Use init containers for setup tasks
- Implement graceful shutdown handling
Operations
- Use GitOps for deployment management
- Implement comprehensive monitoring
- Set up distributed tracing
- Configure log aggregation
- Create runbooks for common issues
- Practice disaster recovery procedures
CI/CD
- Automate everything - builds, tests, deployments
- Use semantic versioning for images
- Implement progressive rollouts
- Enable automatic rollbacks
- Maintain separate environments
- 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.