Skip to main content
Self-hosted environments are in public beta on Team and Enterprise plans; Availability and limitations covers the enablement path. This page covers running the fleet in production; see the quickstart for your first runner and session.
A self-hosted environment runs Claude Code cloud sessions on runners you deploy inside your network, and in production those sessions execute model-directed code on behalf of everyone who can dispatch a session to the environment. This page is for the operator taking a working environment to production. It works through the deployment in order: what to lock down before connecting real systems, the egress the fleet needs, how sessions authenticate to your git host, the deployment recipes themselves, and what to check when sessions misbehave.

Harden your deployment

A self-hosted runner executes arbitrary, model-directed code on your infrastructure on behalf of everyone who can dispatch a session to its environment. That’s any member of your Anthropic organization, and anyone who can start a Claude Tag channel session in a scope an Owner routed to the environment. Work through each item before you connect an environment to production systems:
  • Ephemeral, per-session containers: run each runner process in a fresh container or VM that’s destroyed when the process exits, with --capacity 1 and the default --drain-grace-sec 0 so each container serves exactly one session. At a higher capacity, or with a positive drain grace, one container serves multiple sessions from the same locked owner; see Runner lifecycle. Don’t reuse a filesystem between runner restarts, except in the deliberate pre-warmed checkout setup, and never across owners.
  • No broad credentials in the image: don’t include long-lived SSH keys, cloud-provider credentials, or personal access tokens that grant more than a session needs. Mint credentials used during a session, such as push or API tokens, per session from your wrapper script. For the initial clone, which happens before the wrapper runs, use a checkout lifecycle hook or --use-anthropic-git-proxy; see Configure git.
  • Keep the environment secret off session-running hosts: the environment secret can register runners and pick up any session queued on the environment. On a fixed fleet it lives on every runner host, where any session’s code can read the secret file. Prefer on-demand runners, where the secret stays on the orchestrator host, which never runs user code, and each runner receives a single-use work order that registers exactly one runner. On a fixed fleet, treat the environment-secret file as readable by every session and rotate the secret after any suspected session compromise.
  • Default-deny network egress: restrict runner and session container outbound traffic at your own network boundary on every environment; Default-deny egress covers what to allow and why.
  • Least-privilege host IAM: the compute identity attached to the runner host, such as an instance profile or node service account, should grant only what the runner itself needs. Sessions should obtain their own credentials through your wrapper script rather than inheriting the host’s.
  • Block the cloud metadata endpoint from sessions: keeping sessions off the host identity requires blocking their access to the cloud metadata endpoint, and subnet-level egress policies don’t intercept link-local metadata traffic, so block it in the container itself:
    • IMDSv2 with a hop limit of one
    • GKE Workload Identity with metadata concealment
    • An explicit deny for 169.254.169.254 in the session container’s network namespace
    The block applies to your wrapper script and lifecycle hooks too, since they share the container. Authenticate any token exchange with the session JWT against your own token service over allowlisted egress, or use a file-based web identity such as IAM Roles for Service Accounts (IRSA) on Amazon EKS.
  • Per-runner filesystem isolation: each runner process gets its own working directory that no other process on the host can read or write. Make --hooks-dir, the wrapper script, and the host’s ~/.claude/ read-only to the session, either built into the image or mounted read-only.
  • Dispatch has no per-environment access control: any member of your Anthropic organization can dispatch a session to any of its environments. If an Owner routes Claude Tag channels to the environment, anyone the Claude Tag access setting admits can start channel sessions that run there. By default that’s anyone in the connected Slack workspace, with or without a Claude account. Treat every runner host as reachable for code execution by everyone who can dispatch to it, and place on a runner host only data and credentials that all of those people are allowed to read. --lock-to-account bounds which account’s sessions a given host executes, but it doesn’t narrow who can dispatch into the environment. To make self-hosted environments the only picker option, an Owner can hide Anthropic-hosted environments for the whole organization from the Cloud environments page.
  • Enforce the repo-settings guard: choose the guard mode with --confine-repo-settings. The default warn logs a violation and still spawns the session, enforce refuses the session, and off disables the scan. The runner scans each repository’s committed settings for:
    • A grant that resolves outside that session’s own workspace: an additionalDirectories entry, an Edit, Write, or NotebookEdit rule in permissions.allow, or a sandbox.filesystem.allowWrite or allowRead entry
    • A non-empty env block
    • An operator-posture override such as sandbox.enabled: false
    The guard runs regardless of --trust-workspace, and doesn’t cover repository hooks, .mcp.json, or Bash rules; see Permissions and tool approval for where those grants belong.
Your organization’s IP allowlist doesn’t cover self-hosted runner traffic by default. Don’t rely on it as a network control for runner or session traffic; apply default-deny egress at your own network boundary instead, and contact your Anthropic account team if you want IP-allowlist enforcement for your organization.

Network requirements

The runner and the session children it spawns make outbound connections to the hosts below. Restrict session-container egress to these hosts and the specific internal services sessions need to reach; Default-deny egress covers how and why. These hosts are always required: Whether these hosts are needed depends on your configuration: The runner doesn’t reach statsig.anthropic.com, *.sentry.io, claude.ai, or platform.claude.com. These hosts appear in some older enterprise network checklists, but you don’t need to allowlist them for runner or session traffic: feature-flag fetches go to api.anthropic.com, and the runner authenticates with the environment secret rather than interactive OAuth. Two host-side flows do reach claude.ai, so run them from a host whose egress allows it rather than widening session-container egress: the one-line installer fetches install.sh from claude.ai at install time, and interactive claude auth login, which the guided setup, doctor’s signed-in mode, and CI dispatch use, signs in through claude.ai, claude.com, and platform.claude.com. mcp-proxy.anthropic.com isn’t required either: self-hosted sessions don’t use it, and delivery of your organization’s claude.ai connectors to sessions, when enabled for your organization, routes through api.anthropic.com. See MCP servers.

Default-deny egress

Deploy runner and session containers in a network segment or namespace whose outbound traffic is limited to the hosts in the network requirements table, your git host, and the specific internal services sessions need to reach. The product can’t verify or enforce this, so apply it at your own network boundary on every environment. Session code is model-directed and can attempt connections to arbitrary hosts; default-deny egress at the network layer bounds where those attempts can land. This applies regardless of permission mode: the default pre-approved tool set already includes Bash, so shell egress runs without a prompt even without auto mode. For details on which telemetry each session emits and how to turn it off, see Telemetry.

Authenticate to an egress proxy

Some corporate egress proxies require a Proxy-Authorization header on every connection. The token in that header often rotates too fast to write into the proxy URL you set in HTTPS_PROXY. Set HTTPS_PROXY or HTTP_PROXY to your proxy’s URL as usual, then set --proxy-authorization-command or --proxy-authorization-file to tell the runner where to read the header value from. Both flags require Claude Code v2.1.238 or later.

Choose where the Proxy-Authorization value comes from

Pick the flag that matches how you produce the Proxy-Authorization token:
  • --proxy-authorization-command <command>: choose this for a token you generate on demand. The runner runs the shell command and uses its trimmed stdout as the header value, for example Bearer <token>.
  • --proxy-authorization-file <path>: choose this for a token another process rotates in place. The runner reads the file and uses its trimmed contents as the header value.

Configurations the runner refuses to start with

Each flag also has an environment variable form, listed beside it in the runner CLI flags reference. Before the runner contacts your proxy or the control plane, it checks the flags and their variables, and refuses to start in three cases:
  • Both flags set: one flag plus the other flag’s environment variable counts as setting both.
  • No proxy URL: neither HTTPS_PROXY nor HTTP_PROXY holds an http:// or https:// URL. The runner reads both variables in upper or lower case, and doesn’t consult ALL_PROXY.
  • Either flag passed to the orchestrator subcommand: self-hosted-runner orchestrator doesn’t accept the flags or their environment variables. Pass the flag to each runner the orchestrator starts instead.

What the runner changes while a proxy-authorization flag is set

With either flag set, the runner starts a listener of its own and sends proxy traffic from itself, its lifecycle hooks, and its sessions through that listener. The listener adds the Proxy-Authorization header on the way to your proxy.
  • Listener: the listener is a forward proxy on 127.0.0.1. The runner starts the listener before registering with the control plane, and exits at startup if the listener can’t start.
  • Proxy variables: the runner rewrites whichever of HTTPS_PROXY and HTTP_PROXY you set so that it points at the listener. That rewritten value reaches the runner itself, its lifecycle hooks, and every session it runs.
  • Token rotation: a rotated token takes effect without a restart. For each connection the listener opens to your proxy, the runner runs your command or reads your file again and adds the result as the header.
  • Session environment: a session reaches your proxy only through the listener. In each session’s environment the runner removes ALL_PROXY, removes any spelling of HTTPS_PROXY or HTTP_PROXY that you didn’t set, and pins NO_PROXY to the runner’s own value.
  • Logs: the runner never logs the header value.

Configure git

The runner manages repository checkouts but doesn’t configure git identity or credentials by default. You control the runner’s image and process environment, so you control the git config. Choose one of two approaches:
  • Let the runner configure git: start the runner with --configure-git to have it write the same identity and commit-signing config that Anthropic-hosted sessions use
  • Ship git config in your image: set identity and push credentials yourself, for example to commit under your own bot identity
Git version floors on the runner host: --configure-git SSH commit signing requires Git 2.34 or newer, --use-anthropic-git-proxy requires 2.32 or newer, and resuming sessions from branches pushed by --push-outcome-on-release requires 2.29 or newer. Git 2.24 is sufficient if you omit all three and manage git identity yourself.

Let the runner configure git

Start the runner with --configure-git, or set SELF_HOSTED_RUNNER_CONFIGURE_GIT=1, to have it write global git config at startup:
  • user.name = Claude and user.email = noreply@anthropic.com, matching Anthropic-hosted sessions
  • SSH-format commit and tag signing, routed through a runner-managed shim that signs each commit via Anthropic’s signing service using the session’s own credentials. Signatures are verifiable on GitHub against Anthropic’s published SSH signing key.
  • push.negotiate = true, so git asks your git host which commits it already has before packing a push. Requires Claude Code v2.1.257 or later.
  • core.hooksPath pointing at a runner-managed hooks directory. Its commit-msg and prepare-commit-msg hooks add a Co-authored-by: trailer for the session’s creator to each commit, built from the email in