Claude Code Self-Hosted Runners: Sizing and Cost Guide
We earn commissions when you shop through the links below, at no extra cost to you. We only link products we would use ourselves.
A self-hosted environment runs Claude Code cloud sessions on machines you own instead of on Anthropic's infrastructure. The feature is in public beta on Team and Enterprise plans, and the interesting part is not the setup (three commands) but the capacity planning, because the thing that decides your bill is not how many sessions you run. It is how many people run them at the same time.
Here is the short version before the detail.
- Gates: Team or Enterprise plan, Claude Code on the web enabled for the organization, and an Owner switching on Allow self-hosted environments on the Cloud environments admin page. Off by default.
- Host: Linux or macOS, Claude Code v2.1.224 or later, git 2.24 or newer, clock within five minutes of real time. Windows is not supported as a runner host.
- Per session: start at 4 GiB memory (request and limit equal) and 2 CPUs requested with a limit of 4.
- Fleet floor: one runner locks to one session owner, so your minimum runner count is the number of people you expect to be active concurrently, not your total headcount and not your session volume.
- Billing: sessions in your environment consume your organization's Claude Code usage exactly as Anthropic-hosted sessions do. Self-hosting adds infrastructure cost, it does not subtract model cost.
Verified against the Claude Code docs on 2026-09-01 (self-hosted environments overview, quickstart, deploy, configuration, and reference pages). We have not run a self-hosted fleet ourselves: the numbers below are the documented starting points plus the arithmetic they imply, and where an operational judgment is ours rather than the docs', it says so.
What actually runs on your machines
Three pieces, and it helps to keep them straight because the flags and failure modes attach to different ones.
An environment is a named destination created in claude.ai admin settings. A runner is a process you start on your own host: claude self-hosted-runner, part of the standard claude binary, conceptually the same as a self-hosted CI runner. A session is one Claude Code task somebody started from claude.ai, the mobile or desktop app, a scheduled routine, or the terminal with claude --cloud.
When a developer picks your environment in the session-start picker, Anthropic's control plane queues the session, a runner claims it, clones the chosen repository, and starts a Claude Code process on your host. Every connection is outbound from your network: the runner polls the queue, each session opens its own event stream, and inference goes to api.anthropic.com. Anthropic never connects inward, so there are no inbound ports to open and no ingress to expose.
Repository checkouts and build artifacts stay on your infrastructure. Session content still leaves it, because inference is still the Anthropic API. If that distinction does not buy you anything, you do not want this feature: the docs are blunt that most teams are better served by Anthropic-hosted environments, and we agree. The reasons to take on the operational ownership are internal network access (sessions reaching databases, registries, and services that are not on the public internet), a runner image pre-loaded with your compilers and internal CLIs, and keeping checkouts on your own disks for compliance.
Size one session, then count owners
The runner process itself is cheap. It polls, prepares checkouts, runs your lifecycle hooks, and supervises children. The load is the sessions: a Claude Code process plus whatever it starts, which in practice means your build and your test suite.
The documented starting point for one session, in Kubernetes terms:
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 4Gi
Two details in that block matter more than the numbers.
Memory request equals limit on purpose. The scheduler then accounts for the container's full memory, and 4 GiB is also the floor in Claude Code's own system requirements. Hit the memory limit and the kernel kills processes inside the container, which ends a session mid-task.
CPU deliberately does not. The limit sits at double the request so a session can burst during a build. A container at its CPU limit gets throttled, not killed, so the worst case is a slow session rather than a dead one. That asymmetry is why we would spend the money on dedicated-CPU instances rather than shared-CPU ones for anything but a pilot: on shared CPU the burst headroom you sized for is the part the platform can take away, and the symptom is a session that looks hung during npm install rather than a clean error.
Then the part that surprises people. --capacity <n> caps concurrent sessions on one runner, and it does not divide CPU or memory between them: the sessions share the whole container. So at --capacity 4 you multiply one session's numbers by four, or you accept that four sessions are fighting over 4 GiB. The Kubernetes and Compose recipes in the docs run --capacity 4 with no limits set at all, which is fine for a demo and wrong for production.
And the real fleet floor: the first session a runner picks up locks that runner to that session's owner, and it only serves that owner until it exits. --capacity gives you parallelism within one person's sessions, never across people. The docs state the consequence directly: your minimum replica count is the number of users and Claude Tag agents you expect to be active concurrently. Ten developers who each run one session at 9am need ten runners. One developer running ten sessions needs one runner with capacity ten (and forty gibibytes).
What this costs in real hosts
Work it out in two steps, because only the first one is under your control.
- Host shape: one session's numbers, or one session times
--capacity. At the documented starting point that is a 2 to 4 vCPU, 4 GiB instance per concurrent session. - Host count: concurrent people, not concurrent sessions.
The lever that actually moves the bill is when those hosts exist. A fixed fleet sized for peak concurrency is idle most of the day. The alternative is built in: claude self-hosted-runner orchestrator polls for sessions that are queued with no runner available and calls your spawn-runner hook to boot one per session, and each runner exits when its work finishes (--drain-grace-sec defaults to 0, meaning exit as soon as active sessions finish). That turns the fleet into an hourly, per-session cost and is also the more secure shape, for a reason in the next section.
Which makes hourly pricing the number to compare. DigitalOcean's droplet pricing page listed a shared-CPU 2 vCPU / 4 GB configuration from $0.052 per hour when we checked on 2026-09-01, which is roughly $38 a month left running continuously, and dedicated-CPU general-purpose instances cost several times that per vCPU. Check the current figure rather than trusting ours: the point of the arithmetic is that a five-developer team on an on-demand fleet is buying single-digit dollars of compute a week, while the same team on a fixed peak-sized fleet is buying five always-on instances.
Set against that: Anthropic-hosted environments cost nothing extra and need no image, no orchestrator, and no network policy. Self-hosting does not reduce your Claude Code usage, since sessions bill to your organization either way. So the decision is infrastructure cost plus operational ownership, bought for network access and compliance control. If you are pricing "somewhere to run agents" more generally rather than self-hosting cloud sessions specifically, our scheduled Claude Code hosting comparison covers the cheaper end of that question.
The egress allowlist
Sessions run model-directed code with Bash pre-approved, so default-deny egress at your network boundary is the control that bounds where that code can reach. Two hosts are always required:
| Host | Port | Why |
|---|---|---|
api.anthropic.com | 443 | Control plane, session streaming, inference, JWKS, commit signing |
| Your git host | 443 or 22 | Cloning and pushing (not needed with --use-anthropic-git-proxy) |
Everything else is conditional: downloads.claude.ai at install time and for official-marketplace plugin installs, registry.npmjs.org when a session installs a plugin or runs an npx MCP server, storage.googleapis.com for plugin metadata in /plugin, code.claude.com and claude.com for documentation lookups, *.frame.claudeusercontent.com only if the Artifact tool is available to your organization, and two Datadog intake hosts that are off by default in self-hosted environments.
The genuinely useful line in that section is what you can leave out. The docs name four hosts that appear in older enterprise network checklists and are not needed for runner or session traffic: statsig.anthropic.com, *.sentry.io, claude.ai, and platform.claude.com. Feature flags go to api.anthropic.com, and the runner authenticates with the environment secret instead of interactive OAuth. mcp-proxy.anthropic.com is not required either. Two flows do need claude.ai, and both are host-side rather than session-side: the one-line installer, and interactive claude auth login (used by the guided setup and by CI dispatch). Run those from a host whose egress allows it instead of widening session egress.
If your proxy demands a rotating Proxy-Authorization header, --proxy-authorization-command or --proxy-authorization-file (v2.1.238 or later) supplies it per connection, and the runner never logs the value. Set exactly one of them, and set a proxy URL: the runner refuses to start if both are set, if neither HTTPS_PROXY nor HTTP_PROXY holds a URL, or if either flag is passed to the orchestrator subcommand.
Five things that will bite you
These are the items we would put on a pilot checklist, drawn from the hardening and known-issues sections.
The environment secret is readable by session code on a fixed fleet. One shared credential registers runners and can pick up any session queued on the environment, and on a fixed fleet it sits in a file on every host that runs user code. On-demand runners keep it on the orchestrator host, which never runs user code, and hand each runner a single-use work order instead. If you do run a fixed fleet, treat that file as compromised by any session and rotate after any suspicion. The secret is shown once at creation and expires 365 days later.
Resumed sessions lose unpushed work. When a runner releases a session (idle timeout, drain, restart) and the user sends another message, the session resumes on a fresh runner that re-clones from the starting branch. --push-outcome-on-release best-effort pushes outcome branches first, which preserves committed work but not a dirty tree. Before enabling it, restrict who can push to claude/* refs: on resume the runner fetches that branch without verifying who pushed it.
Some sessions never look idle. A session holding a background task that never finishes, or waiting on an approval requested from inside a running tool call, does not count as idle, so --release-idle-session-min will not free the slot. Pair it with --kill-session-after-min as a hard backstop.
Connector traffic still leaves your network. Anthropic calls claude.ai connector tools (GitHub, Slack, Linear, and the rest) from its own infrastructure, so connector calls in a self-hosted session originate outside your boundary. If tool traffic has to stay inside, run the equivalents as local MCP servers in your runner image, or filter connectors with the allowedMcpServers and deniedMcpServers policy settings.
Block the cloud metadata endpoint inside the container. Subnet egress policy does not intercept link-local traffic, so an explicit deny for 169.254.169.254 in the session container's network namespace (or IMDSv2 with hop limit one, or GKE metadata concealment) is what keeps a session off the host identity. Sessions should get credentials from your wrapper script, not inherit the instance profile. The same logic that applies to secrets in a local session applies here: see our notes on keeping credentials out of Bash.
Should you self-host?
Self-host if sessions need to reach internal services that are not on the public internet, if checkouts and build artifacts have to stay on your infrastructure, or if every session needs internal tooling baked into an image. Otherwise do not: you would be taking on an image, a fleet, a network policy, and a set of beta limitations in exchange for nothing you currently need.
Four exclusions are worth checking before you plan a rollout. Zero Data Retention organizations cannot use the feature. Inference cannot be routed through Bedrock, Google Cloud's Agent Platform, Microsoft Foundry, or an LLM gateway. Repositories come from GitHub. And Claude Security and Code Review sessions do not route to self-hosted environments yet, while Claude Tag sessions do, minus Access bundles.
If you are an individual on Pro or Max who read this hoping to run cloud sessions on your own box, this is not the feature you want: Remote Control drives a session on your own always-on machine from other devices, and it is available on those plans.
FAQ
Can a Windows machine be a runner host? No. Linux or macOS only, and the docs point Windows shops at running the runner in a Linux container. Claude Code itself supports Windows 10 1809+ for ordinary local sessions; that is a separate thing from hosting a runner.
Do I need to open inbound ports? No. All traffic is outbound from your network: queue polling, session streams, inference, and git. Anthropic does not connect in. The runner does serve GET /healthz and /metrics on port 8080 by default (--health-port), but that is for your own probes inside your network.
How do I check whether a host's Claude Code is new enough? Run claude self-hosted-runner --help. On v2.1.224 or later it prints the runner's usage text, listing flags such as --environment-secret-file. On older versions it prints the general claude --help output instead, which is the tell that you need claude update. Note that each session's child process runs the runner's own binary and auto-update is off inside sessions, so the version you install on the host or bake into the image is the version your fleet runs until you restart the runners.
Does self-hosting reduce my Claude Code bill? No. Sessions in a self-hosted environment consume your organization's usage exactly as Anthropic-hosted sessions do, so self-hosting is a net addition of infrastructure cost. Buy it for network access or compliance, not for savings.
What is the smallest sensible production shape? One session per container, --capacity 1, the default --drain-grace-sec 0, on an ephemeral container destroyed when the process exits, started on demand by the orchestrator. That is the shape the hardening section recommends, and it is also the shape where the environment secret never sits on a machine running user code.
How does a stuck runner get noticed? /healthz returns 200 whenever the process is alive, so it detects a dead process, not a stalled one. Alert on the last_poll_age_seconds series from /metrics to catch a runner that stopped polling.