Claude Codeの公式コンテナをPython用にカスタマイズする
はじめに
Claude Code が話題になってからしばらく経ち、基本的な使い方には慣れてきた方も多いのではないでしょうか。そうなると、次は「並列で動かす」「完全に手放しで動かす」といった、より高度な使い方にも挑戦してみたくなるはずです。私自身もその一人です。
こうした上級的な運用を目指すうえで避けて通れないのが、サンドボックス環境の構築です。AI に高い権限を与える以上、それに見合ったリスクヘッジが求められます(自分の環境をぶっ壊されてからではもう遅いですね)。
そこで今回は、Anthropic 公式の Claude Code 向け開発コンテナ(DevContainer)の構成をベースに、Python 開発に特化してカスタマイズした内容をご紹介します。各ファイルの構成も詳しく記載していますので、ご自身の用途に合わせて柔軟にカスタマイズしていただけると思います。
なお、Claude Code や DevContainers の基本的な導入手順や使い方については、本記事では扱いません。事前に他の資料や記事を参考に準備しておいてください。
開発コンテナを使うメリット
まず、開発コンテナを導入することで得られる主な利点は以下の通りになります。
1. ホスト環境の保護
コンテナ化の最大のメリットは、ホスト環境を破壊から守れるということです。たとえば、Claude Code が rm -rf
のような危険なコマンドを実行したとしても、その影響はコンテナ内にとどまり、ホスト環境には及びません。これにより、--dangerously-skip-permissions
や auto-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」
-
-
フォーマッターの変更:
defaultFormatter
にblack
を指定 -
Lint 設定:
flake8
とpylint
の両方を有効化 -
インタプリタ指定:
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 アドレスへの通信のみを許可します。
主な処理フロー
-
初期化: 既存の
iptables
ルールおよびipset
を全て削除してクリーンな状態にリセット -
基本通信の許可: DNS、SSH、ループバック(
localhost
)通信を事前に許可 -
ホワイトリスト集合の作成:
ipset create allowed-domains hash:net
により許可先集合を作成 -
GitHub IPの取得: https://api.github.com/meta より、web/api/git の各IPを取得し、
aggregate
で集約後にipset
へ登録 -
固定ドメインのIP解決:
registry.npmjs.org
,api.anthropic.com
など特定ドメインのIPをdig
で解決し、ipset
へ追加 -
ホストネットワークの許可: デフォルトルートからホストIPを検出し、その
/24
ネットワークに対する入出力を許可 -
デフォルトポリシーの適用:
INPUT
,FORWARD
,OUTPUT
をDROP
に設定 - 既存セッションの許可: 既に確立されたセッションと関連通信を許可
-
宛先制限:
allowed-domains
に登録された宛先のみをOUTPUT
で許可 -
動作確認:
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