claude-code
Alpine ベースの軽量コンテナで Claude Code をリモート実行する Helm チャート。
ブラウザ(PC / スマートフォン)からアクセスし、セッションごとに独立した使い捨て Pod の中で Git リポジトリ(GitHub / Gitea など互換 Git 全般)をチェックアウト → 編集 → 検証 → プッシュします。 一定時間アイドルなセッションは Pod ごと自動破棄され、リソースを占有し続けません。 Raspberry Pi などの ARM ベアメタル Kubernetes を主な対象にしています。
設計目標 — Claude Code は本来ローカルで動かすものだが、これをクラスタにデプロイすることで、 Git 管理された複数プロジェクトを PC からもスマホからも横断的に、安全に触れるようにする。
- Gitea リポジトリ:
ssh://git@192.168.9.65/helmchart/claude-code - Helm リポジトリ:
https://git.cafepieters.com/api/packages/helmchart/helm - セッションイメージ:
git.cafepieters.com/helmchart/claude-code/session:<appVersion>
目次
アーキテクチャ
┌──────────── リリース NS ────────────┐
ブラウザ (PC/スマホ) │ Deployment: claude-code │
│ HTTPS/WSS │ ┌──────────┐ ┌───────────────┐ │
▼ │ │ gateway │──▶│ manager │ │
Ingress / LB ──────────▶│ │ (nginx) │ │ (python stdlib)│ │
│ │ 認証 │ │ - Web UI │ │
│ │ WS proxy │ │ - Session API │ │
│ └────┬─────┘ │ - 回収ループ │ │
│ │ └───────┬────────┘ │
│ │ ttyd(WS) │ K8s API │
└───────┼─────────────────┼───────────┘
▼ ▼ (Role: pods/services)
┌───────── セッション NS (restricted PSA) ──────────┐
│ Pod claude-sess-ab12cd34ef Pod claude-sess-… │
│ ┌───────────────────────┐ │
│ │ alpine + node + git │ git clone │
│ │ + ttyd + claude-code │ → claude (ttyd) │
│ │ 非root / roFS / netpol│ → claude-verify/push │
│ └───────────────────────┘ アイドルで自壊 │
└────────────────────────────────────────────────┘
| コンポーネント | イメージ | 役割 |
|---|---|---|
| gateway | nginxinc/nginx-unprivileged:*-alpine |
単一入口。auth.mode に応じた認証、/s/<id>/ をセッション Pod の ttyd へ WebSocket リバースプロキシ、リクエスト毎にアクティビティ時刻を更新 |
| manager | python:3.x-alpine(標準ライブラリのみ・追加 pip 不要) |
モバイル対応 Web UI、セッション作成/一覧/破棄 API、アイドル/寿命の回収ループ。in-cluster ServiceAccount で Kubernetes API を直接操作 |
| session | git.cafepieters.com/helmchart/claude-code/session(既定)または任意の node:alpine 系 |
セッションごとに作られる 使い捨て Pod。git clone → claude を ttyd でブラウザに提供 |
| reaper | manager と同じイメージ(--reap-once) |
manager 障害時の保険となる孤児掃除 CronJob |
manager / gateway / session-entrypoint の実体は files/ にあり、.Files.Get で ConfigMap 化されます。
セッションのライフサイクル
- 作成 — UI か
POST /api/sessionsにrepo/ref/identityを渡す。manager は- リポジトリ URL の形式と Git ホスト allow-list(
git.allowedHosts)を検証 - 同時セッション数(
session.maxSessions)を確認 - セッション NS に Pod(
claude-sess-<10hex>)と Service を作成
- リポジトリ URL の形式と Git ホスト allow-list(
- ブート(
files/session-entrypoint.sh, 非 root)- マウントされた SSH 鍵 / HTTPS トークンで git 認証を構成(
known_hosts必須、TOFU 無効) - リポジトリを
session.workspaceDirにclone(--filter=blob:none) claudeが無ければ$HOMEに非 root で npm インストールttydを起動(session.autoStartClaude: trueならclaudeを自動起動)
- マウントされた SSH 鍵 / HTTPS トークンで git 認証を構成(
- 利用 —
/s/<id>/経由でブラウザ内ターミナルに接続。claude-verify/claude-pushが使える - 破棄 — 次のいずれかで Pod ごと消滅(Service も回収)
- アイドル:
ttydへの接続がsession.idleTimeoutSeconds秒間ゼロ → Pod 内ウォッチドッグが自壊 - アイドル(保険): manager の回収ループが
last-activityアノテーションを見て削除 - 最大寿命:
session.maxLifetimeSeconds(Pod のactiveDeadlineSeconds)到達で強制終了 - 手動: UI の「破棄」/
DELETE /api/sessions/<id> - 孤児掃除: 予備 CronJob(
session.reaper.cronjob)
- アイドル:
セキュリティモデル
| 層 | 対策 |
|---|---|
| 入口 | すべて gateway 経由。auth.mode= token(既定)/ basic / forward(外部 SSO へ委譲)/ none。WebSocket は Cookie トークンで認証(ブラウザは WS に Authorization ヘッダを付けられないため) |
| 制御プレーン RBAC | manager はセッション NS の pods / services のみ操作する Role(ClusterRole 不使用)。他 NS・他リソースには触れない |
| セッション Pod | automountServiceAccountToken: false(API サーバー到達不可)、runAsNonRoot / readOnlyRootFilesystem / capabilities.drop: [ALL] / seccompProfile: RuntimeDefault。書き込み可能なのは emptyDir(/workspace, $HOME, /tmp)のみ |
| ネットワーク | NetworkPolicy でセッションの ingress は gateway からのみ。egress は DNS + HTTPS(443, git/Anthropic) + SSH(22)。networkPolicy.blockedEgressCIDRs(既定でクラウドメタデータ IP 169.254.169.254/32)を egress の except に指定。自クラスタの Pod/Service CIDR を追加すると内部 SSRF も遮断できる |
| Git 到達先 | git.allowedHosts の allow-list。既定は allow-list 外を拒否(allowArbitraryRepos: true で解除) |
| 認証情報 | ANTHROPIC_API_KEY / SSH 鍵 / トークンは Kubernetes Secret。values.yaml に平文で書かず --set / --set-file / existingSecret で注入。git HTTPS トークンはセッション内で credential.helper store の 0600 ファイルに限定し、プロセス環境に残さない |
| PodSecurity | セッション NS に pod-security.kubernetes.io/enforce: restricted を付与(sessionsNamespace.create: true 時) |
| ブラスト半径 | ResourceQuota(Pod / Service 数)+ LimitRange(コンテナ CPU/メモリ上限)+ session.maxSessions |
| 自動 push | 既定 無効。有効時もデフォルト/保護ブランチ(autoPush.protectedBranches)への push と --force は不可。branchPrefix 配下の作業ブランチにのみ push |
注意 —
values.yamlに API キーや秘密鍵を書いてコミットしないでください。helm get values/ CI ログ / Git 履歴に残ります。existingSecretか CI からの--setを使ってください。
インストール
前提
- Kubernetes 1.25+(PodSecurity admission)、NetworkPolicy 対応 CNI(Calico / Cilium など)
- Ingress コントローラー(外部公開する場合。WebSocket を通すため read/send timeout を延長)
ANTHROPIC_API_KEY(またはclaude setup-tokenで発行した OAuth トークン)
Helm リポジトリから
helm repo add cafepieters https://git.cafepieters.com/api/packages/helmchart/helm
helm repo update
helm upgrade --install claude-code cafepieters/claude-code \
-n claude-code --create-namespace \
-f my.values.yaml \
--set anthropic.apiKey="$ANTHROPIC_API_KEY"
ローカルチャートから(GitHub 公開リポジトリだけ触る最小構成)
helm upgrade --install claude-code ./claude-code \
-n claude-code --create-namespace \
-f examples/minimal.values.yaml \
--set anthropic.apiKey="$ANTHROPIC_API_KEY"
Gitea(SSH) + GitHub(HTTPS) 両対応
helm upgrade --install claude-code ./claude-code \
-n claude-code --create-namespace \
-f examples/gitea-and-github.values.yaml \
--set anthropic.apiKey="$ANTHROPIC_API_KEY" \
--set-file git.identities.gitea-ssh.sshPrivateKey=P:/Claude/.ssh/id_claude \
--set-file git.identities.gitea-ssh.knownHosts=./known_hosts.gitea \
--set git.identities.github-https.token="$GITHUB_PAT"
known_hosts は事前に取得します:
ssh-keyscan 192.168.9.65 > known_hosts.gitea
使い方
-
アクセス(Ingress 無効ならポートフォワード):
kubectl -n claude-code port-forward svc/claude-code 8080:80 -
auth.mode: tokenの場合、トークンを取得して UI に貼り付け(または?token=で開く):kubectl -n claude-code get secret claude-code-auth \ -o jsonpath='{.data.token}' | base64 -d ; echo -
UI で リポジトリ URL / ブランチ / 認証 identity を指定して「セッション作成」。
-
一覧の「開く」でブラウザ内ターミナル(Claude Code)に接続。
-
セッション内の補助コマンド:
claude-verify…session.verifyCommand(例npm test)を実行claude-push… 検証 →claude/<branch>へ push(保護ブランチ /--force不可)
-
無操作
session.idleTimeoutSeconds秒でセッション(Pod)は自動的に消えます。
values リファレンス
image
| キー | 既定 | 説明 |
|---|---|---|
image.gateway.* |
nginxinc/nginx-unprivileged:1.29.1-alpine |
受付 nginx(非 root 版) |
image.manager.* |
library/python:3.13-alpine |
制御プレーン(標準ライブラリのみ) |
image.session.* |
library/node:22-alpine |
セッション。既定はブート時に apk/npm を実行するため初回起動が遅い。セッションイメージ参照 |
imagePullSecrets |
[] |
プライベートレジストリ用 |
gateway / manager
| キー | 既定 | 説明 |
|---|---|---|
gateway.replicaCount |
1 |
制御プレーン Deployment のレプリカ数 |
gateway.port |
8080 |
gateway コンテナ待受ポート(Service / Ingress バックエンド) |
manager.logLevel |
info |
debug / info / warning / error |
gateway.resources / manager.resources |
控えめ | それぞれの resources |
sessionsNamespace
| キー | 既定 | 説明 |
|---|---|---|
sessionsNamespace.name |
claude-code-sessions |
セッション Pod を隔離する NS(空 = リリース NS) |
sessionsNamespace.create |
true |
この NS を作成し PodSecurity ラベルを付与するか。既存 NS を使うなら false にして手動でラベル付与 |
sessionsNamespace.podSecurityLevel |
restricted |
付与する PodSecurity enforce レベル |
sessionsNamespace.resourceQuota.enabled |
true |
セッション NS の ResourceQuota(既定は Pod/Service 数の抑制のみ。compute 集計上限は hard で明示) |
sessionsNamespace.resourceQuota.hard |
{} |
指定するとそのまま spec.hard |
sessionsNamespace.limitRange.* |
max 4CPU/4Gi | セッション NS のコンテナ既定値・上限(LimitRange) |
auth — アクセス認証
| キー | 既定 | 説明 |
|---|---|---|
auth.mode |
token |
token / basic / forward / none |
auth.token.value |
"" |
空なら install 時に自動生成(lookup で再 install 間も安定)。運用では existingSecret 推奨 |
auth.token.existingSecret / existingSecretKey |
"" / token |
既存 Secret からトークンを読む |
auth.basic.users |
[] |
htpasswd -nbB user pass の出力を 1 行以上 |
auth.basic.existingSecret / existingSecretKey |
"" / htpasswd |
既存 htpasswd Secret |
auth.forward.url |
"" |
auth_request の転送先(例: oauth2-proxy の /oauth2/auth) |
auth.forward.signInUrl |
"" |
未認証時のサインイン URL(任意) |
token モードでは UI のログインフォームで一度トークンを入力すると、HttpOnly Cookie に保存され
以降の /s/<id>/(WebSocket 含む)に自動的に付与されます。
anthropic — Claude Code 認証情報
| キー | 既定 | 説明 |
|---|---|---|
anthropic.authMode |
apiKey |
apiKey(ANTHROPIC_API_KEY)/ oauth(CLAUDE_CODE_OAUTH_TOKEN) |
anthropic.apiKey / anthropic.oauthToken |
"" |
--set / --set-file で注入 |
anthropic.existingSecret |
"" |
既存 Secret を使う(キー名は existingSecretKeyApiKey / existingSecretKeyOauthToken) |
anthropic.extraEnv |
{} |
全セッションへ渡す追加 env(例 ANTHROPIC_BASE_URL、Bedrock/Vertex 切替など) |
Secret は セッション NS に作成され、セッション Pod へ envFrom で注入されます。
git — 接続先と認証アイデンティティ
| キー | 既定 | 説明 |
|---|---|---|
git.allowedHosts |
GitHub/GitLab/Gitea | clone を許可するホスト allow-list |
git.allowArbitraryRepos |
false |
allow-list を無効化し任意 URL を許可(非推奨) |
git.defaultAuthorName / Email |
Claude / claude@cafepieters.com |
コミット author 既定 |
git.identities.<name>.type |
— | ssh / https-token |
…ssh.sshPrivateKey / knownHosts |
— | PEM 秘密鍵 / ssh-keyscan 出力(knownHosts 必須) |
…https-token.username / token |
x-access-token / — |
HTTPS ベーシック(GitHub PAT は username 任意) |
…existingSecret ほか |
— | 既存 Secret 参照(キー名は existingSecretKey* で調整) |
GitHub でも Gitea でも GitLab でも、SSH か HTTPS トークンで認証できる Git ホストなら同じ仕組みで扱えます。 セッション作成時に identity 名を選ぶと、その Secret だけがそのセッション Pod にマウントされます。
session — 使い捨てセッション
| キー | 既定 | 説明 |
|---|---|---|
session.autoStartClaude |
true |
ブート後に claude を自動起動(false はログインシェル) |
session.workspaceDir |
/workspace |
clone 先 |
session.ttydPort |
7681 |
ttyd 待受(Pod 内のみ) |
session.extraApkPackages |
[] |
追加 apk(事前ビルドイメージなど root 権限がある場合のみ有効) |
session.claudeCodeVersion |
"" |
インストールする @anthropic-ai/claude-code バージョン(空 = チャート appVersion) |
session.resources |
100m/256Mi 〜 2CPU/2Gi/4Gi(eph) | セッション Pod の resources |
session.idleTimeoutSeconds |
1800 |
無通信での破棄しきい値 |
session.maxLifetimeSeconds |
43200 |
Pod の activeDeadlineSeconds |
session.reaper.intervalSeconds |
60 |
manager 回収ループ間隔 |
session.reaper.cronjob.enabled / schedule |
true / */15 * * * * |
予備の孤児掃除 CronJob |
session.maxSessions |
10 |
同時セッション数上限(manager が enforce) |
session.verifyCommand |
"" |
claude-verify / claude-push が実行するコマンド |
session.autoPush.enabled |
false |
claude-push を許可するか |
session.autoPush.branchPrefix |
claude/ |
push 先ブランチのプレフィックス |
session.autoPush.protectedBranches |
[main, master, develop, release] |
push 禁止ブランチ |
session.podLabels / podAnnotations / nodeSelector / tolerations / priorityClassName |
— | セッション Pod へのメタデータ / スケジューリング |
networkPolicy
| キー | 既定 | 説明 |
|---|---|---|
networkPolicy.enabled |
true |
NetworkPolicy を作成するか(CNI が対応必須) |
networkPolicy.dnsNamespaceSelector |
kube-system |
DNS を許可する NS セレクタ |
networkPolicy.allowInternetHttps |
true |
セッションの 443/tcp 外向きを許可(git HTTPS + Anthropic API) |
networkPolicy.allowSshGit |
true |
セッションの 22/tcp 外向きを許可(SSH git) |
networkPolicy.blockedEgressCIDRs |
[169.254.169.254/32] |
egress except。自クラスタの Pod/Service CIDR を足すと内部 SSRF を遮断 |
networkPolicy.sessionEgressExtra |
[] |
追加許可(社内ミラー / プロキシ)。{cidr, ports[]} |
完全にインターネットを遮断したい場合は allowInternetHttps: false にして egress プロキシ
(例: 許可リスト付き Squid)を sessionEgressExtra で通してください。
ingress / service
ingress.enabled: true で Ingress を作成。WebSocket 用に proxy-read-timeout /
proxy-send-timeout を既定 3600 に設定します(ingress.annotations で上書き可)。
service.type は ClusterIP 既定。
セッションイメージ
既定の image.session は CI(session-image.yaml)がビルドする 事前ビルドイメージ
git.cafepieters.com/helmchart/claude-code/session:<appVersion> です。
git / openssh-client / ttyd / bash / node / @anthropic-ai/claude-code を同梱し、
非 root(uid 10001)で動作するため、restricted PodSecurity / readOnlyRootFilesystem 下でも
確実に起動し、コールドスタートも数秒です。
session-image.yaml は files/Dockerfile.session 等の変更 push、または
image-update-and-release.yaml による Chart.yaml 更新で発火し、linux/amd64,linux/arm64 の
2 アーキテクチャでビルドして Gitea Container Registry へ push します。手動ビルドは:
docker buildx build --platform linux/amd64,linux/arm64 \
--build-arg CLAUDE_CODE_VERSION=2.1.259 \
-f files/Dockerfile.session \
-t git.cafepieters.com/helmchart/claude-code/session:2.1.259 --push .
事前ビルドを使わない場合
image.session に素の node:22-alpine などを指定することもできます。この場合ブート
スクリプトが $HOME に非 root で @anthropic-ai/claude-code を npm インストールします
(初回数十秒)。ただし ttyd は非 root では apk add できない ため、ttyd を含まない
イメージでは起動に失敗します。ttyd 入りの独自イメージを用意するか、事前ビルドイメージを
使ってください。
運用
- セッション一覧:
kubectl -n <sessionsNamespace> get pods -l app.kubernetes.io/component=session - セッションのログ:
kubectl -n <sessionsNamespace> logs claude-sess-<id> - 強制破棄: UI から、または
kubectl -n <sessionsNamespace> delete pod,svc claude-sess-<id> - 回収の確認: manager のログに
reaping session <id> (idle|max-lifetime|phase=...) - トークンのローテーション:
auth.token.existingSecretの値を更新し、Deployment を再起動 - アップグレード:
helm upgrade。files/*の変更は checksum アノテーションで自動ローリング
トラブルシューティング
| 症状 | 原因と対処 |
|---|---|
| UI が 502 / セッションを開くと 502 | セッション Pod がまだ Ready でない(初回は npm i に時間)。数十秒待つ。恒久対策は事前ビルドイメージ |
セッション作成が 許可されていない Git ホスト |
git.allowedHosts に対象ホストを追加(IP と FQDN は別物) |
clone が Host key verification failed |
identity の knownHosts が対象ホストの鍵と不一致。ssh-keyscan <host> で取り直す |
clone が terminated ... could not read Username |
HTTPS の場合 git.identities.<name> 未指定 or token 無効。SSH の場合は identity の type/秘密鍵を確認 |
| WebSocket がすぐ切れる | Ingress の proxy-read-timeout を延長。auth.mode: token で Cookie 未設定なら一度 UI からログイン |
| セッションが破棄されない | manager の RBAC / ログを確認。予備 CronJob(session.reaper.cronjob)が有効か |
apk エラーがログに出る |
既定イメージ + readOnlyRootFilesystem の制約。事前ビルドイメージを使う |
| manager が全リクエストを 401 | auth.mode: token でトークンが空。auth.token.value か existingSecret を設定 |
リリースフロー
Chart.yaml の version / appVersion は Claude Code CLI(@anthropic-ai/claude-code npm 最新版)と同一。
helm-release.yaml—mainpush 時に Gitea Package Registry へチャートを publish(全リポジトリ共通)。image-update-and-release.yaml— 毎週月曜 3:00 JST。- npm の Claude Code 最新版に追従。主アプリ更新時のみ
Chart.yaml更新 → セッションイメージ multi-arch ビルド/push → Git タグv<ver>→ Gitea リリース → チャート publish。 nginx/pythonの Alpine タグのみ更新の場合はvalues.yamlを更新してコミットするだけ (リリースなし)。
- npm の Claude Code 最新版に追従。主アプリ更新時のみ
- 手動リリース(臨時) — バージョン末尾にアルファベットを付与(
2.1.259→2.1.259-a)。 対応するCHANGELOG-<version>.mdを追加する。
必要な Gitea Secrets: GITEA_TOKEN / REGISTRY_USER / REGISTRY_TOKEN。
ワークフロー一覧
| ファイル | トリガー | 動作 |
|---|---|---|
helm-release.yaml |
main への push / 手動 |
helm lint。Chart.yaml の version が未リリース(Gitea リリース・Package Registry 双方に無い)なら タグ + Gitea リリース + チャート publish。リリース済みなら lint のみ |
image-update-and-release.yaml |
毎週月曜 3:00 JST / 手動 | npm の @anthropic-ai/claude-code 最新版と base image(nginx / python)の最新 Alpine タグを確認。Claude Code の更新があれば values.yaml / Chart.yaml / README 履歴を更新してコミット → タグ + リリース + publish。base image のみの更新は values.yaml を更新してコミットのみ(リリースなし) |
session-image.yaml |
files/Dockerfile.session ほかの変更 / 手動 |
セッションイメージ(.../claude-code/session)を linux/amd64,linux/arm64 でビルドし、Chart.yaml の appVersion タグ + latest で Gitea Container Registry へ push |
バージョン履歴
| バージョン | Claude Code | 変更内容 |
|---|---|---|
| 2.1.259 | 2.1.259 | 初版。gateway + manager + 使い捨て session Pod + reaper、GitHub/Gitea 対応、アイドル自動破棄、NetworkPolicy / RBAC 最小化 / restricted PodSecurity |