この記事を読んで、Cline / RooCodeなどを使うなら、
ちゃんと使えるようにしたいなと思い、いろいろ調べてみたときの備忘録(*´ω`*)
DevContainerとは
Dev Containerは、Dockerコンテナ上で開発するための拡張機能。
環境構築をコンテナとして用意でき、ローカル環境も汚さないですむ
また、開いたフォルダをコンテナ上にマウントして、コンテナ内で実行するので、
それ以外のファイルにアクセスできなくすることができるので、AIエージェントを使うときにも安全
コンテナへの接続方法はいくつかあり、それに応じて、必要な拡張機能をインストールする
DevContainerを試してみる
必要なファイルとしては、こんな感じ。
.devcontainer/ ├── devcontainer.json ... 必須: 設定ファイル └── Dockerfile ... 任意: 自分でイメージを作るなら
左下の「><」のアイコンから、Dev Containerのセットアップや開き直すことができる
初回だと、用意されているテンプレートを選択できる感じ

テンプレートだと、こんなのを作成してくれる
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/debian { "name": "Debian", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/base:bullseye", "features": { "ghcr.io/devcontainers/features/github-cli:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Configure tool-specific properties. // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" }
用意されているイメージ
microsoft自体が、dev container用のdocker imageを用意してくれているので、
これで十分であれば、devcontainer.jsonだけでOK
- Docker Hub
- devcontainers/images: Repository for pre-built dev container images published under mcr.microsoft.com/devcontainers
github-cliのようなfeaturesという仕組みがあって、
必要なのを指定すれば、自前でDockerfileを用意しなくても、ある程度カスタマイズができる
- Features
- devcontainers/features: A collection of Dev Container Features managed by Dev Container spec maintainers. See https://github.com/devcontainers/feature-starter to publish your own
devcontainer.jsonの中身
リファレンスはこのあたり。ガイドをみると、実際の設定方法が書かれている
よく使うものだと、こんな感じ
{ // コンテナの名前 "name": "Debian", // コンテナのイメージ "image": "mcr.microsoft.com/devcontainers/base:bullseye", // コンテナのイメージに追加する機能 "features": { "ghcr.io/devcontainers/features/github-cli:1": {} }, "customizations": { "vscode": { "extensions": [ // コンテナ上の各条機能 ], "settings": { // コンテナ上のVSCodeの設定 } } }, // コンテナ上のユーザ名 "remoteUser": "node", "mounts": [ // コンテナにマウントするファイルの設定 ], "remoteEnv": { // コンテナ上の環境変数 }, // コンテナ上のワークスペースのフォルダ "workspaceFolder": "/workspace", // コンテナ作成時に実行するコマンド "postCreateCommand": "..." }
ローカルとは別環境なので、拡張機能や設定は、ここに追加しないといけない
Dev Containerは実行だけで、コーディングはローカルのVSCodeでするとかであれば不要
Tips
コンテナ上でGitにアクセスしたい
Dev Contaier内は、別環境なので、ローカルのSSH秘密鍵にアクセスできない
そのため、pushとかもコンテナからはできない。。。
クレデンシャルを共有する方法も、このガイドに書かれている感じ
- Sharing Git credentials with your container
- Git に GitHub の認証情報をキャッシュする - GitHub Docs
- How to configure ssh-agent, agent forwarding & protocol
ssh-agentは、秘密鍵を登録しておくと、コンテナなどリモート上で必要になった際に、
登録した秘密鍵をリモート側から利用できるようにしてくれるエージェントサービスらしい
ssh-agentでの設定
ホスト側(ローカル側)で設定などが必要
# ssh-agentの起動。macの場合はeval不要 $ ssh-agent # エージェント経由で提供する秘密鍵の登録 $ ssh-add --apple-use-keychain ~/.ssh/id_rsa # or $ ssh-add --apple-use-keychain # 登録状況の確認 $ ssh-add -L
shell起動ごとに実行する必要があるので、
.bashrcなどに以下を追加して、起動するようにしておく
# .bashrc ssh-agent > /dev/null ssh-add --apple-use-keychain 2> /dev/null
エージェントに追加する秘密鍵は、
~/.ssh/configでAddKeysToAgentをつけて指定できる
# ~/.ssh/config Host * AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/id_rsa
最後に、コンテナを起動して、
コンテナ上から接続できればOK
ssh -T git@github.com
これで、ローカルのsshの設置をDevContainer上から利用することができる
Claude Code提供のサンプル構成
Claude Codeでもサンプル構成が公開されていて、
AIエージェント向けのDevContainerとして参考になる
Node20ベースで、zshなどが用意されているDockerfile
iptablesを使った独自のファイアーウォール設定もあるので、
指定したURL以外はアクセスできないようになっている
- Claude Code overview - Anthropic
- anthropics/claude-code: Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
これをベースにいろいろ設定していくと、ある程度安全に利用できそう
ただ、絶対に安全ではないので、注意しつつカスタマイズしていくとよさそう
作ってみたDevContainer
基本は、Claude Codeをベースにしつつ、
pnpmを利用できるようにしてみた
Dockerfile
# .devcontainer/Dockerfile # ref: https://github.com/anthropics/claude-code/blob/main/.devcontainer/Dockerfile FROM node:23.5.0 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 # 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 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" # Set up non-root user USER node # 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 bash 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 Claude and pnpm RUN npm install -g @anthropic-ai/claude-code pnpm # 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
devcontainer.json
devcontainer.jsonは、extensionsを好きなのを追加
roo-clineを追加したり、などなど
// .devcontainer/devcontainer.json { "name": "my-devcontainer", "build": { "dockerfile": "Dockerfile", "args": { "TZ": "${localEnv:TZ:Asia/Tokyo}" } }, "runArgs": [ "--cap-add=NET_ADMIN", "--cap-add=NET_RAW" ], // Configure tool-specific properties. "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner", "rooveterinaryinc.roo-cline", "DavidAnson.vscode-markdownlint", "corschenzi.mermaid-graphical-editor" ], "settings": { "terminal.integrated.defaultProfile.linux": "zsh", "terminal.integrated.profiles.linux": { "bash": { "path": "bash", "icon": "terminal-bash" }, "zsh": { "path": "zsh" } } } } }, "remoteUser": "node", "mounts": [ "source=claude-code-bashhistory,target=/commandhistory,type=volume", "source=claude-code-config,target=/home/node/.claude,type=volume" ], "remoteEnv": { "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" }
init-firewall.sh
ファイアーウォールの設定は、そのまま
必要に応じて、許可するドメインを調整する感じ
#!/bin/bash
# devcontainer/init-firewall.sh
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" \
"api.anthropic.com" \
"sentry.io" \
"statsig.anthropic.com" \
"statsig.com"; do
echo "Resolving $domain..."
ips=$(dig +short A "$domain")
if [ -z "$ips" ]; then
echo "ERROR: Failed to resolve $domain"
exit 1
fi
while read -r ip; do
if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
echo "ERROR: Invalid IP from DNS for $domain: $ip"
exit 1
fi
echo "Adding $ip for $domain"
ipset add allowed-domains "$ip"
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
# 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
以上!! これで安全にAIエージェントと使えるように(*´ω`*)
参考にしたサイト様
- Developing inside a Container
- microsoft/devcontainers-typescript-node - Docker Image | Docker Hub
- devcontainers/images: Repository for pre-built dev container images published under mcr.microsoft.com/devcontainers
- devcontainers/features: A collection of Dev Container Features managed by Dev Container spec maintainers. See https://github.com/devcontainers/feature-starter to publish your own
- [Git]VSCode Devcontainer内からGit操作をする手順
- Sharing Git credentials with your container
- 新しい SSH キーを生成して ssh-agent に追加する - GitHub Docs
- Claude Code overview - Anthropic
- anthropics/claude-code: Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
- Cline / RooCodeを安全に使うためにDevContainerを使い始めた