Docker ComposeでMCPトンネルをデプロイする
Docker Composeを使用してVMにMCPトンネルスタックをインストールします。
このガイドでは、トンネルスタックを単一ホスト上の強化されたコンテナとしてデプロイします。可用性のために、同じ構成を複数のホストに複製することもできます。
始める前に
以下が必要です。
- トンネル。 プログラムによるアクセスを使用する場合、トンネルIDを指定しなければセットアップコンポーネントがトンネルを作成します。代わりに既存のトンネルに接続するには、Claude Consoleでトンネルを作成し、トンネルID(
tnl_...)を控えておきます。手動プロビジョニングは常にConsoleで作成したトンネルから開始します。 - ホストがTunnels APIに対して認証する手段。
- プログラムによるアクセス(推奨)。 トンネルの作成時に Set up programmatic access をオンにすると、セットアップコンポーネントが「Workload Identity Federation」(ワークロードアイデンティティフェデレーション)を通じて認証できるようになります。セットアップコンポーネントにトンネルを作成させる場合は、代わりにClaude ConsoleのWorkload identityページ(
platform.claude.com/settings/workload-identity-federation)でフェデレーションルールを直接作成してください。フェデレーションルールID(fdrl_...)と組織IDを記録してください。 - 手動。 プログラムによるアクセスをスキップします。Consoleからトンネルトークンを取得し、CAとサーバー証明書を自分で生成して、ConsoleにCAを登録します。
- プログラムによるアクセス(推奨)。 トンネルの作成時に Set up programmatic access をオンにすると、セットアップコンポーネントが「Workload Identity Federation」(ワークロードアイデンティティフェデレーション)を通じて認証できるようになります。セットアップコンポーネントにトンネルを作成させる場合は、代わりにClaude ConsoleのWorkload identityページ(
- DockerとDocker Composeがインストールされたホスト。 手動フローでは
openssl(1.1.1以降)も必要です。 - ホストから
api.anthropic.com(443 TCP)およびトンネルエッジ(7844 TCPおよびUDP)へのアウトバウンドネットワーク接続。完全なネットワーク要件を参照してください。 - 実行中で、
routesの下に設定するアドレスでホストから到達可能な1つ以上のMCPサーバー。まだない場合は、サンプルサーバーを使用してください。
オプション:サンプルMCPサーバーを使用する
テスト用のMCPサーバーがない場合は、次の最小限のサーバーを使用してください。
mkdir -p mcp-tunnel
cat > mcp-tunnel/hello_server.py <<'EOF'
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("hello-server", host="0.0.0.0", port=9000)
@mcp.tool()
def hello(name: str = "world") -> str:
"""Say hello to someone."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="streamable-http")
EOF以下のインストール手順では mcp-tunnel/ に cd し、対応するサービスとルートを追加する場所を示します。
インストール
このガイドでは、Docker Composeを使用した参考アプローチの1つを示します。組織のセキュリティ要件を満たすように調整する責任はお客様にあります。
この方法では、ホストにOIDCアイデンティティプロバイダー(クラウドVMのメタデータサーバーやSPIFFEなど)が必要です。ない場合は、代わりに プログラムによるアクセスなし タブを使用してください。
セットアップコンポーネントはWorkload Identity Federationを使用して、トンネルトークンの取得、CAとサーバー証明書の生成、およびAnthropicへのCAの登録を行います。
デプロイディレクトリを準備する
mkdir -p mcp-tunnel/{config,data} cd mcp-tunnel sudo chown 65532:65532 dataコンテナは非rootのUID
65532として実行され、data/への書き込みアクセスが必要です。docker-compose.yamlを作成する
このcomposeファイルは、イメージをSHA-256ダイジェストで固定し、すべてのコンテナを読み取り専用ファイルシステムの非rootとして実行し、すべてのLinuxケーパビリティを削除し、権限昇格を無効にします。
cat > docker-compose.yaml <<'EOF' services: setup: image: us-docker.pkg.dev/anthropic-public-registry/images/mcp-proxy@sha256:d243d30642d39857a0579e465cb3b9bf3ae7c1141e83143bb6856777a31467a5 entrypoint: ["/setup"] command: - init - --api-url=https://api.anthropic.com - --output=dir:/data - --token-version=1 environment: - TUNNEL_ID - ANTHROPIC_FEDERATION_RULE_ID - ANTHROPIC_ORGANIZATION_ID - ANTHROPIC_WORKSPACE_ID - ANTHROPIC_IDENTITY_TOKEN volumes: - ./data:/data user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL profiles: ["setup"] cloudflared: image: cloudflare/cloudflared@sha256:0e56c13c7058fcef9c4f0e7d83372f82330548dbf8f8b366f3d8b506526d93b2 command: tunnel --no-autoupdate run --url http://localhost:8080 environment: - TUNNEL_TOKEN # localhost:8080 でプロキシに到達できるよう、プロキシの netns を共有します。 network_mode: "service:mcp-proxy" restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" mcp-proxy: image: us-docker.pkg.dev/anthropic-public-registry/images/mcp-proxy@sha256:d243d30642d39857a0579e465cb3b9bf3ae7c1141e83143bb6856777a31467a5 volumes: - ./config/mcp-proxy.yaml:/etc/mcp-gateway/config.yaml:ro - ./data:/data:ro restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" EOFサンプルMCPサーバーを使用している場合は、サービスとして追加します。
cat >> docker-compose.yaml <<'EOF' hello-mcp: image: python:3.13-slim working_dir: /app volumes: - ./hello_server.py:/app/hello_server.py:ro command: sh -c "pip install --quiet mcp && python hello_server.py" restart: unless-stopped EOFトンネルをプロビジョニングする
識別子を設定します。セットアップコンポーネントにトンネルを作成させるには
TUNNEL_IDを未設定のままにします。Consoleの既存のトンネルに接続するには設定します。# export TUNNEL_ID=tnl_... # 既存のトンネルに接続する場合に設定します export ANTHROPIC_FEDERATION_RULE_ID=fdrl_... export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000フェデレーションルールのスコープが組織のデフォルト以外のワークスペースである場合は、
ANTHROPIC_WORKSPACE_ID=wrkspc_...も設定してください。設定しない場合、セットアップコンポーネントはデフォルトのワークスペースを使用します。自動作成されるトンネルはそのワークスペースに作成されます。ANTHROPIC_IDENTITY_TOKENを、このホストのアイデンティティプロバイダーから取得したOIDC JWTに設定します。プロバイダー向けのWIFガイドに従って、発行者の登録、ルールのサブジェクトの設定、トークンの発行を行ってください。ルールのオーディエンスは、発行時に要求するオーディエンスと一致する必要があります。セットアップコンポーネントを実行します。
docker compose run --rm setupsetup initはdata/に対して冪等です。再実行すると、そこにすでに保存されているトンネルIDとCAを再利用し、2つ目のトンネルを作成することはありません。新しいCAが生成・登録されるのは、data/が空の場合またはTUNNEL_IDが変更された場合のみです。その場合はアクティブな証明書が2つまでという上限が適用されるため、両方の枠が埋まっている場合は先にConsoleで1つを失効させてください。エラーが発生した場合は、セットアップコンポーネントの認証失敗を参照してください。
トンネルドメインを取得し、後の手順のためにエクスポートします。
export TUNNEL_DOMAIN=$(sudo cat data/tunnel-domain) echo "$TUNNEL_DOMAIN"プロキシ設定を作成する
tunnel_domainは必須です。プロキシはこれを使用して、routesでサブドメインを検索する前に受信ホスト名からドメインサフィックスを取り除きます。routesはサブドメインからアップストリームURLへのフラットなマップであり、リストではありません。cat > config/mcp-proxy.yaml <<EOF listen_addr: ":8080" log_level: info shutdown_timeout: 30s tunnel_domain: ${TUNNEL_DOMAIN} tls: cert_file: /data/tls.crt key_file: /data/tls.key routes: echo: http://hello-mcp:9000 EOFecho:ルートはサンプルMCPサーバーを対象としています。独自のルートに置き換える(または追加する)してください。利用可能なすべてのフィールドについては、プロキシ設定リファレンスを参照してください。デプロイを開始する
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token) docker compose up -d
Set up programmatic access をオンにしなかった場合、またはローカルでの開発やテストには、このフローを使用してください。setup サービスはありません。
Consoleからトンネルトークンとドメインを取得する
トンネルの詳細ページで Domain(
abcd1234.tunnel.anthropic.comの形式)をコピーし、Token の横にある目のアイコンをクリックしてトンネルトークンを取得し、コピーアイコンを使用してコピーします。ガイドの残りの部分のために、両方をシェル変数として設定します。
export TUNNEL_DOMAIN=YOUR_TUNNEL_DOMAIN_HERE export TUNNEL_TOKEN='eyJ...'スキャフォールドを作成して証明書を生成する
mkdir -p mcp-tunnel/{data,config} cd mcp-tunnelプロキシはプレーンなWebSocketで
:8080をリッスンします。内部TLSハンドシェイクは、これらの証明書を使用してそのWebSocketストリームの内部で行われます。Anthropicは、Consoleに登録したCAに対して内部ハンドシェイクを検証します。証明書の要件に記載されているとおり、サーバー証明書の「Subject Alternative Name」(サブジェクト代替名)、すなわちSANには*.<tunnel-domain>を含める必要があります。# 自己署名CA。ディストリビューションの openssl.cnf のデフォルトに関係なく証明書の # 要件を満たすよう、拡張を明示的に指定します。 openssl req -x509 -newkey rsa:2048 -nodes \ -keyout data/ca.key -out data/ca.crt \ -days 3650 -subj "/CN=mcp-tunnel-ca" \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,keyCertSign,cRLSign" \ -addext "subjectKeyIdentifier=hash" # サーバー証明書用の拡張ファイル。-extfile を使用することで(OpenSSL 3.0+ 専用の # -copy_extensions の代わりに)、 # OpenSSL 1.1.x でも動作します。 cat > data/tls.ext <<EOF subjectAltName = DNS:${TUNNEL_DOMAIN},DNS:*.${TUNNEL_DOMAIN} authorityKeyIdentifier = keyid,issuer extendedKeyUsage = serverAuth EOF # CA によって署名されたサーバー証明書 openssl req -newkey rsa:2048 -nodes \ -keyout data/tls.key -out /tmp/server.csr \ -subj "/CN=${TUNNEL_DOMAIN}" openssl x509 -req -in /tmp/server.csr \ -CA data/ca.crt -CAkey data/ca.key -CAcreateserial \ -out data/tls.crt -days 90 \ -extfile data/tls.ext # 非 root のプロキシコンテナ(UID 65532)がバインドマウントから鍵を読み取れるようにします。 # world-read ビットがないと、コンテナはホスト所有のファイルを # 開くことができません。 chmod 644 data/tls.keyConsoleにCA証明書を登録する
トンネルの詳細ページで Certificates セクションまでスクロールし、Add certificate をクリックします。Choose file で
data/ca.crtを直接アップロードする(モーダルウィンドウは.pem、.crt、.cerを受け付けます)か、その内容を貼り付けます。cat data/ca.crt証明書が登録されると、トンネル名の横にある Needs certificate バッジが消えます。CA証明書を追加するを参照してください。
プロキシ設定を作成する
tunnel_domainは必須です。プロキシはこれを使用して、routesでサブドメインを検索する前に受信ホスト名からドメインサフィックスを取り除きます。routesはサブドメインからアップストリームURLへのフラットなマップであり、リストではありません。cat > config/mcp-proxy.yaml <<EOF listen_addr: ":8080" log_level: info tunnel_domain: ${TUNNEL_DOMAIN} tls: cert_file: /data/tls.crt key_file: /data/tls.key routes: echo: http://hello-mcp:9000 EOFecho:ルートはサンプルMCPサーバーを対象としています。独自のルートに置き換える(または追加する)してください。利用可能なすべてのフィールドについては、プロキシ設定リファレンスを参照してください。docker-compose.yamlを作成する
network_mode: "service:mcp-proxy"設定により、cloudflaredがプロキシのネットワーク名前空間に配置され、cloudflaredコンテナ内のlocalhost:8080がプロキシに到達するようになります。--url http://localhost:8080フラグはcloudflaredに転送先を指定します。このフラグがないと、cloudflaredは受信リクエストのルートを持たず、503を返します。cat > docker-compose.yaml <<'EOF' services: cloudflared: image: cloudflare/cloudflared@sha256:0e56c13c7058fcef9c4f0e7d83372f82330548dbf8f8b366f3d8b506526d93b2 # --url は必須です。手動フローでは ingress ルールがプッシュされないため、 # これがないと cloudflared はすべてのリクエストに 503 を返します。 command: tunnel --no-autoupdate run --url http://localhost:8080 environment: - TUNNEL_TOKEN # localhost:8080 でプロキシに到達できるよう、プロキシの netns を共有します。 network_mode: "service:mcp-proxy" restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" mcp-proxy: image: us-docker.pkg.dev/anthropic-public-registry/images/mcp-proxy@sha256:d243d30642d39857a0579e465cb3b9bf3ae7c1141e83143bb6856777a31467a5 volumes: - ./config/mcp-proxy.yaml:/etc/mcp-gateway/config.yaml:ro - ./data:/data:ro restart: unless-stopped user: "65532:65532" read_only: true security_opt: - no-new-privileges:true cap_drop: - ALL stop_grace_period: 30s logging: options: max-size: "10m" max-file: "3" EOFサンプルMCPサーバーを使用している場合は、サービスとして追加します。
cat >> docker-compose.yaml <<'EOF' hello-mcp: image: python:3.13-slim working_dir: /app volumes: - ./hello_server.py:/app/hello_server.py:ro command: sh -c "pip install --quiet mcp && python hello_server.py" restart: unless-stopped EOFデプロイを開始する
docker compose up -d
composeファイルはデフォルト値なしでホスト環境からTUNNEL_TOKENを読み取るため、新しいシェルを開くたび、および再起動後にエクスポートを繰り返す必要があります。
複数VMでのデプロイでは、mcp-tunnel/ ディレクトリを各ホストにコピーし、TUNNEL_TOKEN を設定して docker compose up -d を実行します。プログラムによるフローでは TUNNEL_TOKEN は $(sudo cat data/tunnel-token) であり、手動フローではConsoleからコピーした値です。同じトンネルトークンと証明書がすべてのレプリカで機能します。
デプロイを検証する
Anthropic側からアップストリームMCPサーバーを呼び出して、エンドツーエンドで検証します。トンネル経由のMCPサーバーを使用するを参照してください。サンプルMCPサーバーの場合、ルーティングされるURLは https://echo.<your-tunnel-domain>/mcp です。検証に失敗した場合は、トラブルシューティングを参照してください。
アップグレード
このセクションのコマンドは、mcp-tunnel/ デプロイディレクトリ内から実行してください。
トンネルトークンをローテーションする
プログラムによるアクセスを使用する場合は、setup サービスのコマンドで --token-version をインクリメントし、Workload Identity Federationの識別子を設定し、新しいOIDC JWTを発行して、セットアップコンポーネントを再実行します。
# docker-compose.yaml を編集します: setup サービスの
# --token-version 引数の整数を増やします(例: --token-version=1 から
# --token-version=2 へ)。値が変更されていない場合、setup バイナリは
# ローテーションを拒否します。
# export TUNNEL_ID=tnl_... # インストール時に設定した場合のみ設定します
export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...
export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
# export ANTHROPIC_WORKSPACE_ID=wrkspc_... # ルールがワークスペーススコープの場合
# ご利用の環境向けの WIF プロバイダーガイドに従って ANTHROPIC_IDENTITY_TOKEN を
# 再発行します(インストール以降に有効期限が切れているため)。
export ANTHROPIC_IDENTITY_TOKEN=...
docker compose run --rm setup
export TUNNEL_TOKEN=$(sudo cat data/tunnel-token)
docker compose up -d cloudflared--token-version 引数は、新しい値がセットアップコンポーネントの今後の実行でも保持されるように、コマンドラインで渡すのではなく docker-compose.yaml で編集します。セットアップコンポーネントはWorkload Identity Federationで認証するため、失効させるAPIトークンはありません。
プログラムによるアクセスを使用しない場合は、Consoleのトンネル詳細ページで Rotate token をクリックし、各ホストの TUNNEL_TOKEN 環境変数を更新してcloudflaredを再起動します(docker compose up -d cloudflared)。
証明書の更新
有効期限を監視し、期限切れになる前にサーバー証明書を更新する責任はお客様にあります。
プログラムによるアクセスを使用する場合:
docker compose run --rm setup renew-cert --output=dir:/dataCLI引数は setup サービスの command(init 引数)を置き換えますが、entrypoint は保持されるため、これは /setup renew-cert --output=dir:/data を実行します。
プログラムによるアクセスを使用しない場合は、既存のCA(Consoleに登録されたCAは変わりません)で新しいサーバー証明書に署名し、data/tls.crt を置き換えます。新しいシェルから実行する場合は、先に TUNNEL_DOMAIN を設定してください。
export TUNNEL_DOMAIN=YOUR_TUNNEL_DOMAIN_HERE
openssl req -new -key data/tls.key -out /tmp/server.csr \
-subj "/CN=${TUNNEL_DOMAIN}"
openssl x509 -req -in /tmp/server.csr \
-CA data/ca.crt -CAkey data/ca.key -CAcreateserial \
-out data/tls.crt -days 90 \
-extfile data/tls.extどちらのフローでも、プロキシは tls.cert_file をポーリングして自動的に再読み込みするため、再起動は不要です。
次のステップ
アップストリームMCPサーバーをManaged AgentまたはMessages APIに接続します。
強化のガイダンス、認証情報のローテーション、侵害への対応。
接続、TLS、ルーティングの問題を診断します。
Was this page helpful?