셀프호스티드 환경 프로덕션 배포
기준일: 2026-10-23
공식 기준: Deploy self-hosted environments to production
개요
프로덕션에서 러너는 환경에 세션을 dispatch할 수 있는 모든 사람을 대신해 모델이 지시한 코드를 실행합니다. 이 페이지는 동작하는 환경을 프로덕션으로 올리는 운영자를 위한 것으로 순서는 하드닝 → egress → git 인증 → 배포 레시피 → 트러블슈팅입니다. 첫 러너는 Quickstart, 개념은 Self-hosted environments.
핵심 개념
| 주제 | 핵심 |
|---|---|
| 격리 | 세션당 임시 컨테이너/VM, --capacity 1 + 기본 --drain-grace-sec 0 |
| 자격 증명 | 이미지에 광범위 자격 증명 금지, 세션별 발급(wrapper script) |
| egress | 자체 네트워크 경계에서 default-deny |
| git | --configure-git / 이미지에 git 설정 / --use-anthropic-git-proxy(github.com 전용) |
| 종료 | SIGTERM 드레인, stop timeout ≥ 시작 로그의 This runner needs up to 값 (기본 80초) |
| 스케일 | 고정 플릿(Prometheus 메트릭) 또는 on-demand 러너(orchestrator) |
상세
배포 하드닝
환경에 dispatch할 수 있는 사람은 Anthropic 조직 구성원 전체와, Owner가 라우팅한 범위에서 Claude Tag 채널 세션을 시작할 수 있는 사람입니다. 프로덕션 시스템을 붙이기 전에 점검합니다.
- 세션별 임시 컨테이너: 러너 프로세스마다 새 컨테이너/VM, 종료 시 파기.
--capacity 1및 기본--drain-grace-sec 0. 런너 재시작 간 파일시스템 재사용 금지(의도된 pre-warmed checkout 제외), owner 간 재사용은 절대 금지. 쉘 명령 종료 후 남은 프로세스(daemonize된 서비스)에는 시그널이 가지 않으며 컨테이너/VM 파기가 종료시킵니다. - 이미지에 광범위 자격 증명 금지: 장수 SSH 키, 클라우드 자격 증명, 과권한 PAT 금지. 세션 중 쓰는 push/API 토큰은 wrapper script에서 세션별 발급. 초기 clone은 wrapper 전에 일어나므로
checkoutlifecycle hook 또는 (모든 저장소가 github.com이면)--use-anthropic-git-proxy. - 호스트 GitHub 자격 증명 분리: 세션이 읽을 수 있는 GitHub 자격 증명은 Claude가 그 권한으로 사용합니다. PAT,
gh auth login토큰, 러너 환경의GH_TOKEN을 세션이 읽는 곳에 두지 않습니다(Anthropic-managed git 사용 시 이런 자격 증명이 있으면 managed git 대신 GitHub에 직접 접근). - 환경 secret을 세션 실행 호스트에서 분리: secret은 러너 등록과 큐의 모든 세션 pick-up이 가능합니다. 고정 플릿에서는 모든 러너 호스트에 있고 세션 코드가 읽을 수 있으므로 on-demand 러너(secret은 사용자 코드를 돌리지 않는 orchestrator 호스트에만, 러너는 러너 1개만 등록하는 1회용 work order 수령)를 권장. 고정 플릿은 secret 파일을 모든 세션이 읽을 수 있다고 보고 세션 침해 의심 시 교체.
- default-deny egress, 최소 권한 호스트 IAM(세션은 호스트 아이덴티티 대신 wrapper로 자기 자격 증명 획득)
- 클라우드 metadata 엔드포인트 차단: 서브넷 egress 정책은 link-local metadata 트래픽을 가로채지 못하므로 컨테이너에서 차단합니다. IMDSv2 hop limit 1 / GKE Workload Identity + metadata concealment / 세션 컨테이너 네트워크 네임스페이스에서
169.254.169.254명시 deny. wrapper script와 lifecycle hook도 같은 컨테이너라 동일 적용. 세션 JWT 토큰 교환은 허용된 egress로 자체 토큰 서비스에, 또는 EKS의 IRSA 같은 파일 기반 web identity 사용. - 러너별 파일시스템 격리:
--hooks-dir, wrapper script, 호스트~/.claude/는 세션에 read-only(이미지에 내장 또는 read-only 마운트). - dispatch에 환경별 접근 제어 없음: 조직 구성원 누구나 어느 환경에든 dispatch 가능. 러너 호스트에는 dispatch 가능한 모든 사람이 읽어도 되는 데이터·자격 증명만 둡니다.
--lock-to-account는 호스트가 실행하는 계정을 제한할 뿐 dispatch 가능자를 좁히지 않습니다. Owner는 Cloud environments 페이지에서 Anthropic 호스티드 환경을 조직 전체에서 숨겨 셀프호스티드만 선택지로 만들 수 있습니다. - repo-settings guard:
--confine-repo-settings모드warn(기본, 위반 로그 후 세션 spawn) /enforce(세션 거부) /off. 저장소에 커밋된 설정에서 (a) 세션 워크스페이스 밖을 가리키는 grant(additionalDirectories,permissions.allow의Edit/Write/NotebookEdit규칙,sandbox.filesystem.allowWrite/allowRead), (b) 비어 있지 않은env블록, (c)sandbox.enabled: false같은 운영자 자세 override를 검사합니다.--trust-workspace와 무관하게 실행되고 저장소 hook·.mcp.json·Bash 규칙은 다루지 않습니다.
조직이 IP allowlisting을 쓰면 러너·세션 컨테이너(on-demand면 orchestrator 호스트 포함)의 공인 egress 주소를 시작 전에 추가하되, allowlist를 네트워크 통제 수단으로 의존하지 마세요.
네트워크 요구사항
항상 필요:
| 호스트 | 포트 | 용도 |
|---|---|---|
api.anthropic.com |
443 (Anthropic-managed git은 WSS) | 컨트롤 플레인·세션 스트리밍, 모델 추론, feature flag, 분석, JWKS, 커밋 서명, --use-anthropic-git-proxy 시 managed git |
내 git 호스트 (github.com, GHE 등) |
443 / 22 | clone/push. --use-anthropic-git-proxy 러너는 github.com 경로 불필요(--push-outcome-on-release 또는 post-session hook push 시는 필요) |
구성에 따라 필요:
| 호스트 | 필요한 경우 |
|---|---|
downloads.claude.ai |
네이티브 설치/업데이트 시(install.sh는 claude.ai에서 제공), 세션이 공식 마켓플레이스 플러그인을 설치할 때 |
storage.googleapis.com |
/plugin의 설치 수·메타데이터 |
code.claude.com, claude.com |
claude-code-guide 에이전트 문서 조회, 사전 승인된 WebFetch (차단해도 문서 조회만 영향) |
*.frame.claudeusercontent.com |
Artifact tool 사용 가능 조직. 러너에 CLAUDE_CODE_DISABLE_ARTIFACT=1로 항상 비활성 |
registry.npmjs.org |
플러그인 설치(npm 소스·Node.js 의존성), npx 실행 MCP 서버 |
http-intake.logs.us5.datadoghq.com |
CLAUDE_CODE_BYOC_ENABLE_DATADOG=1일 때만(기본 off) |
browser-intake-us5-datadoghq.com |
에러 리포팅 활성 시. DISABLE_ERROR_REPORTING=1 또는 DISABLE_TELEMETRY=1로 억제 |
클라우드 프로바이더 엔드포인트 (bedrock-runtime.us-east-1.amazonaws.com, aiplatform.googleapis.com 등) |
러너가 Bedrock/Agent Platform으로 모델 요청을 보낼 때 |
허용 불필요: statsig.anthropic.com, *.sentry.io, claude.ai, platform.claude.com(옛 체크리스트에 있으나 러너는 접근 안 함), mcp-proxy.anthropic.com(셀프호스티드 세션은 미사용, 커넥터는 api.anthropic.com 경유).
호스트 측 흐름은 claude.ai에 접근합니다. 원라인 설치 프로그램(install.sh), 인터랙티브 claude auth login(claude.ai, claude.com, platform.claude.com; 가이드 설정·doctor 로그인 모드·CI dispatch에서 사용; 브라우저는 hcaptcha.com, *.hcaptcha.com, challenges.cloudflare.com, SSO 시 sso.claude.com도 로드). 세션 컨테이너 egress를 넓히지 말고 이를 허용하는 호스트에서 실행하세요.
Default-deny egress: 러너·세션 컨테이너를 위 표, git 호스트, 필요한 내부 서비스로만 outbound가 제한된 세그먼트/네임스페이스에 배포합니다. 제품이 검증·강제할 수 없으므로 자체 경계에서 모든 환경에 적용합니다. 기본 사전 승인 도구에 Bash가 있어 permission mode와 무관하게(auto mode 없이도) 쉘 egress가 프롬프트 없이 실행됩니다.
egress 프록시 인증 (2.1.238+)
HTTPS_PROXY/HTTP_PROXY는 평소처럼 프록시 URL로 설정하고, Proxy-Authorization 값의 출처를 지정합니다.
--proxy-authorization-command <command>: 온디맨드 생성 토큰. 셸 명령의 trim된 stdout이 헤더 값(예:Bearer <token>)--proxy-authorization-file <path>: 다른 프로세스가 제자리 갱신하는 토큰. 파일의 trim된 내용이 헤더 값
러너가 시작을 거부하는 경우: 두 플래그 동시 지정(한 플래그 + 다른 플래그의 환경 변수 포함), HTTPS_PROXY/HTTP_PROXY 어느 쪽도 http:///https:// URL이 아님(대소문자 둘 다 읽고 ALL_PROXY는 보지 않음), orchestrator에 플래그 전달(대신 orchestrator가 시작하는 각 러너에 전달).
플래그 설정 시 러너는 127.0.0.1에 포워드 프록시 리스너를 띄워(등록 전 시작, 실패 시 종료) 러너·hook·세션 프록시 트래픽을 그리로 보내 헤더를 붙입니다. HTTPS_PROXY/HTTP_PROXY는 리스너를 가리키도록 재작성되고, 토큰 갱신은 재시작 없이 연결마다 명령 재실행/파일 재읽기로 반영됩니다. 세션 환경에서는 ALL_PROXY와 설정하지 않은 HTTPS_PROXY/HTTP_PROXY 표기를 제거하고 NO_PROXY를 러너 값으로 고정합니다. 헤더 값은 로그에 남지 않습니다.
git 설정
러너는 checkout을 관리하지만 기본으로 git identity/자격 증명을 설정하지 않습니다. 선택지는 (1) --configure-git, (2) 이미지에 git 설정. github.com 저장소는 --use-anthropic-git-proxy 또는 CLAUDE_RUNNER_USE_GIT_PROXY=1도 가능합니다.
git 버전 하한: --configure-git SSH 커밋 서명 2.34+, --use-anthropic-git-proxy 2.32+, --push-outcome-on-release로 푸시된 브랜치 재개 2.29+. 셋 다 쓰지 않고 identity를 직접 관리하면 2.24면 충분.
러너가 git 설정 (--configure-git 또는 SELF_HOSTED_RUNNER_CONFIGURE_GIT=1): 시작 시 전역 git config를 씁니다.
user.name = Claude,user.email = noreply@anthropic.com- SSH 형식 커밋·태그 서명(러너 관리 shim이 세션 자격 증명으로 Anthropic 서명 서비스를 통해 서명, GitHub에서 Anthropic 공개 SSH 서명 키로 검증 가능)
push.negotiate = true(2.1.257+)core.hooksPath-> 러너 관리 hooks 디렉터리(commit-msg,prepare-commit-msg가 세션 생성자에 대한Co-authored-by:트레일러 추가,CCR_SESSION_ACCOUNT_EMAIL에서 생성·미설정이면 생략). 이미지가 이미core.hooksPath를 설정하고 managed git을 안 쓰면 설정 유지 +[runner:git]경고.
서명은 git 2.34+ 필요(시작 시 확인 후 오류 종료). push 자격 증명은 설정하지 않습니다. 2.1.280+ 러너에서는 checkout/post-session hook의 커밋도 세션으로 서명됩니다(트레일러 없음). 커밋 메시지의 Claude-Session: <url> 트레일러와 PR 설명의 세션 URL을 끄려면 러너 호스트 ~/.claude/settings.json에서 attribution.sessionUrl을 false로 하고 재시작.
이미지에 git 설정:
RUN git config --system user.name "Claude" && \
git config --system user.email "noreply@anthropic.com"
identity가 없으면 git commit이 Please tell me who you are로 실패합니다. 봇 identity를 써도 러너는 덮어쓰지 않습니다. 공유 이미지에 장수/광범위 push 자격 증명을 넣지 말고 wrapper script에서 세션 JWT의 생성자 identity로 단기·최소 범위 토큰을 발급하세요. 이미지 수준이 불가피하면(읽기 전용 deploy key 등) 최대한 좁게: 저장소 하나로 제한한 SSH deploy key + url.<base>.insteadOf, 최소 범위 토큰을 반환하는 credential.helper, 좁은 키를 가리키는 GIT_SSH_COMMAND.
러너의 내장 clone/fetch는 프롬프트를 끕니다. GIT_TERMINAL_PROMPT=0, SSH BatchMode=yes(GIT_SSH_COMMAND 뒤에 덧붙임), GCM_INTERACTIVE=never, core.askPass 비움(askpass는 GIT_ASKPASS 환경 변수로). 거부되거나 미설정이면 몇 번 재시도 후, 세션이 결과를 push하는 저장소라면 저장소 준비가 실패합니다. 읽기 전용 저장소는 일부 상황에서 건너뜁니다. GIT_SSH_COMMAND/GIT_ASKPASS가 가리키는 프로그램과 그 명령줄의 키·파일은 세션이 쓸 수 없는 곳에 두세요. checkout 디렉터리 소유 uid가 다르면 git이 거부하므로 다음을 추가합니다.
RUN git config --system --add safe.directory '*'
Anthropic git proxy (Anthropic-managed git)
이미지에 SSH 키/credential helper/.netrc 없이 Anthropic이 세션 git을 서빙합니다. 사용자 세션은 세션 생성자의 GitHub OAuth 토큰, bot/agent 세션은 조직의 GitHub App installation 토큰을 쓰며 --git-host-rewrite/--git-ssh-rewrite는 proxy가 서빙하는 저장소에 효과가 없습니다. 기본 off이며 제약이 있습니다.
- github.com 전용: 세션의 모든 저장소가 github.com이어야 하고 GHE는 미지원
- 세션 저장소에 대한 자격 증명만 (비공개 서브모듈, git으로 받는 의존성, 다른 저장소의 플러그인 마켓플레이스는 제외 -> 세션 생성 시 필요한 저장소를 모두 추가. 클라우드 예약 작업도 동일)
- 브랜치 push만 (브랜치 삭제·태그 push 실패)
- GitHub 계정 연결 필요(없으면
GitHub authentication required. Please reconnect your GitHub account.로 시작 실패) --capacity 1- 전역 git config 삭제·대체: 시작 시와 각 세션 전에 백업 없이 러너 실행 사용자의 전역 git config를 삭제·교체.
--configure-git이 쓰는 설정은 유지. 전용 사용자/컨테이너로 실행하고 비밀이 아닌 설정은 system git config에 둡니다. --push-outcome-on-releasepush와post-sessionhook의 push는 여전히 호스트 자격 증명·github.com 경로 사용- 세션마다 서빙 여부를 Anthropic이 결정, 서빙 안 하면 세션 시작 실패
켜기 요건: Claude Code v2.1.267+(이전은 플래그를 받지만 요청을 보고하지 않음), --capacity 1(기본), Git 2.32+. Kubernetes/Compose 레시피는 --capacity 4이므로 proxy를 추가하면 1로 바꾸지 않으면 매번 시작 시 종료합니다.
claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>' --use-anthropic-git-proxy
시작 시 Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy)를 출력하고, 서빙하는 세션마다 [runner:session] 줄에 governed git ACTIVE를 로그합니다.
세션 시작 실패 시 러너 로그에서 api.anthropic.com 주소와 /git_proxy/가 든 git 오류를 찾습니다.
governed git ACTIVE도withheld도 없음: 2.1.267 미만 러너 -> 업그레이드the server withheld Anthropic-managed git for this session([runner:warn]): 저장소 하나라도 github.com이 아니면(GHE 등) 서빙 안 됨 -> proxy 끄기. 모두 github.com이면 세션 ID와 함께 Anthropic 계정팀에 보고remote: access denied by the git proxy: 조직 정책 등으로 거부, 뒷부분에 이유GitHub authentication required: 생성자가 claude.ai에서 GitHub 재연결 필요
끄기: 플래그 제거(CLAUDE_RUNNER_USE_GIT_PROXY는 파드 스펙/Compose에서 제거, 셸이면 unset CLAUDE_RUNNER_USE_GIT_PROXY) -> 모든 git 호스트(github.com 포함)에 프롬프트 없이 동작하는 자격 증명 제공(proxy 중 삭제된 전역 config의 자격 증명은 사라짐) -> 각 git 호스트로 443/22 경로 개방 -> 러너 재시작 후 실패한 세션 재시작.
unset CLAUDE_RUNNER_USE_GIT_PROXY
GitHub CLI 없이 GitHub API (2.1.287+): 이미지에 GitHub CLI가 없으면 managed git 러너용 내장 gh가 gh api(REST 전용) 하나를 제공합니다. {owner}/{repo}를 현재 저장소로 채웁니다.
gh api repos/{owner}/{repo}/pulls -f title='Fix' -f head='my-branch' -f base='main'
GitHub 자격 증명은 Anthropic 쪽에서 공급되어 이미지에 토큰이 불필요합니다. 세션별로 governed git ACTIVE 줄에 gh_path_shim=true가 나오면 제공되고 아니면 gh가 없습니다. --jq를 쓰려면 jq 설치. 세션 환경에 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC가 설정되면 내장 gh가 없습니다. 이미지에 GitHub CLI가 있으면 그것을 사용합니다(2.1.288: 거부된 gh 명령이 gh api 등가물을 출력, --paginate가 모든 페이지를 따라감).
managed git + 사설 CA (2.1.283+): 시스템 인증서 저장소에 CA를 설치하면 변수 없이 git이 신뢰합니다. GIT_SSL_CAINFO는 CA PEM 파일 경로(예: GIT_SSL_CAINFO=/etc/ssl/corp-ca.pem). GIT_SSL_NO_VERIFY는 re-signing 프록시 뒤에서 도움이 안 됩니다(러너의 managed git clone은 변수와 무관하게 인증서를 검사). 러너는 시스템 CA 번들(/etc/ssl/certs/ca-certificates.crt 또는 /etc/pki/tls/certs/ca-bundle.crt)과 GIT_SSL_CAINFO 파일(러너 사용자가 읽을 수 있고 PEM CERTIFICATE 블록, 1 MiB 이하)로 세션별 인증서 파일을 만들고, 못 만들면 [runner:warn] ... did not build the certificate file을 로그합니다. 세션 내부 git에는 http.sslCAInfo / http.<url>.sslCAInfo, http.sslVerify=false / http.<url>.sslVerify=true 설정을 줍니다. checkout/post-session hook은 GIT_SSL_CAINFO를 그대로 상속합니다. GIT_SSL_NO_VERIFY는 세션 저장소가 managed git에 있을 때 http.sslVerify=false 설정으로 바뀌지만, 함께 들어가는 http.<url>.sslVerify=true 항목 때문에 Anthropic-managed git은 계속 인증서를 검사합니다(세션 내부 git과 command hook도 같음). 매 managed git 세션마다 governed git: GIT_SSL_CAINFO is set 또는 governed git: GIT_SSL_NO_VERIFY is set로 시작하는 [runner:warn] 줄에 조치 필요 여부가 나옵니다.
사설 네트워크용 git URL 재작성
컨트롤 플레인이 HTTPS로 저장소 URL을 내려줍니다. 반복 가능한 두 플래그로 clone 전에 재작성합니다.
--git-host-rewrite <from>=<to>: split-horizon DNS(Anthropic은 외부 호스트명, 러너는 내부 호스트명)--git-ssh-rewrite <host>: SSH만 받는 git 호스트 또는 SSH 키만 있는 호스트.--git-ssh-rewrite github.com이면https://github.com/owner/repo를git@github.com:owner/repo로 재작성. SSH가 프롬프트 없이 연결돼야 함.
호스트 재작성이 먼저 실행되므로 둘 다 필요하면 내부 호스트명을 --git-ssh-rewrite에 넣습니다. 완전한 제어는 checkout lifecycle hook.
러너 이미지 빌드
Anthropic은 미리 빌드된 러너 이미지를 게시하지 않습니다. claude 바이너리 기반으로 툴체인(런타임, 컴파일러, 패키지 매니저, MCP 사이드카)을 얹어 직접 빌드합니다.
FROM debian:bookworm-slim
ARG CLAUDE_CODE_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates openssh-client jq \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://downloads.claude.ai/claude-code-releases/${CLAUDE_CODE_VERSION:?set with --build-arg CLAUDE_CODE_VERSION}/linux-x64/claude" \
-o /usr/local/bin/claude && chmod +x /usr/local/bin/claude
RUN git config --system user.name "Claude" \
&& git config --system user.email "noreply@anthropic.com" \
&& git config --system --add safe.directory '*'
ENTRYPOINT ["claude"]
ARM 노드는 linux-arm64, musl 이미지(Alpine)는 linux-x64-musl/linux-arm64-musl. 러너는 2.1.224+ 필요.
docker build \
--build-arg CLAUDE_CODE_VERSION="$(curl -fsSL https://downloads.claude.ai/claude-code-releases/stable)" \
-t <your-registry>/claude-runner:latest .
명령 치환이 현재 stable 버전을 조회하므로 새 stable 이후 같은 명령이 새 바이너리로 다운로드 레이어를 재빌드합니다. 재현 가능한 빌드는 버전을 직접 넘기고, stable보다 새로운 릴리스(새 모델이 요구하는 버전)가 필요하면 조회 URL의 stable을 latest로 바꿉니다. 아래 레시피는 --capacity 4라 세션별 컨테이너 격리가 아니므로, 프로덕션에서는 --capacity 1로 컨테이너당 세션 1개를 쓰거나 on-demand 러너를 사용하세요.
CPU·메모리 산정
러너 프로세스가 아니라 세션(Claude Code 프로세스 + 빌드·테스트·패키지 설치·MCP 서버) 기준으로 잡습니다. 세션 1개 시작값(Kubernetes 요청/한도 기준): 메모리 request/limit 각 4 GiB(setup 최소 4 GB, 둘을 같게), CPU request 2 / limit 4.
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 4Gi
대표 빌드의 피크 CPU/메모리를 측정해 시작값을 올립니다. --capacity는 동시 세션 수만 제한할 뿐 자원을 나누지 않으므로 용량 N이면 세션 1개 값 x N. 세션별 상한은 wrapper script에서 적용. 아래 K8s/Compose 레시피는 --capacity 4이고 자원 제한이 없으므로 직접 추가하세요.
Kubernetes
러너는 기본 8080 포트(--health-port)에서 GET /healthz를 제공합니다. 프로세스가 살아 있으면 200이므로 멈춘 러너는 못 잡습니다 -> /metrics의 last_poll_age_seconds에 알림을 거세요. 90초 termination grace period를 둡니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: claude-runner
namespace: claude-runners
spec:
replicas: 3
selector:
matchLabels:
app: claude-runner
template:
metadata:
labels:
app: claude-runner
app.kubernetes.io/part-of: claude-code-self-hosted-runner
spec:
terminationGracePeriodSeconds: 90
containers:
- name: runner
image: <your-registry>/claude-runner:latest
args:
- self-hosted-runner
- --environment-secret-file
- /etc/claude/environment-secret
- --capacity
- "4"
- --base-dir
- /workspace
volumeMounts:
- name: environment-secret
mountPath: /etc/claude
readOnly: true
ports:
- name: health
containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: environment-secret
secret:
secretName: claude-runner-environment-secret
kubectl create namespace claude-runners
admin UI의 Copy environment key로 복사한 값을 로컬 파일로 만든 뒤((umask 077 && cat > ./environment-secret), 붙여넣기, Enter, Ctrl-D) Secret을 만들고 파일을 삭제합니다.
kubectl create secret generic claude-runner-environment-secret -n claude-runners --from-file=environment-secret=./environment-secret
Docker Compose
종료할 때마다 재시작합니다(크래시와 드레인 후 정상 종료 모두). Docker restart policy는 같은 컨테이너를 쓰기 레이어 그대로 재시작하므로 하드닝 권고의 "새 파일시스템"이 아닙니다. 평가용이며 프로덕션은 실행마다 컨테이너를 재생성하거나 오케스트레이터를 쓰세요. 계속 종료하는 컨테이너는 Docker가 재시작 간격을 늘립니다.
services:
claude-runner:
image: <your-registry>/claude-runner:latest
command:
- self-hosted-runner
- --environment-secret-file
- /run/secrets/environment-secret
- --capacity
- "4"
- --base-dir
- /workspace
secrets:
- environment-secret
restart: always
stop_grace_period: 90s
secrets:
environment-secret:
file: ./environment-secret
셧다운 타이밍
SIGTERM 후 러너는 오케스트레이터가 죽이기 전에 세션을 정리할 시간이 필요합니다. 시작 시 로그에 This runner needs up to 80s(기본 설정)로 출력합니다. stop timeout(Kubernetes terminationGracePeriodSeconds, Compose stop_grace_period)을 최소 그 값으로 설정하세요. Kubernetes 기본은 30초라 설정하지 않으면 러너가 끝나기 전에 파드가 중지될 수 있습니다.
SIGTERM 시 새 세션 수락을 멈추고(--defer-shutdown-max-min을 설정하지 않았다면) 드레인합니다.
--drain-wait-sec(기본0)초까지 진행 중인 턴이 끝나길 대기- 각 세션의 프로세스 트리 종료(Claude가 실행 중이던 명령 포함, 쉘 명령 종료 후 남은 프로세스는 제외)
post-sessionlifecycle hook 실행
드레인 중에도 폴링을 계속해 세션이 다른 러너로 넘어가지 않게 합니다(post-session hook이 미커밋 작업을 저장하는 동안). 로그 시간의 합은 --drain-wait-sec(기본 0) + --session-stop-grace-sec(기본 5) + --post-session-hook-timeout-sec(기본 60) + 고정 여유 15 + --push-outcome-on-release 시 30 = 기본 0+5+60+15 = 80초입니다. --capacity는 모든 세션을 동시에 드레인하므로 시간을 늘리지 않습니다. --retire-at을 쓰면 retire 시각과 호스트 종료 시각 사이에 일반 턴 완료 + 백그라운드 태스크 대기 + 로그 시간을 확보하고(retire 시각은 매 실행마다 date +%s + 러너 예상 수명으로 계산), --defer-shutdown-max-min을 쓰면 stop timeout이 설정한 분 + 드레인 시작 전 추가 대기(기본 75초)도 포함해야 합니다(시작 로그에 더 긴 시간도 출력).
재시작 전 턴 완료 대기: --drain-wait-sec을 설정하고 stop timeout을 그 값 + 80초 이상으로 올립니다. 기본 0이면 진행 중 턴이 끊기고 세션이 푸시되지 않은 작업 없이 다른 러너에서 재개됩니다. --drain-wait-sec은 상한이라 끝나면 일찍 종료합니다. 예: --drain-wait-sec 120이면 레시피의 90을 200으로. 다른 셧다운 설정을 바꿨다면 러너를 직접 한 번 띄워 시작 로그의 This runner needs up to 값(--defer-shutdown-max-min이면 다음 줄 Size the stop timeout to at least 뒤의 더 큰 값)을 쓰세요. stop timeout이 먼저 끝나면 post-session hook 없이 러너가 kill되고 deregister하지 않아 컨트롤 플레인이 requeue하는 데 몇 분 이상 걸립니다.
재시작 전 상태 메시지: 세션을 끊는 재시작 전에 Owner가 Cloud environments 관리 페이지에서 환경에 상태 메시지를 게시하면 claude.ai/code의 해당 환경 세션에 표시됩니다. 만료를 설정하거나 복구 후 제거하세요.
첫 시그널 이후로 드레인 연기 (2.1.238+): --defer-shutdown-max-min <n>는 첫 SIGTERM/SIGINT에서 새 작업 수락을 멈추되 보유 세션을 최대 n분 동안 계속 서빙합니다(폴링 유지, requeue 방지). 시그널 이후 3단계:
- 처음
n분: 정상 서빙,--startup-timeout-min/--kill-session-after-min계속 적용.--release-idle-session-min을 설정했다면 그만큼 유휴인 세션을 해제(없으면 유휴 세션은 러너에 남음) n분 경과: 보유한 모든 세션 해제(유휴 여부 무관). 턴 진행 중이면 턴 종료, 백그라운드 태스크는 최대 60초 더 기다린 뒤 해제. 해제된 세션은 사용자의 다음 메시지에 새 러너에서 재개- 해제 후 grace 경과(기본 75초,
--drain-wait-sec가 60초 초과면--drain-wait-sec+ 15초): 남은 세션을 드레인, 컨트롤 플레인이 즉시 다른 러너로 requeue
세션이 0이 되면 어느 단계에서든 exit 0. 두 번째 시그널은 단계를 건너뛰고 즉시 드레인(첫 시그널에서 --defer-shutdown-max-min 없이 하는 것과 동일), 드레인 중 다음 시그널은 강제 종료합니다. stop timeout은 n분 + 해제 후 grace + 드레인의 합 이상이어야 하며 기본에서는 n분 + 155초입니다(시작 시 합계 출력). 그 시간을 줄 수 없으면 --defer-shutdown-max-min을 설정하지 마세요.
실행 중 post-session hook에 닿는 것: hook과 세션 자식은 러너와 별개의 POSIX 프로세스 그룹입니다. 이미 드레인 중에 받은 SIGTERM은 러너를 즉시 강제 종료합니다(--defer-shutdown-max-min이 없으면 두 번째 SIGTERM). 프로세스 그룹 전체 시그널(kill -- -<pid> 등)은 러너와 checkout hook 하위 프로세스에는 닿지만 실행 중 post-session hook이나 세션 자식에는 닿지 않습니다. cgroup 전체 kill(systemd 기본 KillMode=control-group, grace 만료 시 Kubernetes SIGKILL)은 hook을 포함한 모든 것에 닿으므로 grace가 드레인 전체를 덮어야 합니다. hook이 --post-session-hook-timeout-sec을 초과하면 hook의 프로세스 그룹 전체에 SIGTERM, 2초 후 SIGKILL. 러너는 드레인 시작과 강제 종료 시 아직 실행 중인 post-session hook 수를 로그합니다.
base directory와 capacity는 모든 러너에서 동일하게
러너가 세션 도중 죽으면 같은 환경의 다른 러너가 세션을 이어받고, 그 러너는 자신의 --base-dir/--capacity로 checkout 경로를 도출합니다. --capacity 1은 --base-dir 바로 아래, 1 초과는 세션별 worktree를 씁니다. 값이 다르면 재개된 세션의 작업 디렉터리가 바뀌어 에이전트가 기록한 절대 경로가 사라집니다. 환경 내 모든 러너에 같은 --base-dir/--capacity를 쓰고(인스턴스 ID/호스트명 같은 호스트별 값 금지) 두 플래그를 명시적으로 전달하세요. 기본 /workspace이며 시작 시 등록 전에 디렉터리를 만들고 쓰기를 확인해 실패하면 cannot create or write to base directory로 종료합니다. root 러너는 기본 /workspace를 직접 만들고, non-root는 디렉터리를 만들어 소유권을 주거나 소유 중인 디렉터리를 지정하세요.
pre-warmed checkout 재사용
대용량 저장소는 clone이 세션 시작을 좌우합니다. checkout hook이 없으면 러너는 저장소당 canonical clone을 <base-dir>/<repo-owner>/<repo>에 두고 세션 간 재사용합니다.
--capacity 1: 요청 ref를 fetch,HEADdetach, hard reset (변경이 적으면 거의 즉시)--capacity1 초과: 해당 clone에 fetch 후 세션마다 별도 worktree를 checkout. pre-warm은 다운로드만 절약
이미지에 clone을 넣거나(모든 새 컨테이너가 warm clone으로 시작), --lock-to-account로 한 사용자 계정에 미리 고정한 러너에서 --base-dir을 영구 볼륨으로 지정합니다(고정 러너는 Claude Tag 채널 세션을 받지 않음).
- 어떤 clone 형태든 그대로 사용: full/shallow/single-branch. 기존 clone에 fetch할 때
--depth를 쓰지 않음.CLAUDE_RUNNER_FETCH_DEPTH(full,0또는 숫자, 기본 50)는 clone이 없을 때의 cold clone에만 적용 - 추적 파일 변경은 reset되고 untracked 파일은 유지(
git clean안 함) <base-dir>/_sessions/아래 세션별 디렉터리(Claude config의 로컬 transcript 사본, 업로드 파일, worktree,checkouthook checkout 등)는 기본으로 세션 종료 후에도 남고 이후 같은 디스크의 세션이 읽을 수 있음 -> 영구--base-dir이면 증가분에 맞게 볼륨 산정(Compose 레시피 포함 같은 파일시스템에서 재시작하는 모든 구성에 해당)--remove-session-state(2.1.268+): 세션 종료 시 세션별 디렉터리 삭제(best-effort, 정리 전 kill되면 남음). canonical clone과 호스트 다른 위치에 쓴 파일은 남음- git proxy 사용 시 reset이 checkout으로: 세션 전에
.git/을 정리(object store와 shallow 상태 유지, index 삭제) -> 매 세션 전체 working-tree checkout. 기존 clone에 fetch하는 점은 동일. 서브모듈 pre-warm은 미지원. 또한.git/refs/는 유지하고.git/reftable/을 제거하므로reftableref 형식으로 pre-warm한 clone은 ref를 잃습니다 ->filesref 형식으로 pre-warm - 긴 clone은 우회가 불필요: git 작업마다 120초 무진행 watchdog + 30분 hard cap
버전 고정
세션 자식은 기본으로 러너의 바이너리를 실행하고 러너가 세션 내 auto-update를 끕니다. 따라서 호스트에 설치하거나 이미지에 빌드한 버전을 모든 세션이 실행하며 호스트 업데이트는 러너가 다음에 시작될 때 반영됩니다.
- 고정 전에 모델별 필요 Claude Code 버전을 확인하세요. 요구보다 낮으면 서버가 "Claude Code does not support this model"로 요청을 거부
- 플릿 고정: 버전 고정 이미지 빌드 또는 베어 호스트에서 특정 버전 설치 + auto-update 비활성
- 고정 플릿 업그레이드: changelog 확인 -> 새 버전 설치/이미지 재빌드 -> 러너 재시작
- on-demand 러너 업그레이드:
spawn-runnerhook이 시작하는 이미지를 변경. 이미 떠 있는 러너(--min-idlestandby 포함)는 종료 전까지 버전 유지, work order가 1회용이므로 재시작 금지 --exec-path또는commandhook으로 다른 바이너리를 쓰면 그 버전이 실행되므로 러너와 함께 업데이트- 플러그인 마켓플레이스도 auto-update하지 않으므로 바이너리를 고정한 채 플러그인만 자동 업데이트하려면 러너 환경에
FORCE_AUTOUPDATE_PLUGINS=1
플릿 스케일
owner당 1러너 고정 때문에 최소 레플리카 수는 동시에 활성인 사용자 + Claude Tag 에이전트 수이며 --capacity는 한 owner의 세션 병렬성이지 owner 간이 아닙니다. 고정 플릿은 러너가 제공하는 Prometheus 메트릭으로 스케일하고, on-demand 러너는 claude self-hosted-runner orchestrator가 러너 없이 큐에 쌓인 세션을 폴링해 spawn-runner hook으로 세션당 하나씩 부팅합니다.
알려진 문제와 제한
커넥터 트래픽이 네트워크를 벗어남: claude.ai 커넥터(GitHub, Slack, Linear 등)는 러너가 아니라 Anthropic 인프라에서 호출되어
api.anthropic.com을 경유합니다. 세션에서 빼려면allowedMcpServers/deniedMcpServers를 사용합니다. Anthropic이 전달하는 커넥터에도 적용되어 다른 서버용 allowlist를 두면 전달 커넥터도 막히므로, URL 기반 allowlist와 함께 쓰려면 다음 Anthropic 프록시 경로를 추가합니다.https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
트래픽이 네트워크 안에 있어야 하면 동등한 도구를 러너 이미지의 로컬 MCP 서버로 실행하세요.
일부 세션은 유휴로 간주되지 않음: 끝나지 않는 백그라운드 태스크가 있거나 실행 중 도구 호출 내부에서 승인을 기다리는 세션은 유휴가 아니라
--release-idle-session-min이 슬롯을 해제하지 않습니다. 항상--kill-session-after-min을 하드 백스톱으로 함께 설정하세요(가장 긴 예상 세션보다 크게, 예: 8시간이면--kill-session-after-min 480). 2.1.260+ 러너는 한도에 도달해도 즉시 종료하지 않고 grace window(기본 15분,SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS로 변경)를 줍니다. 사용자 대기 중이면 해제, 턴이 끝나고 백그라운드 태스크만 있으면 최대 60초 후 해제, 턴이 진행 중이면 턴 종료/사용자 대기 시 해제, window 종료 시에도 남으면 종료(진행 중 턴 작업 유실). 해제된 세션은 새 clone에서 재개하므로 푸시 안 한 작업은 사라집니다. 2.1.260 이전에는 한도에서 모든 세션을 종료.재개된 세션은 푸시되지 않은 작업을 잃음: 새 러너가 시작 브랜치에서 다시 clone합니다. 커밋된 작업은 환경 내 모든 러너에
--push-outcome-on-release를 설정해 보존합니다(없는 러너는 시작 브랜치에서 재개). 해제 전에 세션 결과 브랜치를 best-effort push하며 호스트 자격 증명을 쓰고(managed git 러너 포함) 미커밋 변경은 여전히 유실.checkouthook으로 받은 저장소는 push되지 않으므로post-sessionhook에서 스냅샷. 활성화 전에 소스 remote의claude/*ref push 권한을 제한하세요(재개 시 push한 사람을 검증하지 않고 fetch).세션 도중 추가한 저장소 clone 실패 가능: Claude가 HTTPS
git clone을 쓰므로--use-anthropic-git-proxy가 없고 호스트에서 읽을 수 없으면 git 인증 오류. 가능하면 생성 시 필요한 모든 저장소 선택.일부 커넥터가 셀프호스티드 세션에 나타나지 않음: claude.ai 설정에서 연결하지 않은 커넥터는 목록에 없고 연결 프롬프트도 없음. 먼저 연결 후 새 세션을 시작(실행 중인 세션에 커넥터를 추가해도 도구가 생기지 않음).
세션을 archive/delete하거나 세션 메뉴에서 러너를 해제하는 경우:
--push-outcome-on-releasepush를 기대하지 마세요. 커밋을 보존하려면post-sessionhook에서HEAD를 rescue 브랜치로 push(예제가 스냅샷 커밋 후에만 push하면 변경이 없어도 push하도록 수정).
문제는 Anthropic 계정팀에 문의합니다.
트러블슈팅
러너 호스트에서 doctor 서브커맨드가 러너 로그·상태가 붙은 인터랙티브 Claude Code 세션을 시작합니다. 먼저 claude auth login해야 환경·러너·큐 세션을 조회할 수 있고, 로그인 없이(API 키 등) 쓰면 로컬 health 엔드포인트·메트릭·러너 로그로 제한되며 로그는 러너를 --log-file로 시작한 경우만 읽습니다.
claude self-hosted-runner doctor
러너가 환경에 나타나지 않음: 호스트가
api.anthropic.com에 HTTPS로 닿는지, 환경 secret이 유효한지, 시계가 5분 이내인지 확인. 인증 실패 시 러너가 거부 이유와 함께[runner:fatal]을 로그.시작 시
cannot create or write to base directory:--base-dir(기본/workspace) 소유권 수정 또는 쓰기 가능한 경로 지정.[runner:fatal]이 base directory 확인 타임아웃이라면 hung NFS/CSI 마운트이므로 권한이 아니라 마운트 상태를 점검. 두 시작 실패는--log-file을 열기 전에 stderr로 출력되므로 터미널/컨테이너 로그에서 찾으세요. v2.1.225 이전에는 시작 시 base directory를 검사하지 않아 pick-up 후 세션이 실패.시작 시
RegisterRunner failed:+ TLS 인증서 오류(예:self signed certificate in certificate chain): TLS 검사 프록시 뒤 등. 러너 환경에NODE_EXTRA_CA_CERTS를 프록시 루트 CA의 PEM 파일로 설정(세션은 상속). git은 읽지 않으므로 해당 프록시를 거치는 clone은GIT_SSL_CAINFO도 설정.시작 스크립트에
Resource temporarily unavailable쓰기 오류로 종료: 스크립트가 러너/orchestrator와 stdout/stderr를 공유하는데 러너가 그 공유 파이프를 non-blocking으로 바꿔서 가득 차면 쓰기가 실패(set -e면 스크립트 종료). 출력을 파일로 보내세요.&앞에 리다이렉트를 붙입니다.claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret >> "$HOME/claude-runner.log" 2>&1--log-file만으로는 stdout/stderr 쓰기가 계속되므로 이 셸 리다이렉트를 쓰고, 이미--log-file을 쓴다면 유지합니다.Orchestrator가 TLS 인증서 오류의
poll failed:를 로그하며 재시도: orchestrator 환경(그리고spawn-runnerhook이 시작하는 러너)에NODE_EXTRA_CA_CERTS설정. 수정 확인은 orchestrator/healthz본문의connected가true인지.세션이 큐에 머무름: 온라인 러너가 모두 다른 owner에 고정됐을 수 있음. 러너의
claude_code_self_hosted_runner_locked_account메트릭 또는[runner:health]로그 줄의locked_account로 확인(act.email클레임이 있는 세션 토큰을 받은 뒤에만 이메일 표시, Claude Tag 에이전트 세션엔 없음 -> 시리즈 없이locked_account=yes). 레플리카를 추가하거나 기존 러너가 드레인·재시작하길 기다립니다. on-demand면 orchestrator를 점검.pick-up 직후 세션 실패: claude.ai/code에서 오류 확인. git 인증이면 러너 git 자격 증명 점검,
--use-anthropic-git-proxy러너면 위 proxy 실패 절. base directory 쓰기 불가는 세션이 아니라 시작에서 러너를 멈춥니다.--use-anthropic-git-proxy러너에서 시작 실패: 로그에서access denied by the git proxy또는api.anthropic.com과/git_proxy/가 든 git 오류.인증 egress 프록시로 네트워크 접근 불가:
--proxy-authorization-command/--proxy-authorization-file소스가 실패하거나 30초 타임아웃되거나 빈 값이면 해당 연결에502 Bad Gateway로 응답하고 이유를 로그(명령 stderr는 가려지고 헤더 값은 로그되지 않음). 명령은 호스트에서 직접 실행해 stdout에 헤더 전체가 나오는지 확인. 시작 시could not start the proxy-authorization listener면 loopback 리스너를 열지 못한 것.Poll failed+rejecting the malformed poll response: 폴 응답이 큐의 예상 JSON이 아님(가로채는 프록시/captive portal 등).api.anthropic.com응답을 변경 없이 통과시키도록 프록시 설정. 러너는claude_code_self_hosted_runner_poll_errors_total의transportkind로 집계하고 실패 폴 스케줄로 재시도하며 라이브 세션은 계속 서빙합니다(v2.1.246 이전에는 빈 큐로 읽어 라이브 세션이 끝나거나 종료할 수 있었음).no longer exists on the remote/couldn't find remote ref: 러너는couldn't find remote ref를 삭제된 브랜치로 읽지만 브랜치가 있을 수도 있습니다. git 호스트에서 메시지와 정확히 같은 이름으로 찾아보고 원인을 확인합니다.- 브랜치가 없음: 메시지 안내대로 복원
- 있는데 러너 관리자가 아님: 전체 메시지를 관리자에게 전달
- 있고
checkouthook이 있음: hook의 git 명령이 stderr에 같은 메시지를 내고 비정상 종료한 것. 요청 ref(CLAUDE_RUNNER_REPO_REF)나 fetch remote를 수정 - 있고
checkouthook이 없음: 러너가 직접 fetch.--git-host-rewrite가 적용되면 재작성 대상 호스트에 같은 이름의 브랜치가 있는지 확인하고 없으면 push - 있고 러너가 v2.1.301 미만: 다른 실패도 삭제된 브랜치로 보고했으므로 업그레이드해 다른 오류가 자기 메시지로 나오게 함
세션이 결과를 push하는 소스의 삭제된 브랜치는 세션 실패. 읽기 전용 git 소스는 건너뛰고 남은 소스로 진행하지만 저장소가 하나도 안 남으면 실패(v2.1.228 이전에는 빈 디렉터리에서 시작).
일부 저장소 없이 세션 시작:
checkouthook이 없는 러너에서 git 호스트가 읽기 전용 저장소의 접근 확인을 거부하면[runner:warn] could not access context source줄을 남기고 나머지로 시작. 건너뛰는 것은 저장소 없음, 호스트에 대한 자격 증명 없음, 인증 실패 같은 명확한 거부뿐이며 네트워크 실패/타임아웃/HTTP403과 결과를 push하는 저장소의 거부는 여전히 시작 실패(저장소가 하나도 안 남는 경우 포함).--use-anthropic-git-proxy면 proxy가 직접 거부한 저장소만 건너뜀. 접근 확인은 세션이 러너에서 시작할 때마다 다시 실행(v2.1.274 이전에는 이런 거부가 모두 시작 실패).This runner couldn't read: 러너가 git 자격 증명으로 저장소를 요청해 not-found를 받음(저장소가 없거나 자격 증명이 닿지 않음). 관리자는 읽기 권한을 부여, 세션 시작자는 저장소가 없으면 올바른 저장소로 새 세션, 접근 문제면 관리자에게 요청.세션 시작에 몇 분: 초기 clone이 대부분.
claude_code_self_hosted_runner_session_init_duration_seconds메트릭으로 확인하고 pre-warmed checkout 또는 더 작은CLAUDE_RUNNER_FETCH_DEPTH.턴이 401로 실패: 턴이 Anthropic API의 401/403으로 끝나면 러너가 새
CLAUDE_CODE_OAUTH_TOKEN을 받아 세션에 전달(실패한 턴은 재시도하지 않음, 단기 토큰이며 세션 stdin으로 갱신). fetch 실패 시inference_token refresh failed를 로그하고 세션이 도는 동안 계속 재시도. 세션 약 30분 후 모든 호출이 실패하면 wrapper script가 세션 stdin을 끊어 토큰 갱신이 닿지 않은 것(Keep stdin and file descriptor 3 attached). v2.1.274 이전에는 몇 번 실패 후 다음 예약 fetch까지 재시도를 멈춰 모든 턴이 401로 실패.claude.ai/code에서
This model needs a newer version of Claude Code: 러너가 세션을 시작하는 버전이 모델 요구보다 낮음. 메시지가 요구하는 버전 이상으로 업그레이드(그동안 다른 모델 선택 가능).드레인 중 파드 kill:
terminationGracePeriodSeconds를 시작 로그 값 이상으로.
로깅이 초기화되면 러너가 lifecycle 로그([runner:fatal] 포함)를 stdout에, 디버그 출력을 stderr에 평문(JSON 아님)으로 씁니다. 두 스트림은 --log-file(self-hosted-runner doctor가 tail) 또는 플랫폼 로그 수집으로 캡처하세요. 세션 자식은 별도 디버그 로그를 쓰며 실패 시 러너가 로그 끝부분을 claude.ai/code의 세션 옆에 노출하고, --remove-session-state가 아니면 실패한 세션 로그를 디스크에 두고 경로를 러너 로그에 출력합니다.
러너 종료 시 처리
on-demand 러너는 work order가 1회용이라 재시작하지 않습니다. 시작 직후 종료하는 러너는 다른 종료와 다르게 다룹니다.
- 정상 종료: 세션을 마치고 드레인함 / retire 시각 도달 / 중지 지시 -> 재시작해서 용량 복구
- 시작 실패: 주어진 설정·호스트로 시작할 수 없어 매번 몇 초 만에 같은 방식으로 종료. 빨리 재시작해도 소용없고 출력을 읽어 원인을 고쳐야 함
- 연락 두절: lease보다 오래 Anthropic에 닿지 못하면(호스트 슬립 등) 환경에서 제거될 수 있고 재접속하면 종료.
[runner:fatal]줄에runner record gone server-side또는 더 긴 중단 후poll auth failed. 스스로 재등록하지 않으므로 재시작
supervisor는 종료할 때마다 재시작하고, 시작 직후 반복 종료하면 재시작 간격을 늘리며, 계속되면 담당자에게 알리도록 설정합니다. 프로덕션에서는 새 컨테이너/VM의 새 디스크에서 재시작합니다.
시작 실패 식별: 시작할 수 없으면 이유를 출력하고 종료합니다. 대부분은 [runner:fatal], 플래그 파싱 실패·환경 secret 읽기 실패·base directory 생성/쓰기 실패 같은 일부는 error:로 시작하며 다음 줄이 --help를 가리킵니다. 로그 줄 대부분은 타임스탬프와 [self-hosted-runner]로 시작합니다. 예: Anthropic git proxy + capacity 1 초과.
[runner:fatal] --use-anthropic-git-proxy requires --capacity 1 (the proxy URL is per-session and linked worktrees share origin). Omit --use-anthropic-git-proxy or set --capacity 1.
표준 출력/오류, 플랫폼 컨테이너 로그, --log-file에서 찾습니다(error: 줄은 로그 파일을 열기 전에 출력). 줄이 아예 없으면 호스트나 오케스트레이터가 프로세스를 중지시켰는지(메모리 한도 초과 등) 확인하세요. 종료 코드는 반복되는 오류를 위해 따로 예약돼 있지 않아 설정 오류와 API가 계속 닿지 않는 일시적 실패가 같은 코드로 종료하므로, 얼마나 빨리 종료했는지로 대기를 늘릴지 판단하고 출력을 읽어 이유를 파악하세요. 등록 이후 일부 시작 단계(--configure-git, Anthropic git proxy 자격 증명 설정)가 실패하면 프로세스가 종료돼도 몇 분 동안 환경이 러너를 계속 나열하여 Cloud environments가 Healthy로 보이는데 작업을 받는 러너가 없을 수 있으니, 이 경우 supervisor가 러너를 재시작 중인지 확인합니다.
점증 대기 재시작:
Kubernetes: Deployment 변경 불필요. kubelet이 기본으로 재시작 대기를 점증(상한까지)하고 컨테이너가 한동안 종료 없이 돌면 초기화. 짧게 돈 정상 종료 후에도 같은 대기가 적용되어 자주 드레인하면
CrashLoopBackOff가 보일 수 있으니 출력을 읽고 판단. 마지막 실행 출력은 다음 명령으로 읽습니다(다른 파드는deploy/claude-runner대신 파드 이름).kubectl logs --previous -n claude-runners deploy/claude-runnerDocker/Compose:
restart: always면 Docker가 상한까지 점증. 재시작 횟수는 다음으로 확인(<container>는 컨테이너 이름).docker inspect --format '{{.RestartCount}}' <container>systemd: 기본으로 매번 같은
RestartSec이라 점증하지 않습니다. 시작 빈도가 unit의 start rate limit(기본 10초에 5회)에 닿으면 systemd가 재시작을 멈추고, rate limit 간격이 지나거나systemctl reset-failed후에야 다시 시작할 수 있습니다.RestartSec은 정상 종료 후에도 적용되므로 둘의 균형을 잡고 restart count에 알림을 거세요.셸 루프/자체 supervisor: 5초로 시작해 1분 안에 끝난 실행 뒤마다 대기를 두 배(최대 5분), 1분 이상 돈 뒤에는 5초로 복귀.
계속 종료하는 이유 점검: 마지막 [runner:fatal]/error: 줄, 플래그 조합(Anthropic git proxy는 --capacity 1 필요, 레시피는 더 높은 capacity), 서비스 환경이 닿을 수 있는 것(수동 실행은 되고 supervisor에서 실패하면 사용자·홈 디렉터리·PATH·메모리 한도 비교, --configure-git과 git proxy는 PATH의 git과 쓰기 가능한 ~/.gitconfig 필요), 환경 secret(revoke/오타 시 RegisterRunner auth failed), 환경의 Activity 탭(새 러너가 계속 나타나는데 작업을 받는 러너가 없으면 supervisor가 재시작 중). 가이드 진단은 claude self-hosted-runner doctor.