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 化されます。

セッションのライフサイクル

  1. 作成 — UI か POST /api/sessions に repo / ref / identity を渡す。manager は
    • リポジトリ URL の形式と Git ホスト allow-list(git.allowedHosts)を検証
    • 同時セッション数(session.maxSessions)を確認
    • セッション NS に Pod(claude-sess-<10hex>)と Service を作成
  2. ブート(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 を自動起動)
  3. 利用 — /s/<id>/ 経由でブラウザ内ターミナルに接続。claude-verify / claude-push が使える
  4. 破棄 — 次のいずれかで 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

使い方

  1. アクセス(Ingress 無効ならポートフォワード):

    kubectl -n claude-code port-forward svc/claude-code 8080:80
    
  2. auth.mode: token の場合、トークンを取得して UI に貼り付け(または ?token= で開く):

    kubectl -n claude-code get secret claude-code-auth \
      -o jsonpath='{.data.token}' | base64 -d ; echo
    
  3. UI で リポジトリ URL / ブランチ / 認証 identity を指定して「セッション作成」。

  4. 一覧の「開く」でブラウザ内ターミナル(Claude Code)に接続。

  5. セッション内の補助コマンド:

    • claude-verify … session.verifyCommand(例 npm test)を実行
    • claude-push … 検証 → claude/<branch> へ push(保護ブランチ / --force 不可)
  6. 無操作 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 — main push 時に 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 を更新してコミットするだけ (リリースなし)。
  • 手動リリース(臨時) — バージョン末尾にアルファベットを付与(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.263 2.1.263 自動更新
2.1.259 2.1.259 初版。gateway + manager + 使い捨て session Pod + reaper、GitHub/Gitea 対応、アイドル自動破棄、NetworkPolicy / RBAC 最小化 / restricted PodSecurity
S
Description
Alpine based container designed for running Claude Code remotely.
Readme
561 KiB
v2.1.310
Latest
2026-10-25 18:07:50 +00:00
Languages
Python 73.3%
Shell 21.1%
Go Template 5.6%