renue

ARTICLE

Claude Code VMリモート実行の本番運用ガイド【2026年版】— WebSocket+PTYでブラウザから安全に利用する実装パターン

公開日: 2026/4/6

Claude Codeをブラウザから使う方法は2026年時点で大きく2つある。①Anthropic公式のClaude Code Remote Control(2026年2月25日リリース)でローカルマシンを遠隔操作する方法、②自社VM上でClaude Codeを動かしWebSocket+PTYでブラウザに接続する方法である。本記事では、後者の本格実装パターンを、renueが自社で運用している「AI Terminal VM Wrapper」の実装知見をもとに解説する。エンジニア以外のメンバーにもClaude Codeを安全に提供したい組織向けの実装ガイドである。

2つのアプローチの違い

観点Claude Code Remote ControlVM Wrapper方式(自社運用)
実行場所ローカルマシン(ユーザー個別)集中管理されたVM
環境ユーザー固有のローカル環境全員統一された環境
セットアップ負荷ユーザー個別に必要管理者が一度構築
非エンジニアの利用難しい(ローカル設定必要)容易(URLだけで開始)
権限制御CLAUDE_REMOTE_ALLOWED_PATHVM側で完全制御
コストユーザー個別VM集約(共有)
データ置き場ローカルVM(チーム共有可能)

renueの実装では、非エンジニアを含む全社員がClaude Codeを使えることを重視し、VM Wrapper方式を採用している。

本番品質なVM Wrapper実装に必要な5レイヤー

レイヤー1: WebSocket + PTY連携

ブラウザからClaude Codeを操作するには、WebSocketでテキストを双方向にやり取りし、VM側でPTY(擬似ターミナル)経由でClaude Codeプロセスに入出力を渡す必要がある。

主要なPython標準ライブラリ

  • pty: 擬似ターミナルの生成
  • fcntl: ファイルディスクリプタ制御(ノンブロッキング)
  • select: 入出力待機
  • termios: ターミナル属性制御
  • struct: ウィンドウサイズ送信(TIOCSWINSZ)
  • signal: シグナル制御

FastAPIでのWebSocketエンドポイント

@app.websocket("/ws/terminal/{session_id}")
async def terminal_websocket(
    websocket: WebSocket,
    session_id: str,
    api_key: str = Query(...),
):
    # APIキー検証
    verify_api_key(api_key)
    await websocket.accept()
    # セッション取得 or 新規作成
    session = _terminal_sessions.get(session_id)
    if session is None:
        session = await _create_new_session(session_id)
    else:
        # 既存セッションに再接続(後述)
        await _reconnect_session(session, websocket)
    # 入出力ループ
    ...

レイヤー2: セッション再接続(最重要)

ブラウザベースのClaude Code運用で最も難しいのは「ネットワーク切断後の再接続」である。ユーザーがタブを閉じた、ネットワークが一時的に途切れた、PCがスリープしたといった場合、サーバー側のClaude Codeプロセスは生きたまま維持する必要がある。そうしないと、長時間の処理がすべて失われてしまう。

renueの実装パターン

  • セッションディクショナリ: `_terminal_sessions: dict[str, dict[str, Any]]` でsession_idをキーにセッションを保持
  • 出力バッファ: WebSocket切断中のClaude出力を`deque`で保持し、再接続時に一括送信
  • タイムアウト制御: 切断後7200秒(2時間)までセッション維持(`AI_TERMINAL_SESSION_TIMEOUT_SECONDS`)
  • クリーンアップタスク: 30秒ごとに期限切れセッションを削除(`AI_TERMINAL_SESSION_CLEANUP_INTERVAL_SECONDS`)
  • 出力バッファ上限: 切断中の出力を400チャンクまで保持(`AI_TERMINAL_OUTPUT_BUFFER_CHUNKS`)
  • Graceful termination: セッション終了時の猶予時間を設定(`AI_TERMINAL_TERMINATION_GRACE_SECONDS`)

再接続ロジックのポイント

再接続時は、以下の手順で状態を復元する。

  1. 既存セッションの有無をsession_idで確認
  2. 存在する場合、バッファに溜まった出力を全て送信
  3. WebSocket参照を新しいコネクションに差し替え
  4. PTYへの入出力ループを再開

レイヤー3: プロファイル別の権限制御

VM Wrapper方式の強みは「ユーザーの習熟度に応じて権限を変えられる」ことである。renueの実装では以下のプロファイルを定義している。

beginner_chat プロファイル(やさしいモード)

非エンジニア向けのモード。Claude Codeに許可するツールを極限まで制限する。

  • 許可される操作: `Bash(renue *)` のみ(renue CLIの実行のみ)
  • 禁止コマンド: `cd`, `pushd`, `popd`, `dirs` を含む任意のディレクトリ変更
  • ワークスペース分離: ユーザーごとに専用ディレクトリ(`/tmp/ai-terminal/workspaces/{user}/`)を自動作成
  • 3層防御: ブラウザ/API/VMの3層で同じ防御を入れて多層防御

これにより、非エンジニアが誤ってファイルシステムを破壊したり、他のユーザーのファイルにアクセスしたりするリスクをゼロにできる。

slack_ops プロファイル(Slack対応モード)

Slack対応botとして動かす場合のプロファイル。読み取り専用の調査・回答タスクに特化。

  • 許可ツール: `Bash Read Glob Grep WebFetch WebSearch`
  • 行動指針(システムプロンプト): 「Slack向けに簡潔な返答」「renue CLIを優先使用」「不必要なファイル探索を避ける」
  • JSON出力: `reply_text`を含む構造化JSON

ecommerce プロファイル

EC運用向け。renue CLIを使った広告運用・クリエイティブ管理に特化。

レイヤー4: PATHとsystemd設定の罠

Claude Codeをsystemdサービスとして動かす場合、PATH設定に罠がある。

典型的な問題

  • Anthropic公式のネイティブインストーラー(`claude install`)は`~/.local/bin/claude`にバイナリを配置する
  • systemdのデフォルトPATHには`~/.local/bin`が含まれない
  • 結果、systemdから起動した際にClaudeバイナリが見つからない

解決策

# claude-wrapper.service の [Service] セクション
Environment="PATH=/home/azureuser/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

デプロイスクリプトで自動的にPATHを追加する仕組みを作っておくと、VM再構築時も安定する。

レイヤー5: デプロイと運用

自動デプロイフロー(GitHub Actions)

renueの実装では、`src/**`または`scripts/**`の変更がmainにpushされると、GitHub ActionsがSSH経由で自動的にVMにデプロイする。

必要なGitHub Secrets

  • AI_TERMINAL_VM_SSH_KEY(必須): SSH秘密鍵
  • AI_TERMINAL_VM_HOST(任意): VMのホスト
  • AI_TERMINAL_VM_USER(任意): SSHユーザー
  • AI_TERMINAL_VM_PORT(任意): SSHポート
  • AI_TERMINAL_VM_KNOWN_HOSTS(任意): 未設定時は`ssh-keyscan`で自動取得

デプロイスクリプトの処理

  1. extension.pyをVMに転送
  2. main.pyのmanaged blockを冪等更新(既存コードとの共存)
  3. `python3 -m py_compile`で構文チェック
  4. `sudo systemctl restart claude-wrapper`

ロールバック対策

各デプロイ時に`/tmp/claude-wrapper-backups/`にバックアップを自動保存する。問題発生時はバックアップを復元して`systemctl restart`で即座にロールバックできる。

ローカル開発環境(Docker E2E)

VM本番環境と同じ構成をローカルのDockerで再現できると、開発・テストが楽になる。renueの実装では以下の構成を取っている。

  • docker-compose default: 本物のClaude Code CLIをコンテナ内で起動
  • 認証状態の永続化: `/claude-home`をnamed volumeで永続化
  • SERVICE_API_KEY管理: `./scripts/sync-local-service-api-key.sh`でローカル用のAPIキーを`.local/service_api_key.env`に抽出
  • fake claude切替: `AI_TERMINAL_USE_FAKE_CLAUDE=1`でfake transcriptモード(テスト用)に切替

renueの実装事例 — 全社員がClaude Codeを使える環境

renueは「Self-DX First」の方針のもと、社長から事務スタッフまで全員がClaude Codeを日常業務で使える環境を構築している。社内12業務(採用・経理・PMO・評価など)を553のAIツールで自動化済み(2026年1月時点)であり、その中核がAI Terminal VM Wrapperによる全社共通環境である(全て公開情報)。

公開されている特徴

  • ブラウザ側(nextjs-sales): Next.jsでターミナルUIを提供
  • API中継(pj-shared-fastapi-renue): `/ws/ai-terminal/browser/{session_id}`でブラウザとVM間を中継
  • VM受け入れ側(ai-terminal-vm-wrapper): `/ws/terminal/{session_id}`でClaude Codeプロセスを管理
  • 3つのプロファイル: beginner_chat / slack_ops / ecommerce
  • 非エンジニアも利用可能: 「Bash(renue *)」のみ許可するやさしいモード
  • セッション再接続: 切断後2時間までセッション維持

セキュリティベストプラクティス

1. APIキー認証

WebSocketエンドポイントは必ずAPIキー認証で保護する。`?api_key=`クエリパラメータで渡し、サーバー側で検証する。

2. SERVICE_API_KEYの管理

`SERVICE_API_KEY`はFastAPI側(pj-shared-fastapi-renue)とVM側(ai-terminal-vm-wrapper)で一致させる必要がある。環境変数で渡し、ソースコードには一切記載しない。

3. ユーザー別ワークスペース分離

各ユーザーごとに専用ディレクトリを自動作成し、他ユーザーのファイルにアクセスできないようにする。特にbeginnerモードではcd禁止で絶対的に分離する。

4. 出力バッファの上限

出力バッファを無制限にすると、大量出力時にメモリ枯渇するリスクがある。チャンク数で上限(デフォルト400)を設定する。

5. セッションタイムアウト

切断後のセッション保持は必要だが、無期限保持はリソースを圧迫する。適切なタイムアウト(デフォルト2時間)を設定する。

導入時のよくある失敗パターン

  • セッション再接続を実装しない: ネットワーク切断で長時間の処理がすべて失われる
  • 権限制御を省略する: 非エンジニアが誤ってシステムを破壊するリスク
  • systemd PATHの設定を忘れる: Claude Codeバイナリが見つからず起動失敗
  • APIキー認証を後回しにする: 誰でもアクセス可能な状態で脆弱性を抱える
  • バッファ上限を設定しない: 大量出力時のメモリ枯渇
  • デプロイのロールバック手段がない: 問題発生時に復旧できない
  • ローカル開発環境がない: VMでしか動作確認できず開発速度が落ちる

よくある質問

Claude Code Remote ControlとVM Wrapper、どちらを選ぶべき?

個人利用ならClaude Code Remote Control(公式)が最も簡単。組織全体で運用する場合、特に非エンジニアも含めて利用させたい場合はVM Wrapper方式が適している。統一環境と集中管理のメリットが大きい。

セッション再接続は本当に必要?

必要である。Claude Codeの処理は数分〜数十分かかることが多く、ネットワーク切断で全て失われるとユーザー体験が劣悪になる。本番運用では必須機能である。

権限制御はどこまで厳しくすべき?

利用者の習熟度で分ける。エンジニア向けはフル権限、非エンジニア向けは`Bash(renue *)`のみ、Slack bot向けは読み取りのみ、というように段階的に設計する。

VMのスペックはどれくらい必要?

同時セッション数による。1セッションあたり数百MB〜数GBのメモリを消費する(Claude Codeプロセス+PTY+バッファ)。同時10セッションなら最低16GB RAM、20セッションなら32GB以上を推奨。

コストはどれくらい?

VMの運用コスト(月数万円〜)+ Claude Code API利用料(ユーザー数×プラン料金)+ Azure/AWS等のインフラコスト。組織全体で見れば、ユーザー個別にClaude Codeを設定するより安価になるケースが多い。