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.
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 1and the default--drain-grace-sec 0so 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
checkoutlifecycle 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.254in the session container’s network namespace
-
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-accountbounds 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 defaultwarnlogs a violation and still spawns the session,enforcerefuses the session, andoffdisables the scan. The runner scans each repository’s committed settings for:- A grant that resolves outside that session’s own workspace: an
additionalDirectoriesentry, anEdit,Write, orNotebookEditrule inpermissions.allow, or asandbox.filesystem.allowWriteorallowReadentry - A non-empty
envblock - An operator-posture override such as
sandbox.enabled: false
--trust-workspace, and doesn’t cover repository hooks,.mcp.json, or Bash rules; see Permissions and tool approval for where those grants belong. - A grant that resolves outside that session’s own workspace: an
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 includesBash, 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 aProxy-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 exampleBearer <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_PROXYnorHTTP_PROXYholds anhttp://orhttps://URL. The runner reads both variables in upper or lower case, and doesn’t consultALL_PROXY. - Either flag passed to the orchestrator subcommand:
self-hosted-runner orchestratordoesn’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 theProxy-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_PROXYandHTTP_PROXYyou 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 ofHTTPS_PROXYorHTTP_PROXYthat you didn’t set, and pinsNO_PROXYto 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-gitto 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
--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 = Claudeanduser.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.hooksPathpointing at a runner-managed hooks directory. Itscommit-msgandprepare-commit-msghooks add aCo-authored-by:trailer for the session’s creator to each commit, built from the email in