1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code × Docker Sandbox × MCPで作るセキュアなAIエージェント開発環境──構築手順と運用で学んだ6つの教訓

1
Posted at

結論:AIエージェントには「檻」が要る

AIエージェントにホストマシンのフルアクセスを渡していた過去の自分を殴りたい──Docker Sandbox と MCP で権限を最小化した開発環境を構築した全記録です。

この記事を読むと、以下がわかります。

  • Claude Code をDockerコンテナ内に閉じ込め、ホスト環境を守る構成の作り方
  • MCP(Model Context Protocol)でファイルアクセスやネットワークを最小権限に絞る方法
  • 3ヶ月運用して得た6つの教訓(パフォーマンス、コスト、チーム展開)

環境・前提条件

項目 バージョン / 仕様
OS Ubuntu 24.04 LTS(ホスト)
Docker 27.x + Docker Compose v2
Claude Code 最新版(CLI)
Node.js 22 LTS(MCP Server 実行用)
MCP SDK @modelcontextprotocol/sdk 最新

Claude Code の CLI インストールとAnthropicのAPIキーは取得済みとします。

なぜ Sandbox が必要か──Claude Code が rm -rf した日

ある日、私はClaude Code に「不要なビルド成果物を掃除して」と指示しました。Claude Code はプロジェクトルートから再帰的にファイルを削除し、.git ディレクトリごと吹き飛ばしました。幸いリモートリポジトリがあったので復旧できましたが、ローカルの未コミット変更は消滅しました。

問題の本質は、AIエージェントがホストのファイルシステムに直接アクセスできる状態だったことです。人間のエンジニアなら「.git を消すのはまずい」と判断できますが、LLM はコンテキストの解釈次第でどんなコマンドも実行します。

Claude Code には許可されたコマンドを自動実行する仕組みがありますが、それでもホスト環境への直接アクセスは根本的なリスクです。Sandbox で物理的に隔離する方が、設定ミスに対して圧倒的に堅牢です。

アーキテクチャ概要

構成のポイントは3つです。

  1. Claude Code の実行環境をDockerコンテナに閉じ込める
  2. ホスト側リソースへのアクセスはMCPサーバー経由に限定する
  3. ネットワーク・シークレット・ボリュームを最小権限で設計する

赤い境界線が最も重要な隔離ラインです。Claude Code はこの中でしか動作できず、ホストファイルシステムへのアクセスはMCP Filesystemサーバーが許可したディレクトリだけに制限されます。

手順1:Dockerfile と docker-compose.yml の設計

Dockerfile(最小権限イメージ)

# ---- Sandbox: Claude Code 実行環境 ----
FROM node:22-slim AS claude-sandbox

# セキュリティ: root 以外のユーザーで実行
RUN groupadd -r claude && useradd -r -g claude -m -s /bin/bash claude

# 必要最小限のツールのみインストール
RUN apt-get update && apt-get install -y --no-install-recommends \
    git \
    curl \
    jq \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Claude Code CLI をグローバルインストール
RUN npm install -g @anthropic-ai/claude-code

# 作業ディレクトリ
WORKDIR /workspace
RUN chown claude:claude /workspace

# 非 root ユーザーに切り替え
USER claude

# Claude Code の設定ディレクトリを永続化ポイントとして用意
RUN mkdir -p /home/claude/.claude

ENTRYPOINT ["claude"]

重要なのは USER claude で非rootユーザーに切り替えている点です。万が一コンテナ内でエスケープが発生しても、ホスト側への影響を最小限に抑えます。

docker-compose.yml

version: "3.9"

services:
  claude-sandbox:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: claude-sandbox
    stdin_open: true
    tty: true
    volumes:
      # プロジェクトディレクトリのみマウント(読み書き)
      - ./project:/workspace:rw
      # Claude Code 設定の永続化
      - claude-config:/home/claude/.claude
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
    networks:
      - claude-net
    # セキュリティ強化
    security_opt:
      - no-new-privileges:true
    read_only: false
    tmpfs:
      - /tmp:size=512M
    # リソース制限
    deploy:
      resources:
        limits:
          memory: 4G
          cpus: "2.0"

  mcp-filesystem:
    image: node:22-slim
    container_name: mcp-filesystem
    working_dir: /app
    volumes:
      - ./mcp-servers/filesystem:/app:ro
      - ./project:/data/project:ro  # 読み取り専用でマウント
      - ./project/output:/data/output:rw  # 出力だけ書き込み可
    command: ["node", "server.js"]
    networks:
      - claude-net
    security_opt:
      - no-new-privileges:true
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.5"

networks:
  claude-net:
    driver: bridge
    internal: true  # 外部ネットワークアクセスを遮断

volumes:
  claude-config:

internal: true が重要です。このネットワーク内のコンテナはインターネットに直接アクセスできません。外部APIへのアクセスが必要な場合の対処法は手順3で説明します。

手順2:MCP Filesystem Server の設定

MCP Filesystem Server を使うことで、Claude Code がアクセスできるディレクトリを厳密に制御します。

MCP サーバーの設定ファイル

Claude Code の MCP 設定(.claude/mcp.json)を以下のように記述します。

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/data/project",
        "/data/output"
      ],
      "env": {}
    }
  }
}

この設定により、Claude Code は /data/project(読み取り専用)と /data/output(書き込み可能)のみにアクセスでき、それ以外のパスへのファイル操作は拒否されます。

アクセス制御の流れ

こうすることで、たとえ Claude Code が「/etc/passwd を編集して」というような危険な操作を試みても、MCP サーバーの層で確実にブロックされます。

手順3:ネットワークポリシーと秘密情報の隔離

外部アクセスが必要な場合の構成

internal: true にすると外部APIにもアクセスできなくなります。Anthropic APIへのアクセスが必要なので、プロキシコンテナを挟みます。

  # 外部アクセス用プロキシ(許可リスト方式)
  api-proxy:
    image: nginx:alpine
    container_name: api-proxy
    volumes:
      - ./nginx/allowlist.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - claude-net      # 内部ネットワーク
      - external-net    # 外部ネットワーク
    deploy:
      resources:
        limits:
          memory: 128M

networks:
  claude-net:
    driver: bridge
    internal: true
  external-net:
    driver: bridge
    # internal を指定しない → 外部アクセス可

Nginx 許可リスト

# allowlist.conf - 許可するドメインのみプロキシ
server {
    listen 8080;

    # Anthropic API のみ許可
    location /anthropic/ {
        proxy_pass https://api.anthropic.com/;
        proxy_set_header Host api.anthropic.com;
        proxy_ssl_server_name on;
    }

    # GitHub API のみ許可
    location /github/ {
        proxy_pass https://api.github.com/;
        proxy_set_header Host api.github.com;
        proxy_ssl_server_name on;
    }

    # それ以外は全て拒否
    location / {
        return 403 "Access denied by allowlist policy";
    }
}

シークレットの隔離

APIキーやSSH鍵は環境変数で直接渡さず、Docker Secrets を使います。

secrets:
  anthropic_api_key:
    file: ./secrets/anthropic_api_key.txt

services:
  claude-sandbox:
    secrets:
      - anthropic_api_key
    environment:
      # ファイルパスを環境変数で参照
      - ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key

絶対にやってはいけないこと:

  • .env ファイルをコンテナ内にマウントする
  • SSH秘密鍵をイメージにベイクする
  • APIキーを docker-compose.yml にハードコードする

教訓1-3:インフラ運用で踏んだ落とし穴

教訓1:コンテナ再起動時の状態保持

Claude Code はセッション中にコンテキストを蓄積します。コンテナが再起動するとすべて消えます。

解決策:

volumes:
  # Claude の設定とセッション情報を永続化
  - claude-config:/home/claude/.claude
  # 作業中のファイルはホスト側にマウント
  - ./project:/workspace:rw

加えて、Claude Code の --resume フラグでセッションを継続できるようにしておくと、コンテナ再起動後もスムーズに復帰できます。

教訓2:ボリュームマウントのパフォーマンス

macOS の Docker Desktop でバインドマウントを使うと、ファイルI/Oが著しく遅くなります。特にnode_modulesのような大量の小さいファイルがあるディレクトリで顕著です。

解決策:

volumes:
  # node_modules は名前付きボリュームにする(パフォーマンス改善)
  - node_modules:/workspace/node_modules

volumes:
  node_modules:

Linux ホストでは問題になりにくいですが、macOS で開発する場合は名前付きボリュームか :delegated オプションの活用を検討してください。

教訓3:GPU パススルー

ローカルでLLMを動かすわけではないので基本的にGPUは不要ですが、Claude Code にコード生成させた機械学習モデルのテスト実行でGPUが必要になるケースがありました。

services:
  claude-sandbox:
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

NVIDIA Container Toolkit のインストールが前提です。必要なときだけGPU付きプロファイルを使う docker compose --profile gpu up の運用がおすすめです。

教訓4-6:運用と組織展開

教訓4:コスト管理

コンテナを起動しっぱなしにしていると、Claude Code が待機中でもリソースを消費します。また、APIコールの回数管理も重要です。

自動停止スクリプトの例:

#!/bin/bash
# 30分間 Claude Code プロセスのCPU使用率が1%未満なら停止
IDLE_THRESHOLD=1800  # 秒

while true; do
    CPU=$(docker stats claude-sandbox --no-stream --format '{{.CPUPerc}}' | tr -d '%')
    if (( $(echo "$CPU < 1.0" | bc -l) )); then
        IDLE_COUNT=$((IDLE_COUNT + 60))
        if [ $IDLE_COUNT -ge $IDLE_THRESHOLD ]; then
            docker compose stop claude-sandbox
            echo "$(date): Auto-stopped due to inactivity" >> /var/log/claude-sandbox.log
            exit 0
        fi
    else
        IDLE_COUNT=0
    fi
    sleep 60
done

教訓5:ログ収集

Claude Code の入出力ログは、プロンプト改善の宝庫です。しかし、コンテナ内のログはデフォルトでは揮発します。

services:
  claude-sandbox:
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "5"
    volumes:
      # Claude Code のセッションログを永続化
      - claude-logs:/home/claude/.claude/logs

さらに、以下のような wrapper スクリプトを ENTRYPOINT にすると、全セッションの入出力をタイムスタンプ付きで記録できます。

#!/bin/bash
# entrypoint.sh
LOG_DIR="/home/claude/.claude/logs"
SESSION_LOG="${LOG_DIR}/session_$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$LOG_DIR"

echo "=== Session started: $(date) ===" >> "$SESSION_LOG"
claude "$@" 2>&1 | tee -a "$SESSION_LOG"
echo "=== Session ended: $(date) ===" >> "$SESSION_LOG"

教訓6:チームでの共有テンプレート化

個人の環境で動くだけでは不十分です。チームメンバーが git clonedocker compose up で同じ環境を再現できる必要があります。

テンプレートリポジトリの構成:

claude-sandbox-template/
├── docker-compose.yml
├── docker-compose.gpu.yml      # GPU プロファイル
├── Dockerfile
├── .env.example                # シークレットのテンプレート
├── nginx/
│   └── allowlist.conf
├── mcp-servers/
│   └── filesystem/
│       ├── server.js
│       └── package.json
├── project/                    # ここにプロジェクトを配置
│   └── .gitkeep
├── scripts/
│   ├── setup.sh               # 初期セットアップ
│   ├── auto-stop.sh
│   └── entrypoint.sh
├── CLAUDE.md                   # Claude Code 用プロジェクト指示
└── README.md

setup.sh でシークレットファイルの生成と初回ビルドを自動化しておくと、オンボーディングがスムーズになります。

#!/bin/bash
# setup.sh
echo "=== Claude Code Sandbox Setup ==="

if [ ! -f ./secrets/anthropic_api_key.txt ]; then
    mkdir -p ./secrets
    read -sp "Enter your Anthropic API Key: " API_KEY
    echo "$API_KEY" > ./secrets/anthropic_api_key.txt
    echo ""
    echo "✅ API key saved"
fi

docker compose build
echo "✅ Setup complete. Run: docker compose up -d"

まとめ

  • AIエージェントにはDocker Sandboxによる物理的な隔離が必須──設定ベースの権限制御だけでは、設定ミスひとつでホスト環境が破壊されるリスクがある
  • MCP Filesystem Server でアクセス可能なディレクトリをホワイトリスト方式で制限することで、LLMの誤操作を確実にブロックできる
  • テンプレート化してチームに展開することが最終ゴール── git clonedocker compose up で誰でも同じセキュアな環境を再現できる状態を目指す

テンプレートリポジトリは近日中にGitHubで公開予定です。今後の改善計画としては、Devcontainer対応によるVS Code / Cursor統合、MCPサーバーの動的な権限変更(プロジェクトごとのポリシーファイル)、コスト分析ダッシュボードの追加を予定しています。

参考リンク

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?