Self-Hosted Claude Code: Run on Your Infrastructure
Some teams cannot use cloud services: legal holds, data residency rules, or air-gapped networks. For them, Claude Code must run on-premises. This means building a complete Claude Code runner stack inside your own infrastructure—handling authentication, credential management, session persistence, and monitoring.
This guide covers self-hosted Claude Code: why you might choose it, how to set up runners, handle git credentials and network egress, run on Kubernetes or Docker Compose, and troubleshoot common problems.
Why Self-Hosted vs. Cloud
Cloud deployments (AWS SageMaker, Azure ML, GCP Vertex) are convenient but violate some organizations' requirements. Self-hosted means you control every layer: compute, storage, network, and credentials stay in your data centers.
Trade-offs: self-hosted requires more ops work. You manage infrastructure, patching, backups, and failover. But it gives you:
- Data residency: all code and context stay in your country/region.
- Air-gapped security: no internet egress unless you explicitly allow it.
- Compliance: HIPAA, SOC 2, PCI-DSS audits see your infrastructure, not a third-party's.
- Custom networking: integrate with your VPN, reverse proxies, and legacy systems.
Quick reference
- Use self-hosted if: regulated industry, data residency laws, airgap requirement, or custom infra integration.
- Avoid self-hosted if: you want operational simplicity, multi-region scale, or minimal ops overhead.
- Hybrid: self-hosted runners for sensitive work; cloud runners for general purpose.
- Compliance: document runner deployment, access controls, and audit logs for regulators.
Remember this
Self-hosted Claude Code runs entirely on your infrastructure, giving you full control over data, compliance, and networking at the cost of operational complexity.
Runner Setup and Image Building
A Claude Code runner is a Docker container or VM that executes code in isolation. It receives a request (user code, context, model), spawns a sandbox, runs the code, captures output, and returns it to the control plane.
Runners need: Docker or a container runtime, Python 3.10+, git client, and the Claude Code agent SDK. Build a base image with these, then run it as a pod (Kubernetes) or container (Docker Compose).
The image contains: dependencies, configuration templates, health check scripts, and logging agents. Size it 2–4 GB to keep startup latency under 5 seconds.
Quick reference
- Base image: Linux (Ubuntu 22.04 or RHEL 8), Python 3.10+, Docker (or container runtime), git.
- Claude Code agent SDK: installed via pip; version pinned for reproducibility.
- Security: run as non-root; use multi-stage build to exclude build deps from final image.
- Health check: liveness probe returns 200 if runner is ready; readiness probe confirms connectivity to control plane.
- Logging: all output to stdout (JSON); container runtime or logging agent captures for centralization.
Remember this
Build a container image with Python, Docker, git, and the Claude Code SDK; deploy as Kubernetes pods or containers with health checks and persistent cache.
Network Egress and Git Credential Management
Self-hosted runners need to: (1) pull dependencies (pip packages, git repos), (2) authenticate to private git repos, and (3) optionally send telemetry/logs to central services.
Network egress is controlled. If your network is air-gapped, allow specific egress rules (package registries, git servers, logging services). If not, all traffic flows through a proxy.
Git credentials must not be baked into the image. Use environment variables or a credential helper. For Kubernetes, store credentials in Secrets. For Docker Compose, use a .env file (excluded from Git).
Private git repos require SSH keys or tokens. Mount the SSH key from a Kubernetes Secret, or inject it at container startup. Rotate keys every 90 days.
Quick reference
- Network policy: restrict egress to approved destinations (pypi.org, git.company.com, logs.company.com).
- Proxy: if air-gapped, configure pip, git, and Docker to use corporate proxy (HTTP_PROXY env var).
- Git credentials: mount SSH key from Secret, or use git credential helper with token.
- SSH key rotation: monthly job updates SSH key in Secret; old key remains valid for 7 days grace period.
- Telemetry: if allowed, send logs to central logging service (e.g., Splunk, ELK); otherwise log locally and scrape.
Remember this
Git credentials and API tokens are mounted from Kubernetes Secrets or injected at startup, never baked into images; rotate regularly and audit access.
Kubernetes Recipes: DaemonSet and Job
For Kubernetes deployments, two patterns:
DaemonSet: one runner pod per node. Good if every node should be able to run code. Runners share node resources (CPU, memory). Use this for uniform clusters.
Job: ephemeral pods spawned on-demand. Each request spawns a new pod, runs code, and exits. Good if traffic is bursty or you want strict isolation per request.
We recommend StatefulSet (covered earlier) for production: named pods, persistent cache, and orderly scaling.
Quick reference
- DaemonSet: one pod per node; use for clusters where every node is identical.
- Job: ephemeral pods; use for bursty traffic or strict per-request isolation.
- StatefulSet: named pods (runner-0, runner-1, runner-2); persistent cache; orderly scaling.
- Resource requests/limits: request 1CPU, 2GB memory; limit 2CPU, 4GB; adjust based on workload.
- Pod disruption budget: allow at most 1 runner to be evicted during maintenance.
Remember this
DaemonSet runs one runner per node; Job spawns ephemeral runners on demand; StatefulSet offers balance with persistent cache and named identity.
Docker Compose for Small Teams
For teams <50 engineers, Docker Compose on a single machine is simpler than Kubernetes. Define all services (runners, control plane, Redis, database) in one file, and manage with docker-compose commands.
Caveats: single-node failure is total outage. No auto-scaling. Good for dev/test or on-prem teams with low traffic.
For production <50 engineers, consider: Docker Compose on a machine with SSD and 32GB RAM, daily snapshots to S3, and a hot-standby machine ready to restore.
Quick reference
- Single docker-compose.yml: runners, control-plane, redis, postgres, nginx.
- Volumes: runner cache, database data, logs all mounted on host.
- Networking: all services on a shared network; runners reach control-plane via service name.
- Scaling: scale runners with 'docker-compose up --scale runner=5'.
- Backup: cron job nightly snapshots database and runner cache to S3.
Remember this
Docker Compose is ideal for <50 engineers; add nightly snapshots to S3 and a hot-standby machine for production resilience without Kubernetes complexity.
Session Identity Verification: JWT Validation
Runners must trust that requests come from authenticated users. When the control plane forwards a request to a runner, it includes a JWT signed by the control plane. The runner validates the signature to confirm the control plane is legitimate (not a man-in-the-middle).
JWT payload includes: user_id, team, session_id, issued_at, expires_at. The runner checks: signature is valid, token is not expired, user is allowed to run code.
For mutual TLS (mTLS), both control plane and runners exchange certificates. This is more secure but requires certificate management.
Quick reference
- JWT token: signed by control plane's private key; runner verifies with public key.
- Payload: user_id, team, session_id, timestamp, expires_at (5-min expiry).
- Verification: check signature, expiration, and user is in allowed_users list.
- mTLS: both control-plane and runner use client certificates; verify on connection.
- Certificate rotation: CA re-signs certs every 90 days; old certs valid for 30-day grace.
Remember this
Runners verify JWT tokens signed by the control plane, ensuring only authenticated requests execute code; short TTL (5 min) limits exposure.
Cost Considerations: Compute, Bandwidth, Maintenance
Self-hosted isn't free. Compute: each runner pod costs money (server, power, colocation). Bandwidth: egress to git servers, package registries, and logging. Maintenance: patching, backups, monitoring, on-call support.
For 500 engineers, estimate: 3–5 large servers ($20K/year), bandwidth ($5K/year), managed database ($10K/year), backup storage ($2K/year), and 1 FTE for ops ($150K/year). Total: ~$200K/year.
Cloud deployment: similar traffic on AWS = $150K/year in compute + management by AWS. If you have on-prem infrastructure already, self-hosted marginal cost is lower; otherwise, cloud is more cost-effective.
Quick reference
- Compute: 3–5 high-memory servers for 500 engineers; each $200–300/month if collocated.
- Bandwidth: 100 GB/month outbound = ~$500–1000/month; negotiate tier rates with ISP.
- Database: managed PostgreSQL (AWS RDS or self-hosted) ~$500–2000/month.
- Storage: daily snapshots to S3/GCS = $100–200/month.
- Operations: 1 FTE full-time, or 0.5 FTE if using managed services.
Remember this
Self-hosted costs ~$150–250K/year for 500 engineers; only economical if you already have on-prem infra or need strict compliance.
Troubleshooting and Monitoring
Common issues:
1. Runners crash on startup: check logs for missing dependencies or credentials. Verify Docker image includes all required packages.
2. Slow code execution: runner CPU/memory limits too low, or underlying node is overloaded. Check Kubernetes metrics (kubectl top nodes); increase resource requests.
3. Git credential failures: SSH key expired or not mounted. Verify Secret exists; check runner logs for "Permission denied" errors.
4. Control plane can't reach runners: network policy blocking traffic, or runners not healthy (failed health checks). Use kubectl port-forward to test connectivity.
5. Data residency violation: code/context sent outside your network. Check firewall rules; enable egress logging to audit traffic.
Monitor: runner CPU, memory, disk (cache), latency per request, error rate, and unauthorized access attempts.
Quick reference
- Logs: kubectl logs -f deployment/claude-code-runners; check for errors on startup.
- Metrics: kubectl top nodes/pods; alert if runner memory > 80% or CPU > 70%.
- Health: kubectl get pods; check READY and RESTARTS columns.
- Network: tcpdump -i any to diagnose packet loss or firewall blocks.
- Audit: log all requests to runners, including JWT subject and timestamp.
Remember this
Monitor runner health, logs, and metrics; common failures are missing dependencies, resource contention, or network connectivity issues.
Key takeaway
Self-hosted Claude Code gives you full control over data, compliance, and infrastructure. The cost is operational complexity: managing runners, credentials, backups, and monitoring.
Start small: Docker Compose for dev/test. Migrate to Kubernetes if traffic grows. Always version your infrastructure code and test failover.
The tradeoff is clear: cloud is simpler; self-hosted is more controlled.
Next: understand how to track spending across self-hosted or cloud runners, or dive into enterprise security patterns for regulated environments.
Polo Khan
Lead Author & Systems ArchitectSoftware engineer and distributed systems architect specializing in backend scalability, cloud-native infrastructure, databases, and AI engineering workflows. Author and maintainer of Core Concept Learning.
Related Articles
Explore this topic