OpenClaw con Docker: instalar y blindar la pasarela

OpenClaw funciona como pasarela permanente y decenas de miles de instancias están abiertas en Internet. Aquí está la instalación Docker Compose blindada, ejecutada y medida el 7 de septiembre de 2026.

OpenClaw con Docker: instalar y blindar la pasarela
Respuesta rápida

OpenClaw es un asistente personal de código abierto que funciona como pasarela permanente, escucha en el puerto 18789 y ejecuta skills. Bajo Docker, usa la imagen oficial ghcr.io/openclaw/openclaw, publica el puerto solo en 127.0.0.1, retira todas las capacidades al contenedor y guarda el token en un archivo .env fuera del compose. Cuidado: la pasarela se niega a arrancar mientras gateway.mode no esté escrito, y publicar en el bucle local no la protege de los demás contenedores de la máquina.

OpenClaw se instala con un comando, y ese es exactamente el problema: el 11 de febrero de 2026, SecurityScorecard contaba más de cuarenta mil pasarelas abiertas en Internet en las primeras veinticuatro horas de su rastreo, de las cuales un 35,4 % se consideraban vulnerables a una ejecución de código remota. Docker no arregla nada por sí solo, lo que importa es lo que tu archivo compose publica, lo que le retira al contenedor, y dónde viven los secretos.

¿Qué es OpenClaw?

OpenClaw es un asistente personal de código abierto, bajo licencia MIT, conocido anteriormente con los nombres Clawdbot y luego Moltbot. No es un agente de código en el sentido de Claude Code o de Codex, sino un gateway, un proceso que se ejecuta de forma permanente, expone una interfaz web (la Control UI) y una API WebSocket en el puerto 18789, conecta canales de mensajería (WhatsApp, Telegram, Discord) y ejecuta «skills» en nombre de su propietario.

El modelo de lenguaje viene de otro sitio: la imagen oficial incorpora complementos para Anthropic, OpenAI, xAI y Ollama, y tú aportas la clave o la dirección del servidor local. La pasarela es por tanto una máquina para ejecutar instrucciones, siendo el modelo solo uno de sus proveedores. Es lo que hace peligrosa su exposición en red, mucho más que el modelo elegido.

¿Por qué Docker en vez de una instalación local?

La documentación dice que Docker es opcional, y tiene razón en el plano funcional. En el plano de la seguridad, la diferencia es clara. Una instalación local le da a la pasarela tu cuenta de usuario: tu carpeta personal, tus claves SSH, tu llavero, tus repositorios. Un contenedor le da un usuario sin privilegios, tres volúmenes y nada más. Es el mismo razonamiento que para los agentes de código, detallado en aislar Claude Code y Codex: la única frontera que aguanta es la que hace respetar el sistema operativo.

El precio a pagar es real. Las dependencias del sistema que exigen ciertos skills (ffmpeg, tmux, un navegador) no están en la imagen, y la documentación es categórica: instalar binarios en un contenedor que ya está en marcha es una trampa, hay que cocinarlos en el momento de la construcción con OPENCLAW_IMAGE_APT_PACKAGES.

¿Qué imagen elegir?

La documentación Docker de OpenClaw recoge dos repositorios. El registro oficial es ghcr.io/openclaw/openclaw, con un espejo en Docker Hub bajo openclaw/openclaw. Un tercero, alpine/openclaw, es un espejo no oficial que la documentación pide explícitamente evitar, porque no sigue ni el calendario de publicación ni la política de retención del proyecto.

Descargué las dos el 7 de septiembre de 2026 para comprobar la diferencia.

bash
docker pull ghcr.io/openclaw/openclaw:latest   # 2 min 30 s
docker pull alpine/openclaw:latest             # 1 min 28 s

docker run --rm ghcr.io/openclaw/openclaw:latest openclaw --version
# OpenClaw 2026.9.2 (3928bad)

docker run --rm alpine/openclaw:latest openclaw --version
# OpenClaw 2026.6.9

docker image inspect ghcr.io/openclaw/openclaw:latest \
  --format '{{index .Config.Labels "org.opencontainers.image.created"}}'
# 2026-09-05T15:21:16.692Z
docker pull alpine/openclaw:latest             # el token vive aquí, no en el compose
        required: true
    environment: *openclaw-env
    command: ["node", "openclaw.mjs", "gateway", "--bind", "lan", "--port", "18789"]
    ports:
      - "127.0.0.1:18789:18789"
    volumes:
      - state:/home/node/.openclaw
      - workspace:/home/node/.openclaw/workspace
      - authsecrets:/home/node/.config/openclaw
    networks: [openclaw]
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "node", "dist/docker-healthcheck.js"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 20s

  cli:
    image: ghcr.io/openclaw/openclaw:2026.9.2
    profiles: ["cli"]
    network_mode: "service:gateway"
    init: true
    env_file:
      - path: .env
        required: true
    environment: *openclaw-env
    entrypoint: ["node", "openclaw.mjs"]
    volumes:
      - state:/home/node/.openclaw
      - workspace:/home/node/.openclaw/workspace
      - authsecrets:/home/node/.config/openclaw
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    depends_on: [gateway]

networks:
  openclaw:
    name: openclaw-lab

volumes:
  state:
  workspace:
  authsecrets:

docker run --rm ghcr.io/openclaw/openclaw:latest openclaw --version
# {"ok":true,"status":"live"}
curl -s http://127.0.0.1:18789/startupz  # {"ok":true,"status":"started"}
curl -s http://127.0.0.1:18789/readyz    # {"ready":true}

docker run --rm alpine/openclaw:latest openclaw --version
# 172.29.0.2

# desde la red por defecto, no la del lab
docker run --rm alpine:3.22 sh -lc \
  "apk add --no-cache curl >/dev/null; \
   curl -s -o /dev/null -w '%{http_code}\n' http://$GWIP:18789/healthz"
# 200

docker image inspect ghcr.io/openclaw/openclaw:latest \
  --format '{{index .Config.Labels "org.opencontainers.image.created"}}'
# 2026-09-05T15:21:16.692Z

El espejo alpine/openclaw estaba congelado en la versión 2026.6.9, construida el 21 de junio de 2026: casi tres meses de retraso, correcciones de seguridad de Debian incluidas. La imagen oficial, en cambio, llevaba la versión 2026.9.2, construida el 5 de septiembre, dos días antes de mi prueba. La elección no es cuestión de gustos.

Prevé el espacio: docker image inspect anuncia 1,10 GB de contenido para la imagen oficial, pero la columna DISK USAGE de docker images bajo Docker 29 contaba 4,43 GB realmente ocupados en el disco.

El archivo compose blindado

La documentación aporta un docker-compose.yml completo, pero supone un repositorio clonado y un script de instalación. Aquí está la versión mínima que escribí y ejecuté, solo con la imagen ya construida.

yaml
name: openclaw-lab

x-openclaw-env: &openclaw-env
  HOME: /home/node
  OPENCLAW_HOME: /home/node
  OPENCLAW_STATE_DIR: /home/node/.openclaw
  OPENCLAW_CONFIG_DIR: /home/node/.openclaw
  OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
  OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
  OPENCLAW_GATEWAY_PORT: "18789"
  OPENCLAW_DISABLE_BONJOUR: "1"
  TZ: Europe/Paris

services:
  gateway:
    image: ghcr.io/openclaw/openclaw:2026.9.2
    container_name: openclaw-lab-gateway
    init: true
    restart: unless-stopped
    env_file:
      - path: .env          # docker-compose.internal.yml
networks:
  openclaw:
    internal: true
        required: true
    environment: *openclaw-env
    command: ["node", "openclaw.mjs", "gateway", "--bind", "lan", "--port", "18789"]
    ports:
      - "127.0.0.1:18789:18789"
    volumes:
      - state:/home/node/.openclaw
      - workspace:/home/node/.openclaw/workspace
      - authsecrets:/home/node/.config/openclaw
    networks: [openclaw]
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "node", "dist/docker-healthcheck.js"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 20s

  cli:
    image: ghcr.io/openclaw/openclaw:2026.9.2
    profiles: ["cli"]
    network_mode: "service:gateway"
    init: true
    env_file:
      - path: .env
        required: true
    environment: *openclaw-env
    entrypoint: ["node", "openclaw.mjs"]
    volumes:
      - state:/home/node/.openclaw
      - workspace:/home/node/.openclaw/workspace
      - authsecrets:/home/node/.config/openclaw
    cap_drop: [ALL]
    security_opt:
      - no-new-privileges:true
    depends_on: [gateway]

networks:
  openclaw:
    name: openclaw-lab

volumes:
  state:
  workspace:
  authsecrets:

Seis decisiones merecen una explicación.

  • El prefijo 127.0.0.1: en ports: sin él, Docker publica en todas las interfaces de la máquina, incluida la de la red local. Es el fallo más común, y explica buena parte de las pasarelas que encuentran los escáneres.
  • --bind lan a pesar de todo: dentro del contenedor, loopback significaría «nadie puede contactarme, ni siquiera Docker». Es la publicación del puerto en 127.0.0.1 del host la que hace el trabajo de restricción, no el modo de enlace interno.
  • cap_drop: [ALL]: el compose oficial solo retira NET_RAW y NET_ADMIN. Retirarlo todo también funciona, comprobación hecha más abajo.
  • El servicio cli detrás de un profiles: comparte la pila de red de la pasarela (network_mode: "service:gateway"), así que está dentro de la frontera de confianza. El perfil evita que un docker compose up distraído lo deje funcionando de forma permanente.
  • Volúmenes con nombre, no bind mounts: la documentación insiste en montar el estado como un directorio, nunca como un archivo aislado, bajo pena de divergencia entre el host y el contenedor tras una escritura de configuración.
  • env_file en vez de environment: el token no aparece ni en el compose, ni en el repositorio Git.

Una precisión sobre el puerto: el 18789 ya estaba ocupado en mi máquina por otra pasarela, así que publiqué el laboratorio en 127.0.0.1:18889. Todas las medidas que siguen se refieren a este puerto, sustitúyelo por el 18789 en tu caso, incluido en gateway.controlUi.allowedOrigins.

El archivo .env se genera localmente y nunca se comitea.

bash
umask 077
printf 'OPENCLAW_GATEWAY_TOKEN=%s\n' "$(openssl rand -hex 32)" > .env
chmod 600 .env
echo '.env' >> .gitignore

Primer arranque: la configuración que falta

Lanzar la pila tal cual no basta. El contenedor arranca, falla, se reinicia, y vuelve a empezar.

bash
docker compose up -d gateway
docker compose logs gateway | tail -3
# no ejecutado aquí
docker compose up -d gateway
# defectos de configuración estáticos
docker compose run --rm -T cli mcp status     # transportes, sin conectarse
docker compose run --rm -T cli mcp probe      # conexión real, lista las capacidades
docker compose run --rm -T cli mcp tools      # filtros include/exclude por servidor
# Missing config. Run `openclaw setup` or set gateway.mode=local (or pass --allow-unconfigured).

Con restart: unless-stopped, Docker llevaba once reinicios antes de que revisara los registros, y el contenedor estaba marcado unhealthy. La pasarela se niega a servir sin configuración explícita: es un buen valor por defecto, pero hay que saberlo. La corrección no exige ninguna clave de proveedor.

bash
docker compose stop gateway

docker compose run --rm -T --no-deps --entrypoint node gateway openclaw.mjs \
  config set --batch-json '[
    {"path":"gateway.mode","value":"local"},
    {"path":"gateway.bind","value":"lan"},
    {"path":"gateway.auth.mode","value":"token"},
    {"path":"gateway.controlUi.allowedOrigins","value":["http://127.0.0.1:18789"]}
  ]'
# Updated 4 config paths. Restart the gateway to apply.

docker compose up -d --force-recreate gateway

El --no-deps --entrypoint node no es decorativo: el servicio cli comparte la pila de red de la pasarela y por tanto solo funciona una vez creado el contenedor gateway. Para escribir la configuración antes del primer arranque, hay que pasar por la propia imagen de la pasarela.

El resultado, cronometrado: /healthz respondió 200 en 7,6 segundos, y Docker marcó el contenedor healthy a los 11,9 segundos. Un reinicio en caliente, con el estado ya inicializado, baja a 5,0 segundos.

bash
curl -s http://127.0.0.1:18789/healthz   # {"ok":true,"status":"live"}
curl -s http://127.0.0.1:18789/startupz  # {"ok":true,"status":"started"}
curl -s http://127.0.0.1:18789/readyz    # {"ready":true}

Los registros de arranque son verbosos, e instructivos.

bash
[gateway] ⚠️  Gateway is binding to a non-loopback address. Ensure authentication
          is configured before exposing to public networks.
[gateway] agent model: openai/gpt-5.6-sol (thinking=medium, fast=off)
[gateway] http server listening (13 plugins: anthropic, browser, canvas,
          cua-computer, device-pair, file-transfer, geolocation, linux-node,
          memory-core, ollama, openai, talk-voice, xai; 2.7s)
[gateway] log file: /tmp/openclaw/openclaw-2026-09-07.log
[gateway] remote model catalog updated; restart the Gateway to apply it

Fíjate en la última línea: sin ninguna clave configurada, la pasarela fue a buscar un catálogo de modelos a Internet. Una pasarela «en reposo» sale igualmente a la red.

El archivo de configuración escrito pesa 348 bytes y no contiene ningún secreto: el token se queda en la variable de entorno.

json
{
  "gateway": {
    "mode": "local",
    "bind": "lan",
    "auth": { "mode": "token" },
    "controlUi": { "allowedOrigins": ["http://127.0.0.1:18789"] }
  },
  "meta": { "lastTouchedVersion": "2026.9.2" }
}

Al lado, state/openclaw.sqlite ya pesaba 1,5 MB. Ahí es donde acaban los tokens OAuth, en claro: la documentación pide tratar este directorio y sus copias de seguridad como credenciales.

Lo que el blindaje bloquea de verdad

Tres comprobaciones valen más que una declaración de intenciones.

Las capacidades y el usuario

bash
docker compose exec -T gateway sh -lc \
  'grep -E "^Cap(Prm|Eff|Bnd)" /proc/1/status; id; grep NoNewPrivs /proc/self/status'
# CapPrm: 0000000000000000
# CapEff: 0000000000000000
# CapBnd: 0000000000000000
# uid=1000(node) gid=1000(node) groups=1000(node)
# NoNewPrivs: 1

Ninguna capacidad, usuario sin privilegios, elevación imposible. La imagen oficial ya hace la mitad del trabajo: se ejecuta como node (uid 1000) y lanza tini como proceso 1.

El puerto publicado en el bucle local no protege de Docker

Es el resultado que más me sorprendió. El puerto solo se publica en 127.0.0.1, un contenedor cualquiera, incluso en otra red Docker, contacta sin embargo con la pasarela por su dirección de bridge.

bash
GWIP=$(docker inspect -f \
  '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' openclaw-lab-gateway)
echo "$GWIP"   # 172.29.0.2

# depuis le réseau par défaut, pas celui du lab
docker run --rm alpine:3.22 sh -lc \
  "apk add --no-cache curl >/dev/null; \
   curl -s -o /dev/null -w '%{http_code}\n' http://$GWIP:18789/healthz"
# 200

El prefijo 127.0.0.1: cierra las interfaces de la máquina, no la red del demonio Docker. Si alojas otros contenedores en la misma máquina, basta con que uno solo esté comprometido para hablar con la pasarela. La solución es la autenticación del gateway y, en un servidor Linux, reglas en la cadena DOCKER-USER: la documentación de blindaje de OpenClaw da un juego completo para UFW, porque las reglas INPUT habituales nunca ven el tráfico publicado por Docker.

La red interna cuesta más de lo que parece

El reflejo siguiente es pasar la red a internal: true. Medí lo que eso retira.

yaml
# docker-compose.internal.yml
networks:
  openclaw:
    internal: true
Comprobación Red bridge Red internal
fetch('https://api.anthropic.com/') desde el contenedor HTTP 404 (accesible) fetch failed
curl http://127.0.0.1:18789/healthz desde el host HTTP 200 conexión rechazada
host.docker.internal resuelto
Servicio del host contactado vía host.docker.internal HTTP 200 fetch failed
Contenedor marcado healthy

Dos consecuencias son contraintuitivas. Primero, Docker Compose ignora en silencio la sección ports en una red interna: docker compose ps muestra 18789/tcp en vez de 127.0.0.1:18789->18789/tcp, sin el menor aviso, y la Control UI queda inaccesible. Además, host.docker.internal se sigue resolviendo pero ya no enruta: un Ollama que corre en la máquina host tampoco es accesible.

La red interna solo es utilizable, por tanto, para una pasarela pilotada enteramente por docker compose exec, con un modelo servido por otro contenedor de la misma red. Para todo lo demás, la buena respuesta es un bridge dedicado más un túnel SSH: la documentación de OpenClaw recomienda además Tailscale Serve en vez de un enlace LAN.

Conectar un modelo sin escribir ninguna clave en el compose

Existen tres caminos, y ninguno pide pegar una clave en un archivo versionado.

  1. Un proveedor remoto: la clave va en .env (ANTHROPIC_API_KEY, OPENAI_API_KEY), leída por env_file. Nunca entra en openclaw.json.
  2. Un modelo local en la máquina host: dentro de un contenedor, 127.0.0.1 designa al contenedor. La documentación impone http://host.docker.internal:11434 para Ollama, y el host debe escuchar más allá de su bucle local (OLLAMA_HOST=0.0.0.0:11434 ollama serve).
  3. El binario de Claude Code en el contenedor: posible, pero hay que persistir /home/node en un volumen con nombre, si no, la siguiente actualización de imagen borra la instalación y la autenticación.

No se introdujo ninguna clave durante esta prueba, y el complemento ollama sí está cargado por la pasarela. El camino del modelo local se verifica sin clave: con un Ollama publicado en el puerto 11434 del host, http://host.docker.internal:11434/v1 responde 200 y en formato OpenAI desde un contenedor de la red bridge. Es exactamente la dirección que impone la documentación, y la razón por la que 127.0.0.1 no funcionaría: dentro de un contenedor, designa al contenedor.

Actualizar la imagen sin romper el estado

Las etiquetas móviles (latest, main, extended-stable) se reconstruyen cada semana a partir de la misma fuente, para recoger las correcciones de seguridad del sistema base entre dos versiones de OpenClaw. Cada reconstrucción también publica una etiqueta fechada e inmutable, del tipo 2026.8.1-r20260820: es la que hay que fijar cuando no quieres que un despliegue siga una etiqueta móvil.

Al cambiar de imagen, la pasarela aplica sus migraciones al arrancar. Si no lo consigue, sale con error en vez de declararse sana, y con una política de reinicio, verás un bucle. El remedio documentado es relanzar la misma imagen una vez con openclaw doctor --fix sobre los mismos volúmenes, y luego reiniciar con normalidad.

bash
docker compose pull
docker run --rm -v openclaw-lab_state:/home/node/.openclaw \
  ghcr.io/openclaw/openclaw:2026.9.2 openclaw doctor --fix
docker compose up -d gateway

Elegir tus skills en ClawHub

La imagen trae 53 skills listos para usar: 51 en el paquete base, 2 extra. Ya es mucho, y basta para la mayoría de los usos. El resto viene de ClawHub, el registro público, ahí es donde empiezan los problemas. El formato es el mismo SKILL.md descrito en la guía Agent Skills: un frontmatter YAML, un cuerpo Markdown, archivos anexos.

La documentación de los skills no anda con rodeos: pide tratar cualquier skill de terceros como código no fiable y leerlo antes de activarlo. Las cifras le dan la razón. Un recuento publicado el 1 de marzo de 2026 atribuye a Antiy CERT 1.184 skills maliciosos confirmados en ClawHub, es decir, aproximadamente uno de cada cinco paquetes en el pico de la campaña.

skills verify interroga al registro sin instalar nada, y eso se agradece. Su veredicto global, en cambio, merece abrirse. Esto es lo que devuelve sobre un skill de Docker popular, en la fecha de la prueba.

bash
docker compose run --rm -T cli skills verify @ivangdavila/docker

# decision            : pass
# security.status     : clean
# security.verdict    : benign      (confidence: high)
# signature.status    : unsigned
# provenance.source   : unavailable
# signals.staticScan  : suspicious  -> suspicious.exposed_secret_literal
# signals.skillSpector: suspicious
# signals.virusTotal  : clean
# artifact.files      : 16 fichiers, dont SKILL.md (24 283 octets)

Dos de las tres señales dicen «sospechoso», el paquete no está firmado, su procedencia en GitHub no está registrada, y el veredicto agregado sigue siendo «benigno, confianza alta». El resumen legible habla de un asistente local sin exfiltración detectada, y no es ese skill el que está en cuestión. Quédate con la brecha entre las señales y la conclusión: un puntaje verde no es una lectura.

La regla que me quedo cabe en tres puntos: leer el SKILL.md antes de instalar (skills info da la ruta exacta del archivo), rechazar cualquier skill que salga a la red sin que su función lo exija, y preferir los 53 skills que trae la imagen mientras basten. El campo security.installPolicy de la configuración permite imponer esta salvaguarda en vez de contar con la disciplina.

Añadir servidores MCP

La pasarela gestiona sus servidores MCP en mcp.servers, con una superficie de comandos completa: add (que sondea el servidor antes de registrarlo), probe, doctor, status, tools para filtrar las herramientas expuestas, y login / logout para los servidores OAuth.

bash
docker compose run --rm -T cli mcp doctor     # défauts de configuration statiques
docker compose run --rm -T cli mcp status     # transports, sans se connecter
docker compose run --rm -T cli mcp probe      # connexion réelle, liste les capacités
docker compose run --rm -T cli mcp tools      # filtres include/exclude par serveur

Dos reflejos merecen conservarse. mcp tools existe, úsalo. Un servidor MCP a menudo expone treinta herramientas cuando solo quieres tres, y cada herramienta adicional es una descripción que el modelo lee como una instrucción. Y un servidor MCP en STDIO corre dentro del contenedor de la pasarela, por tanto con sus volúmenes y sus variables de entorno. La nota de investigación de la Cloud Security Alliance del 4 de mayo de 2026 recomienda exactamente lo contrario: un contenedor dedicado por servidor, sin acceso a las credenciales del host. El tema se trata en detalle en crear un servidor MCP en PHP.

¿OpenClaw, Claude Code o Hermes Agent?

Los tres no juegan en el mismo terreno, y confundirlos lleva a malas decisiones.

OpenClaw Claude Code Hermes Agent
Forma Pasarela permanente Sesión en terminal Agente en contenedor
Disparador Mensajería, cron, Control UI Tú, en el teclado Tareas y colas
Superficie de red Un puerto abierto de forma permanente Ninguna escucha entrante Según el despliegue
Modelo Proveedor a elegir, Ollama incluido Anthropic Según el despliegue

Claude Code no escucha nada: cierra la terminal, la superficie de ataque desaparece. OpenClaw escucha de forma permanente, por diseño, porque es lo que se le pide: responder a un mensaje de Telegram a las tres de la madrugada. Hermes Agent, instalado de la misma forma en un artículo dedicado, ocupa una tercera posición: un agente pensado para correr en contenedor desde el principio. El panorama de los agentes en línea de comandos sitúa a los demás, y OpenCode cubre el caso del agente de código de código abierto.

Eliminarlo todo

Un laboratorio se desmonta. docker compose down solo deja los volúmenes atrás, con la base SQLite y sus tokens.

bash
docker compose down --volumes --remove-orphans
docker rmi ghcr.io/openclaw/openclaw:2026.9.2 alpine/openclaw:latest
rm -f .env

# vérification : rien ne doit ressortir
docker ps -a --format '{{.Names}}' | grep -i claw
docker volume ls --format '{{.Name}}' | grep -i claw
docker images --format '{{.Repository}}' | grep -i claw
docker network ls --format '{{.Name}}' | grep -i claw

Lo que hay que recordar

  • Elige ghcr.io/openclaw/openclaw: el espejo alpine/openclaw tenía tres meses de retraso el día de la prueba (2026.6.9 frente a 2026.9.2).
  • La pasarela se niega a arrancar sin configuración y entra en bucle de reinicio: escribe gateway.mode=local antes del primer up.
  • Publicar en 127.0.0.1 cierra las interfaces de la máquina, no la red Docker: un contenedor vecino contactó con la pasarela en HTTP 200.
  • internal: true suprime en silencio el puerto publicado y el acceso a host.docker.internal: resérvalo para pasarelas pilotadas por exec.
  • El token vive en .env, nunca en el compose. state/openclaw.sqlite contiene tokens OAuth en claro y se trata como un secreto.
  • En ClawHub, un veredicto «clean» puede esconder dos señales «suspicious», un paquete sin firmar y una procedencia desconocida. Lee el SKILL.md.

Errores frecuentes

Publicar el puerto sin prefijo de dirección ports: ["18789:18789"] publica en todas las interfaces, red local incluida. Escribe "127.0.0.1:18789:18789", y en un servidor Linux añade reglas en la cadena DOCKER-USER: las reglas INPUT no ven el tráfico publicado por Docker.
Creer que 127.0.0.1 aísla la pasarela Un contenedor de otra red Docker contactó con 172.29.0.2:18789/healthz en HTTP 200. El prefijo cierra las interfaces de la máquina, no el demonio. Cuenta con la autenticación del gateway, no con la publicación.
Lanzar docker compose up sin configuración El contenedor entra en bucle sobre Missing config. Run openclaw setup or set gateway.mode=local y restart: unless-stopped enmascara el error. Escribe la configuración con config set --batch-json y --no-deps --entrypoint node antes del primer arranque.
Tomar alpine/openclaw por la imagen oficial Es un espejo no oficial, congelado en la 2026.6.9 del 21 de junio de 2026 cuando la oficial estaba en la 2026.9.2. La documentación pide usar ghcr.io/openclaw/openclaw u openclaw/openclaw.
Añadir internal: true como un simple blindaje Compose ignora entonces la sección ports sin aviso, la Control UI queda inaccesible, y host.docker.internal se resuelve pero ya no enruta: un Ollama en el host se vuelve inaccesible.

DockerMCPOllamaOpenClawSécuritéSkills

Damien Flandrin Desarrollador web desde 2010, creador de Gekkode y de Email Impact. Cada artículo se prueba en un proyecto real antes de publicarse. Contacto
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.