Docker Gateway使用時のmacOSブラウザからのDashboardアクセス不可
OpenClaw gatewayがmacOS上のDockerコンテナ内でloopbackにバインドすると、コンテナネットワーキングの分離により、ホストブラウザからDashboardに到達できなくなります。
🔍 症状
OpenClawダッシュボードは、バージョン3.9へのアップグレード後、macOSホストのブラウザからアクセスできなくなります。Telegram bot連携は正常に動作し続けます。
ネットワークレベルでのエラー表現
$ curl -v http://127.0.0.1:18789/
* Connected to 127.0.0.1 port 18789
> GET / HTTP/1.1
> Host: 127.0.0.1:18789
>
* Empty reply from server
* Connection died, errnum=0, curlcode=22
curl: (52) Empty reply from server全エンドポイントでの動作
# All routes return identical empty response
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/
000
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/healthz
000
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/ui/
000コンテナの健康状態はサービス稼働を確認
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
openclaw-gateway openclaw/gateway "/entrypoint.sh gate…" gateway 10 minutes ago Up (healthy) 18789/tcp診断:ループバックバインディングの確認
# Exec into container to verify listening address
$ docker exec -it openclaw-gateway sh -c "netstat -tlnp | grep 18789"
tcp 0 0 127.0.0.1:18789 0.0.0.0:* LISTEN 1/openclaw-gateway
# From inside container, the service responds
$ docker exec -it openclaw-gateway curl -s http://127.0.0.1:18789/healthz
{"status":"ok","version":"3.9.0"}デバイスペアリングが登録されない
$ docker compose run --rm openclaw-cli devices list
[]ブラウザ接続がゲートウェイと確立されないため、保留中のデバイスリクエストは表示されません。
🧠 原因
Docker Desktop for macOSのネットワーキングアーキテクチャ
根本原因是、Docker DesktopがmacOSとLinuxホストでコンテナネットワーキングを処理する方法に根本的な違いがあるためです。
Linux Dockerホストでは、コンテナがサービスバインディングを127.0.0.1:18789にバインドすると、Dockerブリッジネットワークがホストのループバックインターフェースを共有するため、ポートはホストからアクセスできます。しかし、Docker Desktop for macOSは軽量Linux VM内でコンテナを実行するため、ネットワーク分離境界が生じます:
┌─────────────────────────────────────────────────────────────────┐
│ macOS Host │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Docker Desktop Linux VM │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Container Network (bridge) │ │ │
│ │ │ │ │ │
│ │ │ openclaw-gateway:127.0.0.1:18789 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Browser: http://127.0.0.1:18789 ──┐ │
│ │ BLOCKED │
│ ↓ │
└─────────────────────────────────────────────────────────────────┘バインディング設定の分析
OPENCLAW_GATEWAY_BIND環境変数がリスンアドレスを制御します:
| Bind 値 | コンテナ内 | macOSホストから | 備考 |
|---|---|---|---|
loopback または 127.0.0.1 | ✓ アクセス可能 | ✗ ブロック | サービスがコンテナループバックのみにバインド |
0.0.0.0 | ✓ アクセス可能 | ✓ アクセス可能 | 仮想Dockerインターフェースを含む全インターフェースでリスン |
| 未指定(デフォルト) | バージョンにより異なる | バージョンにより異なる | セキュリティのためループバックがデフォルトであることが多い |
バージョンの回帰分析
v2.9からv3.9への回帰は、次のような設定変更を示唆しています:
- v2.9: ゲートウェイはデフォルトで
0.0.0.0にバインドされていたか、ポート公開が設定されていた - v3.9: デフォルトが
loopbackバインディングに変更されたか、セキュリティ強化のために明示的なバインディングが必要になった
Telegramが動作し続けている理由
Telegram botは異なる接続メカニズムを使用します:
Telegram Bot Flow (not affected):
┌──────────────┐ ┌─────────────────────────────────────┐
│ Telegram │ │ Container │
│ Servers │ ────── │ openclaw-gateway ── Webhook/Poll │
│ │ │ (outbound connection, no inbound) │
└──────────────┘ └─────────────────────────────────────┘
Dashboard Flow (broken):
┌──────────────┐ ┌─────────────────────────────────────┐
│ macOS │ │ Container │
│ Chrome │ ─X──── │ openclaw-gateway:127.0.0.1:18789 │
│ Browser │ blocked │ (loopback-only inbound) │
└──────────────┘ └─────────────────────────────────────┘Telegramが動作するのは、OpenClawがTelegramサーバーへのアウトバウンド接続を開始するためです。ダッシュボードへのアクセスには、ブラウザからゲートウェイへのインンバウンド接続が必要です。
🛠️ 解決手順
解決方法1:全インターフェースにバインド(推奨)
Docker仮想インターフェースからの接続を許可するように、ゲートウェイバインディング設定を変更します。
変更前(docker-compose.yml):
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=loopback # Current setting
ports:
- "18789:18789"変更後(docker-compose.yml):
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=0.0.0.0 # Changed setting
ports:
- "18789:18789"変更の適用:
$ docker compose down gateway
$ docker compose up -d gateway
$ sleep 2
$ curl -s http://127.0.0.1:18789/healthz
{"status":"ok","version":"3.9.0"}解決方法2:動的ゲートウェイアドレス(代替案)
Docker Compose内からアクセスする場合はDockerサービス名を使用し、macOSホストからアクセスする場合はホストのDocker Desktop IPを使用します。
# Get the Docker Desktop VM IP on macOS
$ docker run -it --rm --network host alpine ip route | grep default | awk '{print $3}'
192.168.65.0
# Or use the special host.docker.internal mapping
$ curl -s http://host.docker.internal:18789/healthzhost.docker.internalを使用したdocker-compose.yml:
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=127.0.0.1 # Keep loopback for internal
extra_hosts:
- "host.docker.internal:host-gateway"
cli:
depends_on:
- gateway
environment:
- OPENCLAW_GATEWAY_URL=http://host.docker.internal:18789解決方法3:リバースプロキシコンテナ(本番対応)
追加のセキュリティメリットを持つnginxサイドカーをデプロイして、適切なリバースプロキシを実行します。
docker-compose.yml:
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=127.0.0.1 # Internal-only
expose:
- "18789"
nginx:
image: nginx:alpine
ports:
- "18789:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- gateway
cli:
depends_on:
- gateway
environment:
- OPENCLAW_GATEWAY_URL=http://gateway:18789nginx.conf:
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name _;
# Proxy WebSocket connections for dashboard streaming
location / {
proxy_pass http://gateway:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400;
}
}
}解決方法4:SSHトンネル(最高レベルのセキュリティ)
ポートを公開したくない環境では、SSHトンネルを使用します。
# On macOS host, establish tunnel
$ ssh -L 18789:localhost:18789 docker-desktop-host
# Then in another terminal
$ open http://127.0.0.1:18789/🧪 検証
手順1:ゲートウェイバインドアドレスの確認
# Check inside container
$ docker exec openclaw-gateway sh -c "ss -tlnp | grep 18789"
LISTEN 0 128 0.0.0.0:18789 0.0.0.0:* users:(("openclaw-gateway",pid=1,fd=3))
# Should show 0.0.0.0, NOT 127.0.0.1手順2:HTTPエンドポイントレスポンスの確認
$ curl -s -w "\nHTTP Status: %{http_code}\n" http://127.0.0.1:18789/healthz
{"status":"ok","version":"3.9.0"}
HTTP Status: 200手順3:WebSocket接続性のテスト(ダッシュボードはWebSocketを使用)
# Install websocat if needed
$ brew install websocat
# Test WebSocket upgrade
$ websocat ws://127.0.0.1:18789/api/v1/stream
# Should establish connection and wait for events手順4:デバイスペアリングの確認
# In one terminal, watch for devices
$ docker compose run --rm openclaw-cli devices list
# Should show [] initially
# Open browser to http://127.0.0.1:18789/ and trigger pairing
# Re-check devices
$ docker compose run --rm openclaw-cli devices list
[
{
"id": "browser-xxxx",
"type": "dashboard",
"status": "pending",
"created_at": "2025-01-15T10:30:00Z"
}
]手順5:ダッシュボード全体のフローテスト
# Get dashboard URL with token
$ docker compose run --rm openclaw-cli dashboard --no-open
Opening dashboard at: http://127.0.0.1:18789/?token=eyJhbGc...
# Open in browser (should load pairing screen)
$ open http://127.0.0.1:18789/
# Approve pending device
$ docker compose run --rm openclaw-cli devices approve browser-xxxx
# Refresh browser (should now show full dashboard)
$ open http://127.0.0.1:18789/手順6:コンテナ健康状態の確認
$ docker compose ps
NAME IMAGE STATUS
openclaw-gateway openclaw/gateway Up (healthy)⚠️ よくある落とし穴
落とし穴1:Docker Desktopネットワークアドレスの忘れ
# INCORRECT: Assumes 127.0.0.1 works
$ curl http://127.0.0.1:18789/healthz
curl: (52) Empty reply from server
# CORRECT: Use host.docker.internal on macOS
$ curl http://host.docker.internal:18789/healthz
{"status":"ok","version":"3.9.0"}落とし穴2:インターフェースバインディングなしのポート公開
-p 18789:18789があっても、OPENCLAW_GATEWAY_BIND=loopbackの場合、Docker Desktopはコンテナが内部ループバックでのみリッスンしているため、接続をブロックし続けます。
落とし穴3:Docker Desktopのファイアウォールブロック
# macOS firewall may block Docker Desktop incoming connections
# Verify in System Preferences > Security & Privacy > Firewall
# Test with verbose curl to see connection status
$ curl -v http://host.docker.internal:18789/healthz落とし穴4:Docker Desktopのリソース制約
# Check Docker Desktop resources
# Settings > Resources > Memory should be >= 4GB
# Container may be OOMKilled, check logs
$ docker compose logs gateway | grep -i memory落とし穴5:バージョン固有の設定のドリフト
v2.9からの設定ファイルは、v3.9のデフォルトと互換性がない場合があります。
# Check for deprecated environment variables
$ docker compose config | grep -i bind
# Compare with current defaults
$ docker exec openclaw-gateway env | grep OPENCLAW落とし穴6:nginxでのWebSocketプロキシ設定
リバースプロキシを使用する場合、WebSocketアップグレードは明示的に設定する必要があります。そうしないと、ダッシュボードが無限にハングする可能性があります。
# Verify WebSocket headers are forwarded
$ curl -I -N \
-H "Upgrade: websocket" \
-H "Connection: Upgrade" \
http://127.0.0.1:18789/api/v1/stream
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade落とし穴7:テスト中のトークン有効期限
ダッシュボードトークンは開発テスト中に急速に期限切れになる場合があります。テスト前に常にURLを再取得してください。
# Get fresh token
$ docker compose run --rm openclaw-cli dashboard --no-open
Opening dashboard at: http://127.0.0.1:18789/?token=fresh_token_here🔗 関連するエラー
直接的に関連するエラー
curl: (52) Empty reply from server— ゲートウェイがコンテナ内でループバックにバインドされており、macOSホストから到達不能。修正方法:OPENCLAW_GATEWAY_BIND=0.0.0.0を設定します。ERR_CONNECTION_REFUSED— ブラウザがDocker Desktop VMに到達できない。host.docker.internalの名前解決を確認するか、Docker Desktopが実行中であることを確認します。ERR_CONNECTION_TIMED_OUT— ポートが公開されていないか、ファイアウォールがブロックしている。Docker DesktopネットワーキングとmacOSファイアウォールルールを確認します。
文脈的に関連するエラー
upstream prematurely closed connection— WebSocketでのnginxプロキシ設定エラー。proxy_read_timeout 86400が設定されていることを確認します。502 Bad Gateway— nginxがゲートウェイコンテナに到達できない。コンテナネットワーキングとdepends_on設定を確認します。devices listが空の配列を返す — ブラウザのWebSocket接続が確立されない。バインディング設定とブラウザコンソールの接続エラーを確認します。Docker Desktop: connection refused to 127.0.0.1— macOSループバックとDocker Desktopの既知の制限。host.docker.internalまたは0.0.0.0バインディングを使用します。
過去の課題
- GitHub Issue #2341 — 「ゲートウェイがループバックにバインドされmacOSアクセスが壊れる」— Docker Desktopネットワーキング分離の問題が確認されました。
- GitHub Issue #1892 — 「Windows Docker Desktopからダッシュボードに到達不能」— 同様の根本原因、Windows固有のネットワーキングスタック。
- GitHub Issue #3107 — 「リクエスト:macOS Docker Desktopネットワーキング要件のドキュメント化」— この特定のシナリオのドキュメント化リクエスト。
関連する設定ドキュメント
OPENCLAW_GATEWAY_BIND— ネットワークインターフェースバインディングを制御します。値:loopback、0.0.0.0、または特定のIPアドレス。OPENCLAW_GATEWAY_URL— CLIツールのゲートウェイ接続URLをオーバーライドします。非デフォルトポートまたはhost.docker.internal使用時に必要。docker-compose ports vs expose—portsはホストに公開し、exposeはリンクされたサービスからのみポートを利用可能にします。