April 19, 2026 • Versión: v3.9

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 server

Comportamiento 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/
000

La 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/tcp

Diagnó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 BindDentro del ContenedorDesde Host macOSNotas
loopback o 127.0.0.1✓ Accesible✗ BloqueadoEl servicio solo se enlaza al loopback del contenedor
0.0.0.0✓ Accesible✓ AccesibleEscucha en todas las interfaces incluyendo la interfaz virtual de Docker
Sin especificar (predeterminado)Varía por versiónVaríaA 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:

  1. v2.9: El gateway posiblemente estaba enlazado a 0.0.0.0 por defecto, o la publicación del puerto estaba configurada
  2. 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/healthz

docker-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:18789

Solució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:18789

nginx.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.1

Paso 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: 200

Paso 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 events

Paso 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/healthz

Error 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 memory

Error 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 OPENCLAW

Error 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: Upgrade

Error 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: Establecer OPENCLAW_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 que proxy_read_timeout 86400 esté configurado.
  • 502 Bad Gateway — nginx no puede alcanzar el contenedor del gateway. Verificar la red del contenedor y la configuración de depends_on.
  • devices list devuelve 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 exposeports publica al host, expose solo hace el puerto disponible a servicios enlazados.

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.