
はじめに
最近、Claude Code on the Webを使って個人プロジェクトの開発をしている。Claude Codeは便利だ。コードを書いてくれるし、テストも回してくれる。ただ、こいつが動いているサンドボックス環境はセキュリティがしっかりしている分、「普通に開発環境立ち上げるだけ」にちょっと工夫がいる。
自分のプロジェクト(kidoku)はNext.js + NestJS + MySQL + MeiliSearchという構成で、ローカルではdocker-compose upとpnpm run devで立ち上がる。これをサンドボックスで動かすまでにやったことを全部書いておく。
リポジトリは以下。
サンドボックス環境の制約
まず前提として、Claude Code on the Webのサンドボックスがどういう環境かを把握しておく。
- カーネルがLinux 4.4.0(overlayfsに制限あり)
- nftablesが使えない
- Google Chromeがプリインストールされていない
- ネットワークは「フルネットワーク」設定でも完全に自由ではない
最後のネットワークの話が地味に重要で、Claude Code on the Webの環境設定で「フルネットワーク」を選択していても、外部通信がブロックされるケースがある。具体的には外部DBホストへの接続や、特定のDockerイメージのdocker pullが通らないことがあった。「フルネットワーク」はあくまで主要なレジストリやAPIへのアクセスを許可するもので、完全に制限なしというわけではないっぽい。
この制約があるので、DBもMeiliSearchもすべてサンドボックス内のローカルコンテナとして立ち上げる構成にしている。外部サービスに依存しない、自己完結型の開発環境を作るのがポイント。
docker-composeが動かない → docker runで個別起動
最初にdocker-compose up -dを試したところ、overlayfsのoperation not permittedで止まった。docker-composeは内部的にoverlayfsを前提としているので、ここは素直に方針転換。
docker runで一つずつコンテナを立ち上げることにした。ポイントは3つ。
1. ストレージドライバをvfsにする
overlayfsの代わりにvfsを使う。パフォーマンスは落ちるが、開発用途なら問題ない。
sudo -E dockerd --iptables=false --bridge=none --storage-driver=vfs &
2. iptablesをlegacyモードにする
nftablesが使えないので、iptablesをlegacyモードに切り替える。これをやらないとDockerデーモンが起動しない。
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
3. --network=hostで起動する
ブリッジネットワークも使えないので、--network=hostで起動する。
docker run -d --name kidoku_db --network=host \ -e MYSQL_ROOT_PASSWORD=root \ -e MYSQL_DATABASE=kidoku \ -e MYSQL_USER=dev \ -e MYSQL_PASSWORD=pass \ mariadb:lts
MySQLが使えない → MariaDBで代替
MySQL公式イメージはレイヤー展開時にコケる。Alpine系じゃないイメージは厳しいっぽい。
mariadb:ltsはAlpineベースで軽量なのでpullできた。MySQLとMariaDBはほぼ互換なので、Prismaからの接続もmysql://のままでいける。
DATABASE_URL="mysql://dev:pass@localhost:3306/kidoku"
docker-compose時代はdbというホスト名だったが、--network=hostなのでlocalhostに変わる。
MeiliSearchの日本語対応版
うちのプロジェクトでは日本語検索のためにMeiliSearchのプロトタイプビルド(getmeili/meilisearch:prototype-japanese-6)を使っている。これは特殊なイメージだが、問題なくpullできた。
docker run -d --name kidoku_meilisearch --network=host \ -e MEILI_MASTER_KEY=masterKey \ -e MEILI_NO_ANALYTICS=true \ getmeili/meilisearch:prototype-japanese-6
Playwright MCPが動かない → v1.50.0を直接実行
Claude CodeにはPlaywright MCPとChrome DevTools MCPというブラウザ操作ツールが用意されている。ただ、サンドボックスではどちらも使えない。
- Playwright MCP: 内部のPlaywrightバージョン(alpha版)とインストール済みChromiumのビルド番号が合わない
- Chrome DevTools MCP: Google Chromeがインストールされていない
そこで、Playwright v1.50.0をバージョン固定でインストールし、スクリプトとして直接実行する方針にした。
npx playwright@1.50.0 install chromium
起動時にはサンドボックス対応のフラグを指定する。
const browser = await chromium.launch({ args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--single-process', '--no-zygote' ] });
--single-processと--no-zygoteがこの環境では必須。これでフルページスクリーンショットもフォーム操作もいける。
Prismaのチェックサムエラー → 検証スキップ
Prismaクライアント生成時にエンジンのチェックサム検証で失敗することがある。環境変数一つで回避できる。
PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1 pnpm --filter web prisma generate PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1 pnpm --filter api prisma generate
Google OAuthが使えない → 裏口ログイン
裏口ログイン機能を実装した。
本番環境ではGoogle OAuthを使っているが、サンドボックスでは外部ネットワーク制限でOAuthコールバックが通らない。
NextAuthのCredentialsProviderで裏口ログインを用意してある。ENABLE_BACKDOOR_LOGIN=trueで有効化。
# CSRFトークン取得
curl -s -c cookies.txt http://localhost:3000/api/auth/csrf
# ログイン
curl -X POST -b cookies.txt -c cookies.txt \
http://localhost:3000/api/auth/callback/backdoor \
-d "email=test@example.com&csrfToken=${TOKEN}"
全部自動化した
ここまでの手順を毎回やるのはしんどいので、SessionStartフックで全自動化した。
{ "hooks": { "SessionStart": [{ "type": "command", "command": "bash scripts/sandbox-setup.sh", "timeout": 300000 }] } }
sandbox-setup.shの処理フロー:
- サンドボックス環境かどうか判定(
CLAUDE_CODE_REMOTE=true) pnpm install- iptablesをlegacyモードに切り替え
- Dockerデーモン起動(
--storage-driver=vfs) - MariaDBコンテナ起動
- MeiliSearchコンテナ起動
.envファイル生成prisma db pushでスキーマ反映prisma generateでクライアント生成- シードデータ投入
- API・Webサーバー起動
- ヘルスチェック
これでClaude Codeのセッションが開始した時点でもう開発できる状態になっている。
ヘルスチェックも作った
全コンポーネントが正常に動いているか確認するスクリプトも用意した。
bash scripts/health-check.sh
チェック項目:
- Dockerデーモンが動いているか
- MariaDBが接続を受け付けるか
- テーブルが存在するか
- MeiliSearchが応答するか
- APIがGraphQLクエリに応答するか(HMAC署名付き認証も検証)
- Webサーバーが200を返すか
結果はPass/Fail/Warnで出る。何かコケてたら一発で分かる。
コマンド許可リスト
Claude Codeはセキュリティのためにコマンド実行時に許可を求めてくる。開発中に毎回聞かれるのはテンポが悪いので、.claude/settings.jsonに許可リストを書いておく。
{ "permissions": { "allow": [ "Bash(docker *)", "Bash(pnpm *)", "Bash(node *)", "Bash(npx playwright*)" ] } }
50個くらい登録してあって、これでClaude Codeが自律的に動ける。
まとめ
サンドボックス環境で開発環境を動かすためにやったことの一覧。
| 問題 | 代替策 |
|---|---|
| フルネットワークでも外部通信が通らない | DB・検索エンジンをローカルコンテナで自己完結 |
| docker-composeが動かない | docker run --network=hostで個別起動 |
| overlayfsが使えない | --storage-driver=vfsでDockerデーモン起動 |
| nftablesが使えない | iptables-legacyモードに切り替え |
| MySQLイメージがpullできない | MariaDBで代替 |
| Playwright MCPが動かない | Playwright v1.50.0を直接スクリプト実行 |
| Chrome DevTools MCPが動かない | 同上 |
| Prismaチェックサムエラー | PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1 |
| Google OAuthが使えない | 裏口ログインで代替 |
「フルネットワーク」設定でも外部通信が完全に自由ではないという前提に立って、最初からローカル完結型で組んでおくのが結局一番安定する。Claude Code on the Webでガッツリ開発したい人の参考になれば。