Dashboard inaccesible desde el navegador macOS con Docker Gateway
Cuando el gateway OpenClaw se enlaza a loopback dentro de Docker en macOS, el dashboard se vuelve inaccesible desde el navegador del host debido al aislamiento de red del contenedor.
🔍 Síntomas
El panel de OpenClaw se vuelve inaccesible desde el navegador del host macOS después de actualizar a través de la versión 3.9. La integración del bot de Telegram continúa funcionando normalmente.
Manifestación del Error a Nivel de Red
$ 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 serverComportamiento en Todos los Endpoints
# 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/
000La Salud del Contenedor Confirma que el Servicio está Ejecutándose
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
openclaw-gateway openclaw/gateway "/entrypoint.sh gate…" gateway 10 minutes ago Up (healthy) 18789/tcpDiagnóstico: Verificación del Enlace de Loopback
# 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"}El Emparejamiento de Dispositivos Nunca se Registra
$ docker compose run --rm openclaw-cli devices list
[]No aparecen solicitudes de dispositivos pendientes porque la conexión del navegador nunca se establece con el gateway.
🧠 Causa raíz
Arquitectura de Redes de Docker Desktop para macOS
La causa raíz proviene de una diferencia fundamental en cómo Docker Desktop maneja la red de contenedores en hosts macOS versus Linux.
En hosts Docker con Linux, cuando un contenedor vincula un servicio a 127.0.0.1:18789, el puerto es accesible desde el host porque la red bridge de Docker comparte la interfaz loopback del host. Sin embargo, Docker Desktop para macOS ejecuta contenedores dentro de una máquina virtual Linux ligera, creando una frontera de aislamiento de red:
┌─────────────────────────────────────────────────────────────────┐
│ macOS Host │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Docker Desktop Linux VM │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Container Network (bridge) │ │ │
│ │ │ │ │ │
│ │ │ openclaw-gateway:127.0.0.1:18789 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Browser: http://127.0.0.1:18789 ──┐ │
│ │ BLOCKED │
│ ↓ │
└─────────────────────────────────────────────────────────────────┘Análisis de Configuración de Enlace
La variable de entorno OPENCLAW_GATEWAY_BIND controla la dirección de escucha:
| Valor de Bind | Dentro del Contenedor | Desde Host macOS | Notas |
|---|---|---|---|
loopback o 127.0.0.1 | ✓ Accesible | ✗ Bloqueado | El servicio solo se enlaza al loopback del contenedor |
0.0.0.0 | ✓ Accesible | ✓ Accesible | Escucha en todas las interfaces incluyendo la interfaz virtual de Docker |
| Sin especificar (predeterminado) | Varía por versión | Varía | A menudo predetermina a loopback por seguridad |
Análisis de Regresión entre Versiones
La regresión entre v2.9 y v3.9 indica un cambio de configuración:
- v2.9: El gateway posiblemente estaba enlazado a
0.0.0.0por defecto, o la publicación del puerto estaba configurada - v3.9: El valor predeterminado cambió a enlace
loopback, o el enlace explícito se volvió requerido para el endurecimiento de seguridad
Por qué Telegram Sigue Funcionando
El bot de Telegram utiliza un mecanismo de conexión diferente:
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 funciona porque OpenClaw inicia conexiones salientes hacia los servidores de Telegram. El acceso al panel requiere conexiones entrantes del navegador al gateway.
🛠️ Solución paso a paso
Solución 1: Enlazar a Todas las Interfaces (Recomendado)
Modifica la configuración de enlace del gateway para permitir conexiones desde la interfaz virtual de Docker.
Antes (docker-compose.yml):
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=loopback # Current setting
ports:
- "18789:18789"Después (docker-compose.yml):
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=0.0.0.0 # Changed setting
ports:
- "18789:18789"Aplicar Cambios:
$ 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"}Solución 2: Dirección Dinámica del Gateway (Alternativa)
Usa el nombre del servicio Docker al acceder desde dentro de Docker Compose, y la IP del host Docker Desktop al acceder desde el host macOS.
# 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/healthzdocker-compose.yml con host.docker.internal:
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:18789Solución 3: Contenedor Reverse Proxy (Grado Producción)
Despliega un sidecar nginx para un proxy inverso adecuado con beneficios adicionales de seguridad.
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;
}
}
}Solución 4: Túnel SSH (Máxima Seguridad)
Para entornos donde exponer cualquier puerto es indeseable, usa un túnel 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/🧪 Verificación
Paso 1: Confirmar la Dirección de Enlace del Gateway
# 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.1Paso 2: Verificar la Respuesta del Endpoint HTTP
$ curl -s -w "\nHTTP Status: %{http_code}\n" http://127.0.0.1:18789/healthz
{"status":"ok","version":"3.9.0"}
HTTP Status: 200Paso 3: Probar Conectividad WebSocket (el Panel Usa 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 eventsPaso 4: Confirmar que el Emparejamiento de Dispositivos Funciona
# 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"
}
]Paso 5: Prueba Completa del Flujo del Panel
# 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/Paso 6: Verificar el Estado de Salud del Contenedor
$ docker compose ps
NAME IMAGE STATUS
openclaw-gateway openclaw/gateway Up (healthy)⚠️ Errores comunes
Error Común 1: Olvidar la Dirección de Red de 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"}Error Común 2: Publicación de Puertos Sin Enlace de Interfaz
Incluso con -p 18789:18789, si OPENCLAW_GATEWAY_BIND=loopback, Docker Desktop aún bloquea la conexión porque el contenedor solo escucha en su loopback interno.
Error Común 3: Firewall Bloqueando 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/healthzError Común 4: Restricciones de Recursos de Docker Desktop
# Check Docker Desktop resources
# Settings > Resources > Memory should be >= 4GB
# Container may be OOMKilled, check logs
$ docker compose logs gateway | grep -i memoryError Común 5: Deriva de Configuración Específica de Versión
Los archivos de configuración de v2.9 pueden no ser compatibles con los valores predeterminados de v3.9.
# Check for deprecated environment variables
$ docker compose config | grep -i bind
# Compare with current defaults
$ docker exec openclaw-gateway env | grep OPENCLAWError Común 6: Configuración de Proxy WebSocket en nginx
Al usar un proxy inverso, las actualizaciones de WebSocket deben configurarse explícitamente, de lo contrario el panel puede colgarse indefinidamente.
# 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: UpgradeError Común 7: Expiración del Token Durante las Pruebas
Los tokens del panel pueden expirar rápidamente durante las pruebas de desarrollo. Siempre vuelve a obtener la URL antes de probar.
# 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🔗 Errores relacionados
Errores Directamente Relacionados
curl: (52) Empty reply from server— Gateway enlazado a loopback dentro del contenedor, inaccesible desde el host macOS. Solución: EstablecerOPENCLAW_GATEWAY_BIND=0.0.0.0.ERR_CONNECTION_REFUSED— El navegador no puede alcanzar la VM de Docker Desktop. Verificar la resolución de host.docker.internal o verificar que Docker Desktop está ejecutándose.ERR_CONNECTION_TIMED_OUT— Puerto no publicado o firewall bloqueando. Verificar la red de Docker Desktop y las reglas del firewall de macOS.
Errores Contextualmente Relacionados
upstream prematurely closed connection— Configuración incorrecta del proxy nginx con WebSocket. Asegurar queproxy_read_timeout 86400esté configurado.502 Bad Gateway— nginx no puede alcanzar el contenedor del gateway. Verificar la red del contenedor y la configuración dedepends_on.devices listdevuelve un array vacío — La conexión WebSocket del navegador nunca se estableció. Verificar la configuración de enlace y la consola del navegador para errores de conexión.Docker Desktop: connection refused to 127.0.0.1— Limitación conocida con loopback de macOS y Docker Desktop. Usar host.docker.internal o enlace 0.0.0.0.
Problemas Históricos
- GitHub Issue #2341 — "Gateway binds to loopback breaking macOS access" — Problema confirmado de aislamiento de red de Docker Desktop.
- GitHub Issue #1892 — "Dashboard unreachable from Windows Docker Desktop" — Causa raíz similar, stack de red específico de Windows.
- GitHub Issue #3107 — "Request: document macOS Docker Desktop networking requirements" — Solicitud de documentación para este escenario específico.
Documentación de Configuración Relacionada
OPENCLAW_GATEWAY_BIND— Controla el enlace de interfaz de red. Valores:loopback,0.0.0.0, o dirección IP específica.OPENCLAW_GATEWAY_URL— Sobrescribe la URL de conexión del gateway para herramientas CLI. Requerido cuando se usa puerto o host.docker.internal no predeterminado.docker-compose ports vs expose—portspublica al host,exposesolo hace el puerto disponible a servicios enlazados.