Claude Code の利用にあたり、DevContainerをしっかり勉強する。
このblogはその勉強ログ。
目標
Devcontainerを利用することで、Claude Codeの --dangerously-skip-permissions
を利用できる環境を構築する。
--dangerously-skip-permissions
について
これはClaude Codeに全権を握らせるモード。すべての操作について、ユーザーからの許可を求めず、自動的に実行ができる。
がゆえに、破壊的なことも起きる。たとえば、rm -rf /
とか。だから、コンテナなどを使って、環境を分離することが必要。
ということで、最終目標はこの--dangerously-skip-permissions
を利用できる環境を構築すること。
具体的には
git worktree
を利用し、メインブランチとは別環境のディレクトリを作成
- Worktreeごと各自に独立したdevcontainerを立ち上げる
- 各devcontainer内でClaude Codeが稼働し、実装を行う
さて、ここまでは前提情報として、以下は雑多なメモになる。
結論
成果物リポジトリ
↑ これが作れた。
Claude Codeが提供しているdevcontainerを使って、featureに言語機能を付ければok!
ここから完全に、私が勉強するときにつけたメモです。
(構成したり、読みやすくしたりとかナシ)
メモ
devcontainer is 何?
まずはそもそも、devcontainerが、どんな目的で、何を達成するための技術なのかを確認したい
→ 概要を把握したので、いったんChatGPTに聞いてみる
devcontainer(Dev Container)は、主にVisual Studio Code(VS Code)やGitHub Codespacesなどで提供される、**開発環境をDockerコンテナ内で定義・起動し、再現性の高い環境で開発を行うための技術**です。以下、目的や得られるメリット、その仕組みについて整理します。
---
## 1. 目的:開発環境の「再現性」と「分離性」を確保する
* **環境差によるトラブルの削減**
各開発者のローカル環境(OSやインストール済みツールのバージョンなど)が異なることで起こる「動かない」「挙動が違う」といった問題を、コンテナイメージを共有することで根本的に解消できます。 ([Visual Studio Code][1])
* **スピーディーなオンボーディング**
新しくプロジェクトに参加するメンバーは、DockerとVS Codeがあれば `.devcontainer.json` をリポジトリから取得し、コマンド一つで環境が立ち上がるため、面倒なセットアップ手順を書いたREADMEが不要に近づきます。 ([Medium][2])
* **環境の分離**
プロジェクトごと、言語ごと、サービスごとにコンテナを分けることで、ローカル環境を汚さずに複数案件を並行して開発できます。特にマイクロサービスの開発や幅広い技術スタックを扱う場合に有効です。
---
## 2. 実現できること
1. **統一されたツールチェーン**
コンテナ内に必要な言語ランタイム(Node.js, Python, Go など)やデバッガ、リンター、拡張機能をインストールし、VS CodeのIntelliSenseやデバッグ機能をフル活用できます。 ([Visual Studio Code][3])
2. **マルチコンテナ構成**
Docker Composeを組み合わせれば、DBやキャッシュ、バックエンド/フロントエンドなど複数コンテナを一括で起動し、ローカルに近い本番環境を再現可能です。
3. **クラウドIDEとの連携**
GitHub CodespacesやVS Code for Webでは、同じ `.devcontainer.json` を参照してクラウド上で開発環境を構築でき、ローカル不要で開発を続行できます。 ([GitHub Docs][4])
---
## 3. 仕組みの概要
* プロジェクトルートに `.devcontainer/devcontainer.json`(または `.devcontainer.json`)を置き、**どのイメージを使うか**、**どのDockerfileをビルドするか**、**どの拡張機能をインストールするか** といった設定をJSON形式で定義します。
* VS Codeの「Remote – Containers」拡張機能や Dev Container CLI がこのファイルを読み込み、コンテナの起動・接続・環境構築を自動化します。
* 設定は [Dev Containers Specification](https://containers.dev) としてオープンになっており、VS Code以外のツールや環境でもサポートが広がっています。
---
## 4. 私見と今後の展望
私の意見では、devcontainerは\*\*チーム開発のDX(開発体験向上)\*\*に大きく貢献する技術です。とくに複数の異なるOSやランタイムを扱うプロジェクトでは、環境構築の手間をゼロに近づけられる点が魅力的です。一方で、Docker自体の学習コストやビルド時間の増大には注意が必要で、軽量化のためのキャッシュ戦略やマルチステージビルドの活用が求められます。
近年はCodespacesやGitPodといったクラウドIDEでも同じ設定が流用できるため、ローカルに依存しない開発ワークフローの実現が加速しています。今後はローカル/クラウドの垣根を意識せずシームレスに移動できる、より「開発環境のユニバーサル化」が進むでしょう。
[1]: https://code.visualstudio.com/docs/devcontainers/containers?utm_source=chatgpt.com "Developing inside a Container"
[2]: https://medium.com/versent-tech-blog/introduction-to-dev-containers-4c01cb1752a0?utm_source=chatgpt.com "Introduction to Dev Containers"
[3]: https://code.visualstudio.com/docs/devcontainers/create-dev-container?utm_source=chatgpt.com "Create a Dev Container"
[4]: https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers?utm_source=chatgpt.com "Introduction to dev containers - Codespaces"
第一に知りたいことは、何となくわかった。
つまり、
- 開発環境の再現性を確保した
- ので、コンテナを使ってハードウェアやOSに寄らない開発環境を作成する
- そのために、devcontainer.jsonというファイルを作成する
ということは、 devcontainer.json
の記法・設定内容を知る必要がある。
devcontainer.json
本家大元のCreate a Dev Containerを見てみる。
.devcontainer/devcotainer.json
がdevcontainerの設定ファイル
- ベースイメージは
image
で指定する
- Docker Hub などで配布しているコンテナが選べそう
- DockerFileを使って、カスタムイメージを作成することもできる
- Docker Composeも使える
- 追加のツールなどもinstallできる
- vscodeの拡張機能とか
- cliとか
- 環境変数とかも入れられる
- featureで提携的に追加のファイルなどを追加可能
- featureの設定方法
feature
ディレクトリで外部ファイルとして管理することも可能
- これ以上の深堀りは必要になったら
所感
- 今回の目的の場合、Claude Codeが提供しているコンテナイメージを利用する形になるか
- このドキュメントだとVSCodeを利用する前提になっている
- が、Claude CodeはCLIなので、worktreeごとにVSCodeを立ち上げるのはちょっと面倒
- ↑ たしか、
devcontainer-cli
があったはずで、これを使えばcliで立ち上げてられるかも
Claude Code公式のdevcontainerのドキュメント
Claude Code 開発コンテナのページを読む。Anthropicの公式ドキュメントで、Claude Codeを利用するための、devcontainerの設定ファイルなどがある。
- すでにClaude Code向けに基本的な設定をしたdevcontainerファイルが用意されている
init-firewall.sh
でネットワークのセキュリティ設定もできる
- 基本的にはホワイトリスト方式で接続を制御できる
- 公式のリファレンス実装
- Claude Codeが自動的にインストールされているので、こいつにプロンプトを渡せば、その環境で実行できる
たぶん、基本的にはリファレンスの実装を使っていればいいのかな
→ 必要な言語のインストールなどはどう行えばいいのか?
例えばRustを使いたい場合は?
→ これへの回答がdevcontainer.jsonのfeatureにあたるのでは?
Rustのfeatureがあったから、Rustはfeatureで指定できるのか。
便利だなこれ
あとはどうやって、git worktree + devcontainerの組み合わせを実現させるか
→ 理想は環境構築・worktreeの切り離しも、Cluade Codeに任せたい
まずは、自分でできるようにならんと、指示の出し方がわからん。
devcontainer cliの使い方
cliで起動して、claude codeに自走させたい
→ できればサブエージェントとかに起動と管理させたい
devcontainer cliをとりあえずみる。
install
npm install -g @devcontainers/cli
devcontainer cliでできること
- [x] devcontainer build - Enables building/pre-building images
- [x] devcontainer up - Spins up containers with devcontainer.json settings applied
- [x] devcontainer run-user-commands - Runs lifecycle commands like postCreateCommand
- [x] devcontainer read-configuration - Outputs current configuration for workspace
- [x] devcontainer exec - Executes a command in a container with userEnvProbe,remoteUser, remoteEnv, and other properties applied
- [x] devcontainer features <...> - Tools to assist in authoring and testing DevContainer Features
- [x] devcontainer templates <...> - Tools to assist in authoring and testing DevContainer Templates
- [ ] devcontainer stop - Stops containers
- [ ] devcontainer down - Stops and deletes containers
コンテナ環境の構築はできるけど、削除や停止ができない模様。
→ これは別で補う = コンテナエンジン自体の操作で賄うかたちかな
miseか何かでまとめて置けばよさそう
実際に環境を作成してみる
devcontainerの動作確認
Rustを例にとって、Claude Code x devcotainerの環境を作成してみる
{
"name": "Claude Code Sandbox",
"build": {
"dockerfile": "Dockerfile",
"args": {
"TZ": "${localEnv:TZ:America/Los_Angeles}",
"CLAUDE_CODE_VERSION": "latest",
"GIT_DELTA_VERSION": "0.18.2",
"ZSH_IN_DOCKER_VERSION": "1.2.0"
}
},
"runArgs": [
"--cap-add=NET_ADMIN",
"--cap-add=NET_RAW"
],
"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"
}
}
}
}
},
"remoteUser": "node",
"mounts": [
"source=claude-code-bashhistory-${devcontainerId},target=/commandhistory,type=volume",
"source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
],
"containerEnv": {
"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",
"features": {
"ghcr.io/devcontainers/features/rust:1": {}
}
}
適当にmain.rsを作って、実行できることを確認する
devcontainer build
でイメージの作成をしてみる
→ が上手くいかない?
Missing required argument: workspace-folder
なるほど、 workspace-folder
を設定する必要があるのね
devcontainer build --workspace-folder .
[10 ms] @devcontainers/cli 0.80.0. Node.js v22.15.0. linux 6.6.87.2-microsoft-standard-WSL2 x64.
[5747 ms] Resolving Feature dependencies for 'ghcr.io/devcontainers/features/rust:1'...
[7534 ms] Soft-dependency 'ghcr.io/devcontainers/features/common-utils' is not required. Removing from installation order...
[8171 ms] Files to omit: ''
[8217 ms] Start: Run: docker build -t dev_container_feature_content_temp -f /tmp/devcontainercli-metal/container-features/0.80.0-1754138906386/Dockerfile.buildContent /tmp/devcontainercli-metal/container-features/0.80.0-1754138906386
The command 'docker' could not be found in this WSL 2 distro.
We recommend to activate the WSL integration in Docker Desktop settings.
For details about using Docker Desktop with WSL 2, visit:
https://docs.docker.com/go/wsl2/
{"outcome":"error","message":"Command failed: docker build -t dev_container_feature_content_temp -f /tmp/devcontainercli-metal/container-features/0.80.0-1754138906386/Dockerfile.buildContent /tmp/devcontainercli-metal/container-features/0.80.0-1754138906386","description":"An error occurred building the container."}
dockerをインストールしないといけないのか
できれば、日常使いのpodmanでいけないか?
→ はじめて使うので、とりあえず公式のいうことを聞こう。 = dockerを入れる
dockerを入れたらうごいた
devcontainer build --workspace-folder .
[12 ms] @devcontainers/cli 0.80.0. Node.js v24.5.0. linux 6.6.87.2-microsoft-standard-WSL2 x64.
[5349 ms] Resolving Feature dependencies for 'ghcr.io/devcontainers/features/rust:1'...
[7157 ms] Soft-dependency 'ghcr.io/devcontainers/features/common-utils' is not required. Removing from installation order...
[7925 ms] Files to omit: ''
[7970 ms] Start: Run: docker buildx build --load --build-arg BUILDKIT_INLINE_CACHE=1 -f /tmp/devcontainercli-metal/container-features/0.80.0-1754139843778/Dockerfile-with-features -t vsc-devcontainer-test-3bc22499c683a513faf2e94bbc65dd6310fe59128648e336452847f0519aa3eb --target dev_containers_target_stage --build-arg TZ=America/Los_Angeles --build-arg CLAUDE_CODE_VERSION=latest --build-arg GIT_DELTA_VERSION=0.18.2 --build-arg ZSH_IN_DOCKER_VERSION=1.2.0 --build-context dev_containers_feature_content_source=/tmp/devcontainercli-metal/container-features/0.80.0-1754139843778 --build-arg _DEV_CONTAINERS_BASE_IMAGE=dev_container_auto_added_stage_label --build-arg _DEV_CONTAINERS_IMAGE_USER=node --build-arg _DEV_CONTAINERS_FEATURE_CONTENT_SOURCE=dev_container_feature_content_temp /home/metal/repos/devcontainer-test/.devcontainer
[+] Building 94.7s (28/28) FINISHED docker:default
=> [internal] load build definition from Dockerfile-with-features 0.1s
=> => transferring dockerfile: 5.85kB 0.0s
=> resolve image config for docker-image://docker.io/docker/dockerfile:1.4 3.1s
=> [auth] docker/dockerfile:pull token for registry-1.docker.io 0.0s
=> docker-image://docker.io/docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc 1.0s
=> => resolve docker.io/docker/dockerfile:1.4@sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc 0.0s
=> => sha256:9ba7531bd80fb0a858632727cf7a112fbfd19b17e94c4e84ced81e24ef1a0dbc 2.00kB / 2.00kB 0.0s
=> => sha256:ad87fb03593d1b71f9a1cfc1406c4aafcb253b1dabebf569768d6e6166836f34 528B / 528B 0.0s
=> => sha256:1e8a16826fd1c80a63fa6817a9c7284c94e40cded14a9b0d0d3722356efa47bd 2.37kB / 2.37kB 0.0s
=> => sha256:1328b32c40fca9bcf9d70d8eccb72eb873d1124d72dadce04db8badbe7b08546 9.94MB / 9.94MB 0.6s
=> => extracting sha256:1328b32c40fca9bcf9d70d8eccb72eb873d1124d72dadce04db8badbe7b08546 0.3s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [context dev_containers_feature_content_source] load .dockerignore 0.0s
=> => transferring dev_containers_feature_content_source: 2B 0.0s
=> [internal] load metadata for docker.io/library/node:20 2.3s
=> [auth] library/node:pull token for registry-1.docker.io 0.0s
=> [context dev_containers_feature_content_source] load from client 0.1s
=> => transferring dev_containers_feature_content_source: 52.09kB 0.0s
=> [dev_container_auto_added_stage_label 1/11] FROM docker.io/library/node:20@sha256:2c071012e93673d595e1399564c4a48a70a5c0a0acd7db8211d5e6f6 20.9s
=> => resolve docker.io/library/node:20@sha256:2c071012e93673d595e1399564c4a48a70a5c0a0acd7db8211d5e6f6316b431a 0.0s
=> => sha256:2c071012e93673d595e1399564c4a48a70a5c0a0acd7db8211d5e6f6316b431a 6.41kB / 6.41kB 0.0s
=> => sha256:4912e0f4b102ce2beb6cd79657b7dc6128019ac682a55a059dec735a547e4772 2.49kB / 2.49kB 0.0s
=> => sha256:d05db19b2fc951de57277a69bef61300129cfa87385b241b9201db91f01376ea 6.63kB / 6.63kB 0.0s
=> => sha256:c2e76af9483f2d17a3e370639403df2c53a3da1480d533116a8694cd91f15d5a 24.02MB / 24.02MB 4.7s
=> => sha256:ebed137c7c18cb1906fb8314eabc10611ddf49a281f8c1b5eab987a7137f749f 48.49MB / 48.49MB 3.5s
=> => sha256:37f838b71c6b82c581b7543a313255b8c99c23cc9d96c1ad6f9f5f208c942553 64.40MB / 64.40MB 7.0s
=> => sha256:873a4c80287477653c01b20948fc34bb1bacf0f826bcc2ddc3bd2fe25b342d45 211.36MB / 211.36MB 13.1s
=> => extracting sha256:ebed137c7c18cb1906fb8314eabc10611ddf49a281f8c1b5eab987a7137f749f 2.7s
=> => sha256:ce90112df955d4dd33b8ca351a47782e8258d90dff2199b7a56fec929ac71bd5 3.32kB / 3.32kB 5.2s
=> => sha256:787104c2d8bcecc48a284535501e5ef07bc31f45b84f14d8ef35cc9cf6f55657 48.67MB / 48.67MB 12.0s
=> => extracting sha256:c2e76af9483f2d17a3e370639403df2c53a3da1480d533116a8694cd91f15d5a 0.7s
=> => sha256:f809fc3353aacdd42e3ec24f041dec70415784866c60e1dbb485eb218394a380 1.25MB / 1.25MB 7.6s
=> => extracting sha256:37f838b71c6b82c581b7543a313255b8c99c23cc9d96c1ad6f9f5f208c942553 3.1s
=> => sha256:b02ac2c75cc331e8c16951f908100ff0b021adfffd4877ce0e8588eff6307f64 448B / 448B 8.1s
=> => extracting sha256:873a4c80287477653c01b20948fc34bb1bacf0f826bcc2ddc3bd2fe25b342d45 5.2s
=> => extracting sha256:ce90112df955d4dd33b8ca351a47782e8258d90dff2199b7a56fec929ac71bd5 0.0s
=> => extracting sha256:787104c2d8bcecc48a284535501e5ef07bc31f45b84f14d8ef35cc9cf6f55657 1.8s
=> => extracting sha256:f809fc3353aacdd42e3ec24f041dec70415784866c60e1dbb485eb218394a380 0.0s
=> => extracting sha256:b02ac2c75cc331e8c16951f908100ff0b021adfffd4877ce0e8588eff6307f64 0.0s
=> [internal] load build context 0.3s
=> => transferring context: 4.34kB 0.0s
=> [dev_container_auto_added_stage_label 2/11] RUN apt-get update && apt-get install -y --no-install-recommends less git procps sudo 10.9s
=> [dev_container_auto_added_stage_label 3/11] RUN mkdir -p /usr/local/share/npm-global && chown -R node:node /usr/local/share 0.3s
=> [dev_container_auto_added_stage_label 4/11] RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_histor 0.2s
=> [dev_container_auto_added_stage_label 5/11] RUN mkdir -p /workspace /home/node/.claude && chown -R node:node /workspace /home/node/.claud 0.3s
=> [dev_container_auto_added_stage_label 6/11] WORKDIR /workspace 0.0s
=> [dev_container_auto_added_stage_label 7/11] RUN ARCH=$(dpkg --print-architecture) && wget "https://github.com/dandavison/delta/releases/d 0.9s
=> [dev_container_auto_added_stage_label 8/11] RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/v1.2.0/zsh-in-d 2.8s
=> [dev_container_auto_added_stage_label 9/11] RUN npm install -g @anthropic-ai/claude-code@latest 5.0s
=> [dev_container_auto_added_stage_label 10/11] COPY init-firewall.sh /usr/local/bin/ 0.1s
=> [dev_container_auto_added_stage_label 11/11] RUN chmod +x /usr/local/bin/init-firewall.sh && echo "node ALL=(root) NOPASSWD: /usr/local/bi 0.2s
=> [dev_containers_target_stage 1/4] RUN mkdir -p /tmp/dev-container-features 0.3s
=> [dev_containers_feature_content_normalize 1/2] COPY --from=dev_containers_feature_content_source devcontainer-features.builtin.env /tmp/buil 0.0s
=> [dev_containers_feature_content_normalize 2/2] RUN chmod -R 0755 /tmp/build-features/ 0.4s
=> [dev_containers_target_stage 2/4] COPY --from=dev_containers_feature_content_normalize /tmp/build-features/ /tmp/dev-container-features 0.1s
=> [dev_containers_target_stage 3/4] RUN echo "_CONTAINER_USER_HOME=$( (command -v getent >/dev/null 2>&1 && getent passwd 'node' || grep -E '^ 0.3s
=> [dev_containers_target_stage 4/4] RUN --mount=type=bind,from=dev_containers_feature_content_source,source=rust_0,target=/tmp/build-features 42.9s
=> exporting to image 2.2s
=> => exporting layers 2.2s
=> => preparing layers for inline cache 0.0s
=> => writing image sha256:ac2349b384ee3faea5f1e0bb69794c4d3f8e39ca897db519c03302b9bff5caf4 0.0s
=> => naming to docker.io/library/vsc-devcontainer-test-3bc22499c683a513faf2e94bbc65dd6310fe59128648e336452847f0519aa3eb
rustがインストールされていて、 hello world
が実行できることを確認!!
ひとまず、devcontainerが動作していることは確か。
Claude Codeと共同作業してみる
さっきまでの作業では、手作業で「起動→コマンド実行→停止」を実行してきた。
→ これをClaude Codeに任せながら、コンテナ内でClaude Codeに作業させたい
まずは、Claude Codeに作業を委譲させるために、miseでコンテナ周りの操作をまとめる
テストリポジトリ
devcontainerにはdonw操作がないので、mise側で操作をまとめる
→ --id-label
というdevcontainerのオプションを利用することで、コンテナにメタ情報を付けることがいけそう
最終的に、下のような環境に
- mise run container-up : コンテナの作成・起動
- mise run container-exec : コンテナ内でコマンドを実行
- mise run container-down : コンテナの削除
[tasks.container-up]
description="devcontainer-cliを使って、コンテナ環境を作成する"
run = "devcontainer up --workspace-folder . --id-label id_label={{env.ID_LABEL}}"
[tasks.container-exec]
description="コンテナ内でコマンドを実行"
run="devcontainer exec --workspace-folder"
[env]
ID_LABEL = "test"
mise run container-exec claude -p hi
を実行してみた
→ が、なぜかログインが求めるられるように
= コンテナ内でログインをしたところ、うまく動いた(一度downをしても、再度ログインしなくても良かった)
ids=$(docker ps -q --filter "label=id_label=$ID_LABEL")
if [ -z "$ids" ]; then
echo "No containers found with label '$ID_LABEL'"
exit 1
fi
docker rm -f $ids
次はworktreeの切り出しも、miseでまとめる
- checkout-worktree : git worktreeを切り出す
- remove-worktree : git worktreeのクリーンアップ
[tasks.checkout-worktree]
description="git worktreeを切り出す"
run="git worktree add -B {{env.BRANCH_NAME}} ./.git/worktree/{{env.BRANCH_NAME}}"
[tasks.remove-worktree]
description="git worktreeのクリーンアップ"
run="git worktree remove {{env.BRANCH_NAME}}"
[env]
BRANCH_NAME = "test"
これで、claude --dangerously-skip-permissions
が実行できると思われる。
↓ 実行してみる
mise container-up
mise container-exec claude --dangerously-skip-permissions -p スタックマシン形式の電卓を作成 think hard
実際に開発させたブランチ
いけてそう