🐳

Claude Codeの公式コンテナをPython用にカスタマイズする

に公開

はじめに

Claude Code が話題になってからしばらく経ち、基本的な使い方には慣れてきた方も多いのではないでしょうか。そうなると、次は「並列で動かす」「完全に手放しで動かす」といった、より高度な使い方にも挑戦してみたくなるはずです。私自身もその一人です。

こうした上級的な運用を目指すうえで避けて通れないのが、サンドボックス環境の構築です。AI に高い権限を与える以上、それに見合ったリスクヘッジが求められます(自分の環境をぶっ壊されてからではもう遅いですね)。

そこで今回は、Anthropic 公式の Claude Code 向け開発コンテナ(DevContainer)の構成をベースに、Python 開発に特化してカスタマイズした内容をご紹介します。各ファイルの構成も詳しく記載していますので、ご自身の用途に合わせて柔軟にカスタマイズしていただけると思います。

なお、Claude Code や DevContainers の基本的な導入手順や使い方については、本記事では扱いません。事前に他の資料や記事を参考に準備しておいてください。

開発コンテナを使うメリット

まず、開発コンテナを導入することで得られる主な利点は以下の通りになります。

1. ホスト環境の保護

コンテナ化の最大のメリットは、ホスト環境を破壊から守れるということです。たとえば、Claude Code が rm -rf のような危険なコマンドを実行したとしても、その影響はコンテナ内にとどまり、ホスト環境には及びません。これにより、--dangerously-skip-permissionsauto-accept edits on といったオプションも、ある程度は安心して有効化し自動実行することが可能になります。

2. セキュリティの強化

ファイアウォールを正しく設定することで、外部ネットワークへのアクセスを制限し、ホワイトリスト方式でのアクセス制御が可能となります。これにより、Claude Code が不審な外部サイトにアクセスするのを防ぐことができます。

3. 環境の一貫性と開発効率の向上

コンテナ化における一般的な利点にはなりますが、すべての開発者が同一の Python バージョン・依存ライブラリ・拡張機能セットを即座に共有できます。環境差異を最小限に抑えられるため、チーム開発にはもってこいです。

各ファイルの詳細

ここからは、開発コンテナを作成する際に必要な各ファイルについて順に解説していきます。公式は Node.js 向けに作成されているので、コンテナをカスタマイズする際にはどの部分をどのように変更すればよいかの参考としてご活用ください。

devcontainer.json

VS Code の Dev Containers 機能を制御する中心的な設定ファイルです。コンテナのビルド方法や起動設定、エディタの拡張機能や各種設定を一元的に管理できます。

コンテナの基本設定

"name": "Claude Code Sandbox",
"build": {
  "dockerfile": "Dockerfile",
  "args": {
    "TZ": "${localEnv:TZ:America/Los_Angeles}"
  }
},
"runArgs": [
  "--cap-add=NET_ADMIN",
  "--cap-add=NET_RAW"
]
  • name: VS Code のステータスバーやコンテナ一覧に表示される名称
  • build: コンテナのビルドに関する設定
    • dockerfile: ビルドに使用する Dockerfile のパス
    • args: Dockerfile 内の ARG 命令に渡すビルド時引数
  • runArgs: コンテナ実行時に docker run に渡す追加オプション

VS Code 設定

"customizations": {
  "vscode": {
    "extensions": [
      "dbaeumer.vscode-eslint",
      "esbenp.prettier-vscode",
      "eamodio.gitlens"
    ],
    "settings": {
      "editor.formatOnSave": true,
      "editor.defaultFormatter": "esbenp.prettier-vscode",
      "editor.codeActionsOnSave": {
        "source.fixAll.eslint": "explicit"
      },
      "terminal.integrated.defaultProfile.linux": "zsh",
      "terminal.integrated.profiles.linux": {
        "bash": {
          "path": "bash",
          "icon": "terminal-bash"
        },
        "zsh": {
          "path": "zsh"
        }
      }
    }
  }
}
  • customizations.vscode: VS Code に関するカスタマイズ項目
    • extensions: コンテナ接続時に自動インストールされる拡張機能
    • settings: コンテナ内で有効な VS Code の設定

実行ユーザー・ボリューム・環境変数など

"remoteUser": "node",
"mounts": [
  "source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
  "source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
],
"remoteEnv": {
  "NODE_OPTIONS": "--max-old-space-size=4096",
  "CLAUDE_CONFIG_DIR": "/home/node/.claude",
  "POWERLEVEL9K_DISABLE_GITSTATUS": "true"
},
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
"workspaceFolder": "/workspace",
"postCreateCommand": "sudo /usr/local/bin/init-firewall.sh"
  • remoteUser: コンテナ内でのデフォルトユーザー
  • mounts: ホストとコンテナ間で共有するボリュームの設定
    • source: ホスト側のボリューム名
    • target: コンテナ内のマウント先ディレクトリ
    • type=volume: 永続化可能な Docker ボリュームとして指定
  • remoteEnv: コンテナ内で使用する環境変数
  • workspaceMount: ローカルのワークスペースをコンテナにマウント
  • workspaceFolder: コンテナ接続時に VS Code が開くディレクトリ
  • postCreateCommand: コンテナ作成後、一度だけ実行される初期化コマンド

Python向けカスタマイズ

devcontainer.json
{
  "name": "Python Development Sandbox",
  "build": {
    "dockerfile": "Dockerfile",
    "args": {
      "TZ": "${localEnv:TZ:Asia/Tokyo}"
    }
  },
  "runArgs": [
    "--cap-add=NET_ADMIN",
    "--cap-add=NET_RAW"
  ],
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python",
        "ms-python.black-formatter",
        "ms-python.flake8",
        "ms-python.pylint",
        "ms-toolsai.jupyter",
        "eamodio.gitlens",
        "anthropic.claude-code"
      ],
      "settings": {
        "editor.formatOnSave": true,
        "editor.defaultFormatter": "ms-python.black-formatter",
        "editor.codeActionsOnSave": {
          "source.organizeImports": "explicit"
        },
        "python.defaultInterpreterPath": "/usr/local/bin/python",
        "python.linting.enabled": true,
        "python.linting.flake8Enabled": true,
        "python.linting.pylintEnabled": true,
        "terminal.integrated.defaultProfile.linux": "zsh",
        "terminal.integrated.profiles.linux": {
          "bash": {
            "path": "bash",
            "icon": "terminal-bash"
          },
          "zsh": {
            "path": "zsh"
          }
        }
      }
    }
  },
  "remoteUser": "python",
  "mounts": [
    "source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
    "source=claude-code-config-${devcontainerId},target=/home/python/.claude,type=volume"
  ],
  "remoteEnv": {
    "PYTHONPATH": "/workspace",
    "PYTHON_UNBUFFERED": "1",
    "CLAUDE_CONFIG_DIR": "/home/python/.claude",
    "POWERLEVEL9K_DISABLE_GITSTATUS": "true"
  },
  "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
  "workspaceFolder": "/workspace",
  "postCreateCommand": "sudo /usr/local/bin/init-firewall.sh"
}

変更点

  • コンテナ名: Python Development Sandbox に変更(プロジェクトに合わせて適宜変更)
  • タイムゾーン: Asia/Tokyo に設定し、日本時間で動作
  • 拡張機能: JS/TS 向け(eslint, prettier)を削除し Python 向けに差し替え
    • ms-python.python: Python の基本拡張機能
    • ms-python.black-formatter: フォーマッター「Black
    • ms-python.flake8: 静的解析ツール「Flake8
    • ms-python.pylint: 静的解析ツール「Pylint
    • ms-toolsai.jupyter: ノートブック環境「Jupyter Notebook
  • フォーマッターの変更: defaultFormatterblack を指定
  • Lint 設定: flake8pylint の両方を有効化
  • インタプリタ指定: Python.defaultInterpreterPath で使用する Python パスを明示
  • 環境変数追加:
    • PYTHONPATH: /workspace を追加し、ルート直下のモジュール参照を有効化
    • PYTHON_UNBUFFERED: "1" を指定して標準出力をリアルタイムで反映
  • ユーザー名: デフォルトユーザーを node から python に変更

Dockerfile

開発コンテナのイメージをビルドするための手順を記述したファイルです。ベースイメージの選定、パッケージのインストール、ユーザー権限の設定、ツールチェーンの構築などをコード化します。

ベースイメージと基本ツール

FROM node:20

ARG TZ
ENV TZ="$TZ"

# Install basic development tools and iptables/ipset
RUN apt update && apt install -y less \
  git \
  procps \
  sudo \
  fzf \
  zsh \
  man-db \
  unzip \
  gnupg2 \
  gh \
  iptables \
  ipset \
  iproute2 \
  dnsutils \
  aggregate \
  jq
  • FROM: コンテナの土台となるベースイメージ
  • ARG TZ / ENV TZ: ビルド時に渡したタイムゾーンをランタイム環境変数へ反映
  • apt install: 開発・監視・ネットワーク制御・JSON 処理などに必要なツール群を導入

ユーザーと権限設定

# Ensure default node user has access to /usr/local/share
RUN mkdir -p /usr/local/share/npm-global && \
  chown -R node:node /usr/local/share

ARG USERNAME=node

# Persist bash history.
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  && mkdir /commandhistory \
  && touch /commandhistory/.bash_history \
  && chown -R $USERNAME /commandhistory

# Set `DEVCONTAINER` environment variable to help with orientation
ENV DEVCONTAINER=true

# Create workspace and config directories and set permissions
RUN mkdir -p /workspace /home/node/.claude && \
  chown -R node:node /workspace /home/node/.claude

WORKDIR /workspace

USER node
  • chown: 一般ユーザーが npm グローバルパスや履歴ファイルへアクセスできるよう所有権を調整
  • WORKDIR: 作業ディレクトリを /workspace に設定
  • USER: 以降のコマンドを node ユーザーで実行

開発環境のカスタマイズ

RUN ARCH=$(dpkg --print-architecture) && \
  wget "https://github.com/dandavison/delta/releases/download/0.18.2/git-delta_0.18.2_${ARCH}.deb" && \
  sudo dpkg -i "git-delta_0.18.2_${ARCH}.deb" && \
  rm "git-delta_0.18.2_${ARCH}.deb"

# Install global packages
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
ENV PATH=$PATH:/usr/local/share/npm-global/bin

# Set the default shell to zsh rather than sh
ENV SHELL=/bin/zsh

# Default powerline10k theme
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.0/zsh-in-docker.sh)" -- \
  -p git \
  -p fzf \
  -a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
  -a "source /usr/share/doc/fzf/examples/completion.zsh" \
  -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  -x
  • git-delta: Git の差分表示を見やすくするビジュアルツール
  • NPM_CONFIG_PREFIX / PATH: npm グローバルパッケージのパスを調整し、一般ユーザーでも利用可能に
  • ENV SHELL=/bin/zsh: デフォルトのシェルを zsh に変更
  • zsh-in-docker.sh: Powerlevel10k テーマやプラグイン(git, fzf)をインストールし、zsh の設定を一括適用

Claude Code CLI とファイアウォール設定

# Install Claude
RUN npm install -g @anthropic-ai/claude-code

# Copy and set up firewall script
COPY init-firewall.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/init-firewall.sh && \
  echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \
  chmod 0440 /etc/sudoers.d/node-firewall
USER node
  • Claude Code CLI: @anthropic-ai/claude-code をグローバルインストール
  • ファイアウォールスクリプト: sudoers により対象スクリプトの無パスワード実行を許可し、最小権限を確保

Python向けカスタマイズ

Dockerfile
FROM python:3.11-slim

ARG TZ
ENV TZ="$TZ"

# Install basic development tools and iptables/ipset
RUN apt update && apt install -y less \
  git \
  procps \
  sudo \
  fzf \
  zsh \
  man-db \
  unzip \
  gnupg2 \
  gh \
  iptables \
  ipset \
  iproute2 \
  dnsutils \
  aggregate \
  jq \
  curl \
  wget \
  build-essential

# Create python user
RUN useradd -m -s /bin/zsh python && \
  usermod -aG sudo python && \
  echo 'python ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

ARG USERNAME=python

# Persist bash history.
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  && mkdir /commandhistory \
  && touch /commandhistory/.bash_history \
  && chown -R $USERNAME /commandhistory

# Set `DEVCONTAINER` environment variable to help with orientation
ENV DEVCONTAINER=true

# Create workspace and config directories and set permissions
RUN mkdir -p /workspace /home/python/.claude && \
  chown -R python:python /workspace /home/python/.claude

WORKDIR /workspace

RUN ARCH=$(dpkg --print-architecture) && \
  wget "https://github.com/dandavison/delta/releases/download/0.18.2/git-delta_0.18.2_${ARCH}.deb" && \
  sudo dpkg -i "git-delta_0.18.2_${ARCH}.deb" && \
  rm "git-delta_0.18.2_${ARCH}.deb"

# Install Python development tools
RUN pip install --upgrade pip && \
  pip install poetry black flake8 pylint pytest jupyter ipython

# Set up non-root user
USER python

# Set the default shell to zsh rather than sh
ENV SHELL=/bin/zsh

# Default powerline10k theme
RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.0/zsh-in-docker.sh)" -- \
  -p git \
  -p fzf \
  -a "source /usr/share/doc/fzf/examples/key-bindings.zsh" \
  -a "source /usr/share/doc/fzf/examples/completion.zsh" \
  -a "export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
  -x

# Install Node.js for Claude Code
USER root
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
  apt-get install -y nodejs

# Install Claude Code via npm
RUN npm install -g @anthropic-ai/claude-code
USER python

# Copy and set up firewall script
COPY init-firewall.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/init-firewall.sh && \
  echo "python ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/python-firewall && \
  chmod 0440 /etc/sudoers.d/python-firewall
USER python

変更点

  • ベースイメージ: 軽量な Python 公式イメージである python:3.11-slim に変更
  • パッケージ構成: Python 開発に必要なツールを網羅的にインストール
    • curl, wget, build-essential などビルドや通信に必要な基本ツールを追加
  • ユーザー作成: python ユーザーを新規作成し、zsh を標準シェルに設定
  • Pythonツール: pip によって主要な開発支援ツールを一括インストール
    • poetry, black, flake8, pylint, pytest, jupyterなど
  • Claude Code CLI:
    • npm を使用して @anthropic-ai/claude-code をインストール

init-firewall.sh

開発コンテナの外部通信をホワイトリスト方式で制御するための初期化スクリプトです。postCreateCommand によりコンテナ作成後に一度だけ実行され、信頼できる特定のドメインの IP アドレスへの通信のみを許可します。

主な処理フロー

  1. 初期化: 既存の iptables ルールおよび ipset を全て削除してクリーンな状態にリセット
  2. 基本通信の許可: DNS、SSH、ループバック(localhost)通信を事前に許可
  3. ホワイトリスト集合の作成: ipset create allowed-domains hash:net により許可先集合を作成
  4. GitHub IPの取得: https://api.github.com/meta より、web/api/git の各IPを取得し、aggregate で集約後に ipset へ登録
  5. 固定ドメインのIP解決: registry.npmjs.org, api.anthropic.com など特定ドメインのIPを dig で解決し、ipset へ追加
  6. ホストネットワークの許可: デフォルトルートからホストIPを検出し、その /24 ネットワークに対する入出力を許可
  7. デフォルトポリシーの適用: INPUT, FORWARD, OUTPUTDROP に設定
  8. 既存セッションの許可: 既に確立されたセッションと関連通信を許可
  9. 宛先制限: allowed-domains に登録された宛先のみを OUTPUT で許可
  10. 動作確認: example.com への通信遮断、api.github.com への通信許可を検証

Python向けカスタマイズ

init-firewall.sh
#!/bin/bash
set -euo pipefail  # Exit on error, undefined vars, and pipeline failures
IFS=$'\n\t'       # Stricter word splitting

# Flush existing rules and delete existing ipsets
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
ipset destroy allowed-domains 2>/dev/null || true

# First allow DNS and localhost before any restrictions
# Allow outbound DNS
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
# Allow inbound DNS responses
iptables -A INPUT -p udp --sport 53 -j ACCEPT
# Allow outbound SSH
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT
# Allow inbound SSH responses
iptables -A INPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
# Allow localhost
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Create ipset with CIDR support
ipset create allowed-domains hash:net

# Fetch GitHub meta information and aggregate + add their IP ranges
echo "Fetching GitHub IP ranges..."
gh_ranges=$(curl -s https://api.github.com/meta)
if [ -z "$gh_ranges" ]; then
    echo "ERROR: Failed to fetch GitHub IP ranges"
    exit 1
fi

if ! echo "$gh_ranges" | jq -e '.web and .api and .git' >/dev/null; then
    echo "ERROR: GitHub API response missing required fields"
    exit 1
fi

echo "Processing GitHub IPs..."
while read -r cidr; do
    if [[ ! "$cidr" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
        echo "ERROR: Invalid CIDR range from GitHub meta: $cidr"
        exit 1
    fi
    echo "Adding GitHub range $cidr"
    ipset add allowed-domains "$cidr"
done < <(echo "$gh_ranges" | jq -r '(.web + .api + .git)[]' | aggregate -q)

# Resolve and add other allowed domains
for domain in \
    "registry.npmjs.org" \
    "pypi.org" \
    "files.pythonhosted.org" \
    "api.anthropic.com" \
    "sentry.io" \
    "statsig.anthropic.com" \
    "statsig.com"; do
    echo "Resolving $domain..."
    ips=$(dig +short A "$domain")
    if [ -z "$ips" ]; then
        echo "WARNING: Failed to resolve $domain. Skipping."
        continue
    fi
    
    while read -r ip; do
        if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
            echo "Adding $ip for $domain"
            ipset add allowed-domains "$ip" 2>/dev/null || true
        else
            echo "WARNING: Ignoring non-IPv4 address for $domain: $ip"
        fi
    done < <(echo "$ips")
done

# Get host IP from default route
HOST_IP=$(ip route | grep default | cut -d" " -f3)
if [ -z "$HOST_IP" ]; then
    echo "ERROR: Failed to detect host IP"
    exit 1
fi

HOST_NETWORK=$(echo "$HOST_IP" | sed "s/\.[0-9]*$/.0\/24/")
echo "Host network detected as: $HOST_NETWORK"

# Set up remaining iptables rules
iptables -A INPUT -s "$HOST_NETWORK" -j ACCEPT
iptables -A OUTPUT -d "$HOST_NETWORK" -j ACCEPT

# Set default policies to DROP first
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# First allow established connections for already approved traffic
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Then allow only specific outbound traffic to allowed domains
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT

echo "Firewall configuration complete"
echo "Verifying firewall rules..."
if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
    echo "ERROR: Firewall verification failed - was able to reach https://example.com"
    exit 1
else
    echo "Firewall verification passed - unable to reach https://example.com as expected"
fi

# Verify GitHub API access
if ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
    echo "ERROR: Firewall verification failed - unable to reach https://api.github.com"
    exit 1
else
    echo "Firewall verification passed - able to reach https://api.github.com as expected"
fi

変更点

  • PyPI 関連ドメインの追加: Pythonパッケージ取得に必要な以下のドメインをホワイトリストへ追加
    • pypi.org, files.pythonhosted.org(pip利用時に必須)
  • エラー処理の修正: エラー時にスクリプトが途中停止しないように修正
    • files.pythonhosted.org の名前解決の失敗や ipset の重複などで、スクリプトが途中停止するため

おわりに

Python 向けの Claude Code 用の開発コンテナを作成してみました。試行錯誤をしながら作成したばかりなので、より良い設定方法がまだまだあると思います。使っていく中で改善点や新たな設定などを見つけた際には、随時記事に追記していきます。

もし同じような開発環境をうまく構築されている方がいらっしゃいましたら、コメントなどで教えていただけると大変嬉しいです!最後までお読みいただき、ありがとうございました。

Discussion