Claude Code Self-Hosted Infrastructure 2026

Claude Code Self-Hosted Environments: The Complete Guide to Running Cloud Sessions on Your Own Infrastructure (2026)

The Prompt Shelf ·

Shipped in v2.1.224 (August 7, 2026), self-hosted environments let a Team or Enterprise org run Claude Code’s cloud sessions — the ones normally started from claude.ai, the mobile app, or claude --cloud — on infrastructure they operate instead of Anthropic’s. The developer experience barely changes: same claude.ai picker, same mobile app, same session UI. What changes is where the container actually executes.

The name causes a specific kind of confusion worth clearing up first: “self-hosted” here does not mean self-hosted inference. Model calls still go to api.anthropic.com — there’s no way to point a self-hosted environment at Bedrock, Vertex AI, Microsoft Foundry, or an LLM gateway. What moves onto your infrastructure is code execution: the clone, the file edits, the shell commands, the build. If you came here looking for how to run Claude Code against a self-hosted model, this isn’t that feature — see the Bedrock/Vertex/Foundry setup in the GitHub Actions guide instead.

This guide walks through the architecture, setup, the production-hardening checklist Anthropic’s own docs treat as mandatory, and the gotchas that don’t show up until you’ve run it for a few weeks. Everything below is sourced from the official docs at code.claude.com/docs/en/self-hosted-environments and its linked pages, current as of v2.1.226.


The three-part model: environment, runner, session

Three terms recur throughout the docs, and mixing them up makes the rest hard to follow:

  • Environment — a named destination for cloud sessions, created by an Owner/admin on the Cloud environments admin page. Internally it’s still called a pool in metrics and API fields (pool_id, form ccpool_...) — a leftover from before the public docs renamed it.
  • Runner — a long-lived process you deploy inside your network. It registers with an environment, polls for queued sessions, and executes them, the same idea as a self-hosted CI runner.
  • Session — one Claude Code task a developer started. Each session becomes a child process the runner spawns.

The flow: a developer picks your environment from the session-start picker (alongside Anthropic-hosted options). The control plane queues the session; a runner with free capacity claims it, clones the repo, and spawns a child claude process. That child streams events back to api.anthropic.com over outbound HTTPS, and the poll loop doubles as a heartbeat — if a runner stops polling for ~60 seconds, the session gets requeued to another runner.

Critically, every connection is outbound from your network. Anthropic never connects into your network. The queue, the session’s event stream, and model inference all go out to api.anthropic.com; nothing comes in.

One rule shapes almost everything else about operating this: a runner locks to one user’s account on its first session and only serves that account until it exits. This means your minimum fleet size is the number of developers you expect active at once, not some smaller shared pool — --capacity controls how many concurrent sessions from that one user a runner can run, not how many different users.


Self-hosted environments vs. Remote Control vs. Anthropic-hosted cloud sessions

Anthropic now has three genuinely different ways to run Claude Code away from a plain local terminal, and the names invite confusion:

Self-hosted environmentsRemote ControlAnthropic-hosted cloud sessions
Where code executesYour infrastructure (runner you deploy)Your own machine, already runningAnthropic-managed cloud
PlansTeam/Enterprise only, opt-in betaPro, Max, Team, EnterpriseAll plans with cloud sessions enabled
Setup requiredRunner fleet, environment secret, git credentialsclaude remote-control on an already-running local sessionNone — pick “Anthropic” in the environment picker
What you getInternal network access, custom tooling, compliance control over checkoutsSteer a local session from phone/browserZero-infra execution, no local dependency
Model inferenceapi.anthropic.com (fixed)Wherever the local session’s config pointsapi.anthropic.com (fixed)

If your team doesn’t use cloud sessions at all — everyone works from a terminal or IDE — none of this applies; local sessions always run on the developer’s own machine regardless. If you want to drive an always-on personal machine from your phone, that’s Remote Control, and it’s available on Pro/Max, not just Team/Enterprise. Self-hosted environments solve a different problem: teams whose network, tooling, or compliance posture won’t let any session — even a cloud one someone kicks off from their phone — touch infrastructure Anthropic operates.


Availability and what’s excluded

  • Plans: public beta, Team and Enterprise only. Off by default — an Owner/admin has to turn on Allow self-hosted environments on the Cloud environments admin page, which itself requires Claude Code on the web to already be enabled org-wide.
  • Zero Data Retention: not available if your org has ZDR enabled.
  • Model routing: fixed to the Anthropic API. Cannot route through Bedrock, Vertex AI’s Agent Platform, Microsoft Foundry, or an LLM gateway.
  • Surfaces: works for sessions from Claude Code on the web, mobile, desktop, scheduled routines, and claude --cloud. Claude Tag, Claude Security, and Code Review sessions don’t route to self-hosted environments yet.
  • Repositories: GitHub only (github.com or GitHub Enterprise Server) at launch.
  • Billing: identical to Anthropic-hosted sessions — self-hosting doesn’t change your usage metering.

Quickstart

Anthropic ships a guided setup as an interactive Claude Code session — it walks through creating the environment, saving the secret, starting a local runner, and confirming registration, then writes a cheat sheet to ./runner-setup/CHEAT-SHEET.md:

claude self-hosted-runner setup

This needs claude auth login with an Owner/admin account and Claude Code v2.1.224+. Where an interactive session isn’t possible, do it manually:

# 1. Confirm the host is ready (v2.1.224+ prints runner usage text)
claude self-hosted-runner --help

# 2. Create the environment in claude.ai admin settings, copy the
#    environment secret (shown once, expires after 365 days), then:
mkdir -p /etc/claude
(umask 077 && cat > /etc/claude/environment-secret)   # paste secret, Ctrl-D

# 3. Create a writable base directory for checkouts
mkdir -p '<writable-dir>'

# 4. Start the runner
claude self-hosted-runner \
  --environment-secret-file '/etc/claude/environment-secret' \
  --base-dir '<writable-dir>'

The environment’s status flips from “No runners deployed” to “Healthy” within a few seconds. Start a session at claude.ai/code, pick your environment from the picker, and the runner logs Picked up session <session-id> once it claims it.

Once a session is running, message it from any machine where you’re logged in — it doesn’t need to be the runner host:

claude -p "your message" --cloud <session-id>

Production hardening (Anthropic treats this as mandatory, not optional)

The deploy docs are blunt about the threat model: “a self-hosted runner executes arbitrary, model-directed code on your infrastructure on behalf of any member of your Anthropic organization.” Before pointing an environment at real systems:

  1. Ephemeral, per-session containers. Run each runner in a fresh container/VM destroyed on exit, with --capacity 1 and default --drain-grace-sec 0, so one container ever serves exactly one session. Anything higher shares a filesystem across sessions from the same locked account.
  2. No broad credentials baked into the image. No long-lived SSH keys or cloud credentials with more access than a session needs. Mint per-session credentials from a wrapper script, or use --use-anthropic-git-proxy for the initial clone.
  3. Keep the environment secret off session-running hosts. On a fixed fleet, the secret sits on every runner host, readable by any session’s code. Prefer on-demand runners (the orchestrator subcommand): the secret stays on the orchestrator, which never runs user code, and each spawned runner gets a single-use work order instead.
  4. Default-deny network egress, enforced at your own network boundary — the product can’t do this for you. Session code is model-directed and will attempt arbitrary outbound connections; this applies regardless of permission mode, since Bash is pre-approved by default.
  5. Block the cloud metadata endpoint (169.254.169.254) from session containers explicitly — subnet-level egress rules don’t intercept link-local traffic. IMDSv2 with hop limit 1, or GKE Workload Identity with metadata concealment.
  6. Dispatch is organization-wide. Any org member can send a session to any environment — there’s no per-environment access control on dispatch. Treat every runner host as reachable by every org member; --lock-to-account bounds which account’s sessions a host executes, but not who can dispatch to it.
  7. Enforce the repo-settings guard with --confine-repo-settings enforce (default is warn, which logs but still runs). It scans committed repo settings for grants that escape the session’s own workspace — additionalDirectories, permissions.allow write rules, sandbox.filesystem.allowWrite/allowRead, non-empty env blocks, or sandbox.enabled: false.

None of this is enforced by the product itself — every item on this list is something you configure at your network boundary or in your runner flags.


Configuring git access

Three approaches, in increasing order of how much credential management you own:

ApproachHowGit version floorNotes
--configure-gitRunner writes user.name/user.email matching Anthropic-hosted sessions, plus SSH commit signing via Anthropic’s signing service2.34+Doesn’t set push credentials — you still provide those
Ship config in your imagegit config --system user.name/user.email in your Dockerfile, your own bot identity2.24+Never bake long-lived push tokens into a shared image — mint per-session instead
--use-anthropic-git-proxyClones through api.anthropic.com, authenticated with the session’s own short-lived token — the same path Anthropic-hosted sessions use2.32+Requires --capacity 1; needs your git host reachable from Anthropic’s infrastructure, so it doesn’t work for internal-only git hosts

For a git host that’s only reachable inside your network, use --git-host-rewrite <from>=<to> (split-horizon DNS) or --git-ssh-rewrite <host> instead of the proxy — both are ignored when the proxy is enabled.


Runner CLI flags worth knowing

The full reference lists ~20 flags; these are the ones that actually change behavior in a typical deployment (run claude self-hosted-runner --help for the authoritative, version-specific list):

FlagDefaultWhat it does
--environment-secret-file <path>requiredPath to the environment secret (or, for orchestrator-spawned runners, a single-use work-order JWT)
--base-dir <path>/workspaceWhere checkouts and per-session directories live. Must be identical across every runner in an environment — mismatched values break session resume, since the resumed session’s working directory changes and recorded absolute paths go stale
--capacity <n>1Max concurrent sessions per runner, all from the same locked account
--drain-grace-sec <n>00 = exit immediately once active sessions finish (needed for per-session isolation); positive = keep polling the locked account’s queue for N more seconds
--exit-if-unused-min <n>0Exit after N minutes of polling with zero work assigned — for autoscaler scale-down
--kill-session-after-min <n>0Hard wall-clock kill for stuck sessions. Pair this with --release-idle-session-min, since some sessions (an unfinished background task, or one waiting on an approval from inside a running tool call) never register as idle
--release-idle-session-min <n>0Release a session’s slot after N minutes of inactivity
--push-outcome-on-releaseoffBest-effort push of a session’s outcome branch before the workspace is deleted on release, so resumed sessions don’t lose unpushed work. Restrict push access to claude/* refs before enabling — on resume the runner fetches the branch without verifying who pushed it
--retire-at <epoch-seconds>unsetFor infra that kills hosts at a known wall-clock time without a signal (spot reclamation, sandbox lifetime caps) — the runner releases sessions cleanly before that time instead of leaving a silent crash for the control plane to detect
--confine-repo-settings <mode>warnwarn / enforce / off — see hardening checklist above
--trust-workspace [bool]onSet false to ignore repo-committed permissions.allow/additionalDirectories grants and rely on host-config settings.json instead. Repo-committed sandbox.* settings still get scanned by the repo-settings guard either way
--lock-to-account <id>unsetPre-lock a runner to a specific account at startup instead of locking on first session

Fixed fleet vs. on-demand orchestrator

Two ways to scale:

  • Fixed fleet — a static set of replicas, scaled manually on the Prometheus metrics each runner exposes.
  • On-demand — run claude self-hosted-runner orchestrator, which polls for queued sessions with no runner available and invokes a spawn-runner hook you provide to boot one per session, tearing it down after. This is also the recommended path for the hardening item above: the environment secret lives only on the orchestrator, never on a host that runs session code.

Remember the one-user-per-runner lock: --capacity scales sessions within one user, not across users, so orchestrator autoscaling still needs to spin up a distinct runner per concurrently-active account.


Monitoring

Each runner serves GET /healthz and GET /metrics (Prometheus) on --health-port (default 8080). /healthz returns 200 for any alive process — it doesn’t tell you if the poll loop is stuck, so alert on the metric instead:

  • claude_code_self_hosted_runner_last_poll_age_seconds — alert above 60s
  • claude_code_self_hosted_runner_active_sessions vs. claude_code_self_hosted_runner_capacity — utilization
  • claude_code_self_hosted_runner_locked_account{email} — which account a runner is currently locked to; drop or hash this label at scrape time if your metrics store is broadly readable
  • claude_code_self_hosted_orchestrator_queue_circuit_broken_sessions — should stay at zero; nonzero means a spawn-runner hook is repeatedly failing and needs a manual retry from the Activity tab

Known limitations

  • Connector traffic (GitHub, Slack, Linear, etc.) doesn’t stay in your network. Connectors are invoked from Anthropic’s side, so that traffic routes through api.anthropic.com regardless of where the session executes. To keep a connector out entirely, filter it via allowedMcpServers/deniedMcpServers; run the equivalent tool as a local MCP server on the runner image if it needs to stay internal.
  • Resumed sessions lose unpushed work by default. Releasing a session (idle timeout, runner restart) and resuming it clones fresh from the starting branch — anything not pushed is gone. --push-outcome-on-release mitigates this but doesn’t cover a dirty working tree or per-session config.
  • Private repos can’t be added mid-session — credentials aren’t provisioned for a repo added after the session starts. Select everything a session needs up front.
  • A newly connected connector doesn’t appear in an already-running session. Connect it in Settings, then start a fresh session.

Should you actually use this?

Anthropic’s own framing is direct: “most teams are better served by Anthropic-hosted environments, which need no infrastructure to run or maintain.” Self-hosting makes sense when:

  • Sessions need to reach internal services, databases, or registries that can’t be exposed to the public internet
  • Compliance requires checkouts and build artifacts to stay on infrastructure you control (the conversation itself — prompts, responses, tool results — still goes to api.anthropic.com for inference either way)
  • You already run a self-hosted CI runner fleet and can extend the same operational muscle to Claude Code

It doesn’t make sense if you’re trying to avoid sending code to Anthropic at all (inference still happens on their side, full stop), or if you just want to drive a personal machine from your phone (that’s Remote Control, and it’s simpler, available on Pro/Max, and needs no fleet to operate).


FAQ

Does a self-hosted environment let me use a different model provider? No. Model inference is fixed to api.anthropic.com; Bedrock, Vertex AI, Microsoft Foundry, and LLM gateways aren’t supported for self-hosted sessions.

What’s the difference between self-hosted environments and Remote Control? Remote Control steers a session already running on your own machine, from your phone or browser — no fleet, no environment secret, available on Pro/Max. Self-hosted environments run cloud sessions (the kind started from claude.ai or mobile with no local process involved) on infrastructure you deploy — Team/Enterprise only, opt-in, requires operating a runner fleet.

Is code ever sent to Anthropic in a self-hosted environment? The conversation — prompts, responses, tool results — goes to api.anthropic.com for model inference either way, and Anthropic stores the transcript so the session can resume from another surface. What stays on your infrastructure is the repository checkout, build artifacts, and anything a session writes to disk.

How many runners do I need? At minimum, one per developer you expect to have an active session at once — a runner locks to a single account on its first session and won’t serve anyone else until it exits. --capacity adds parallelism within one account, not across accounts.

Can I run this on Windows? Not as a runner host — Linux or macOS only (run it in a Linux container if your infrastructure is Windows-based). Developer workstations aren’t affected either way, since sessions start from a browser or app, not the runner host.


Browse how real teams configure Claude Code’s permissions, hooks, and sandboxing in our rules gallery.

Source: code.claude.com/docs/en/self-hosted-environments and its linked quickstart, deploy, and reference pages — official Anthropic documentation, current as of Claude Code v2.1.226 (August 2026).

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules