mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix: allow docker cli container to connect to gateway (#12504)
* Docker: route CLI through gateway network namespace * Tests: assert Docker Compose CLI namespace wiring * Changelog: add Docker Compose CLI connectivity fix * Docker: pin docker setup gateway mode and bind * Tests: cover docker setup mode and bind sync * Docs: clarify Docker LAN vs loopback gateway targeting * Changelog: expand Docker #12504 targeting note * Docker: default optional CLAUDE compose vars to empty * Docs(Docker): document non-interactive compose runs * Changelog: note docker compose env-noise reduction * Docker: restore onboarding Tailscale guidance * Docker: simplify onboarding output and clarify Tailscale * Docker: harden shared-namespace CLI container * Docs(Docker): document shared-namespace trust boundary * Changelog: note docker shared-namespace hardening --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
committed by
GitHub
parent
710004e011
commit
feefedfb83
@@ -129,6 +129,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Doctor/macOS state-dir safety: warn when OpenClaw state resolves inside iCloud Drive (`~/Library/Mobile Documents/com~apple~CloudDocs/...`) or `~/Library/CloudStorage/...`, because sync-backed paths can cause slower I/O and lock/sync races. (#31004) Thanks @vincentkoc.
|
- Doctor/macOS state-dir safety: warn when OpenClaw state resolves inside iCloud Drive (`~/Library/Mobile Documents/com~apple~CloudDocs/...`) or `~/Library/CloudStorage/...`, because sync-backed paths can cause slower I/O and lock/sync races. (#31004) Thanks @vincentkoc.
|
||||||
- Doctor/Linux state-dir safety: warn when OpenClaw state resolves to an `mmcblk*` mount source (SD or eMMC), because random I/O can be slower and media wear can increase under session and credential writes. (#31033) Thanks @vincentkoc.
|
- Doctor/Linux state-dir safety: warn when OpenClaw state resolves to an `mmcblk*` mount source (SD or eMMC), because random I/O can be slower and media wear can increase under session and credential writes. (#31033) Thanks @vincentkoc.
|
||||||
- CLI/Startup follow-up: add root `--help` fast-path bootstrap bypass with strict root-only matching, lazily resolve CLI channel options only when commands need them, merge build-time startup metadata (`dist/cli-startup-metadata.json`) with runtime catalog discovery so dynamic catalogs are preserved, and add low-power Linux doctor hints for compile-cache placement and respawn tuning. (#30975) Thanks @vincentkoc.
|
- CLI/Startup follow-up: add root `--help` fast-path bootstrap bypass with strict root-only matching, lazily resolve CLI channel options only when commands need them, merge build-time startup metadata (`dist/cli-startup-metadata.json`) with runtime catalog discovery so dynamic catalogs are preserved, and add low-power Linux doctor hints for compile-cache placement and respawn tuning. (#30975) Thanks @vincentkoc.
|
||||||
|
- Docker/Compose gateway targeting: run `openclaw-cli` in the `openclaw-gateway` service network namespace, require gateway startup ordering, pin Docker setup to `gateway.mode=local`, sync `gateway.bind` from `OPENCLAW_GATEWAY_BIND`, default optional `CLAUDE_*` compose vars to empty values to reduce automation warning noise, and harden `openclaw-cli` with `cap_drop` (`NET_RAW`, `NET_ADMIN`) + `no-new-privileges`. Docs now call out the shared trust boundary explicitly. (#12504) Thanks @bvanderdrift and @vincentkoc.
|
||||||
- Telegram/Outbound API proxy env: keep the Node 22 `autoSelectFamily` global-dispatcher workaround while restoring env-proxy support by using `EnvHttpProxyAgent` so `HTTP_PROXY`/`HTTPS_PROXY` continue to apply to outbound requests. (#26207) Thanks @qsysbio-cjw for reporting and @rylena and @vincentkoc for work.
|
- Telegram/Outbound API proxy env: keep the Node 22 `autoSelectFamily` global-dispatcher workaround while restoring env-proxy support by using `EnvHttpProxyAgent` so `HTTP_PROXY`/`HTTPS_PROXY` continue to apply to outbound requests. (#26207) Thanks @qsysbio-cjw for reporting and @rylena and @vincentkoc for work.
|
||||||
- Browser/Security: fail closed on browser-control auth bootstrap errors; if auto-auth setup fails and no explicit token/password exists, browser control server startup now aborts instead of starting unauthenticated. This ships in the next npm release. Thanks @ijxpwastaken.
|
- Browser/Security: fail closed on browser-control auth bootstrap errors; if auto-auth setup fails and no explicit token/password exists, browser control server startup now aborts instead of starting unauthenticated. This ships in the next npm release. Thanks @ijxpwastaken.
|
||||||
- Sandbox/noVNC hardening: increase observer password entropy, shorten observer token lifetime, and replace noVNC token redirect with a bootstrap page that keeps credentials out of `Location` query strings and adds strict no-cache/no-referrer headers.
|
- Sandbox/noVNC hardening: increase observer password entropy, shorten observer token lifetime, and replace noVNC token redirect with a bootstrap page that keeps credentials out of `Location` query strings and adds strict no-cache/no-referrer headers.
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ services:
|
|||||||
HOME: /home/node
|
HOME: /home/node
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
|
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
|
||||||
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-}
|
||||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
|
||||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
|
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
||||||
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
||||||
@@ -29,14 +29,20 @@ services:
|
|||||||
|
|
||||||
openclaw-cli:
|
openclaw-cli:
|
||||||
image: ${OPENCLAW_IMAGE:-openclaw:local}
|
image: ${OPENCLAW_IMAGE:-openclaw:local}
|
||||||
|
network_mode: "service:openclaw-gateway"
|
||||||
|
cap_drop:
|
||||||
|
- NET_RAW
|
||||||
|
- NET_ADMIN
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
environment:
|
environment:
|
||||||
HOME: /home/node
|
HOME: /home/node
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
|
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
|
||||||
BROWSER: echo
|
BROWSER: echo
|
||||||
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-}
|
||||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
|
||||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
|
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
||||||
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
||||||
@@ -44,3 +50,5 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
init: true
|
init: true
|
||||||
entrypoint: ["node", "dist/index.js"]
|
entrypoint: ["node", "dist/index.js"]
|
||||||
|
depends_on:
|
||||||
|
- openclaw-gateway
|
||||||
|
|||||||
@@ -92,6 +92,14 @@ ensure_control_ui_allowed_origins() {
|
|||||||
echo "Set gateway.controlUi.allowedOrigins to $allowed_origin_json for non-loopback bind."
|
echo "Set gateway.controlUi.allowedOrigins to $allowed_origin_json for non-loopback bind."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync_gateway_mode_and_bind() {
|
||||||
|
docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \
|
||||||
|
config set gateway.mode local >/dev/null
|
||||||
|
docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \
|
||||||
|
config set gateway.bind "$OPENCLAW_GATEWAY_BIND" >/dev/null
|
||||||
|
echo "Pinned gateway.mode=local and gateway.bind=$OPENCLAW_GATEWAY_BIND for Docker setup."
|
||||||
|
}
|
||||||
|
|
||||||
contains_disallowed_chars() {
|
contains_disallowed_chars() {
|
||||||
local value="$1"
|
local value="$1"
|
||||||
[[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]]
|
[[ "$value" == *$'\n'* || "$value" == *$'\r'* || "$value" == *$'\t'* ]]
|
||||||
@@ -340,14 +348,18 @@ fi
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Onboarding (interactive)"
|
echo "==> Onboarding (interactive)"
|
||||||
echo "When prompted:"
|
echo "Docker setup pins Gateway mode to local."
|
||||||
echo " - Gateway bind: lan"
|
echo "Gateway runtime bind comes from OPENCLAW_GATEWAY_BIND (default: lan)."
|
||||||
echo " - Gateway auth: token"
|
echo "Current runtime bind: $OPENCLAW_GATEWAY_BIND"
|
||||||
echo " - Gateway token: $OPENCLAW_GATEWAY_TOKEN"
|
echo "Gateway token: $OPENCLAW_GATEWAY_TOKEN"
|
||||||
echo " - Tailscale exposure: Off"
|
echo "Tailscale exposure: Off (use host-level tailnet/Tailscale setup separately)."
|
||||||
echo " - Install Gateway daemon: No"
|
echo "Install Gateway daemon: No (managed by Docker Compose)"
|
||||||
echo ""
|
echo ""
|
||||||
docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli onboard --no-install-daemon
|
docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli onboard --mode local --no-install-daemon
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Docker gateway defaults"
|
||||||
|
sync_gateway_mode_and_bind
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Control UI origin allowlist"
|
echo "==> Control UI origin allowlist"
|
||||||
|
|||||||
@@ -59,6 +59,31 @@ After it finishes:
|
|||||||
- Paste the token into the Control UI (Settings → token).
|
- Paste the token into the Control UI (Settings → token).
|
||||||
- Need the URL again? Run `docker compose run --rm openclaw-cli dashboard --no-open`.
|
- Need the URL again? Run `docker compose run --rm openclaw-cli dashboard --no-open`.
|
||||||
|
|
||||||
|
### Automation/CI (non-interactive, no TTY noise)
|
||||||
|
|
||||||
|
For scripts and CI, disable Compose pseudo-TTY allocation with `-T`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run -T --rm openclaw-cli gateway probe
|
||||||
|
docker compose run -T --rm openclaw-cli devices list --json
|
||||||
|
```
|
||||||
|
|
||||||
|
If your automation exports no Claude session vars, leaving them unset now resolves to
|
||||||
|
empty values by default in `docker-compose.yml` to avoid repeated "variable is not set"
|
||||||
|
warnings.
|
||||||
|
|
||||||
|
### Shared-network security note (CLI + gateway)
|
||||||
|
|
||||||
|
`openclaw-cli` uses `network_mode: "service:openclaw-gateway"` so CLI commands can
|
||||||
|
reliably reach the gateway over `127.0.0.1` in Docker.
|
||||||
|
|
||||||
|
Treat this as a shared trust boundary: loopback binding is not isolation between these two
|
||||||
|
containers. If you need stronger separation, run commands from a separate container/host
|
||||||
|
network path instead of the bundled `openclaw-cli` service.
|
||||||
|
|
||||||
|
To reduce impact if the CLI process is compromised, the compose config drops
|
||||||
|
`NET_RAW`/`NET_ADMIN` and enables `no-new-privileges` on `openclaw-cli`.
|
||||||
|
|
||||||
It writes config/workspace on the host:
|
It writes config/workspace on the host:
|
||||||
|
|
||||||
- `~/.openclaw/`
|
- `~/.openclaw/`
|
||||||
@@ -322,9 +347,30 @@ scripts/e2e/onboard-docker.sh
|
|||||||
pnpm test:docker:qr
|
pnpm test:docker:qr
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### LAN vs loopback (Docker Compose)
|
||||||
|
|
||||||
|
`docker-setup.sh` defaults `OPENCLAW_GATEWAY_BIND=lan` so host access to
|
||||||
|
`http://127.0.0.1:18789` works with Docker port publishing.
|
||||||
|
|
||||||
|
- `lan` (default): host browser + host CLI can reach the published gateway port.
|
||||||
|
- `loopback`: only processes inside the container network namespace can reach
|
||||||
|
the gateway directly; host-published port access may fail.
|
||||||
|
|
||||||
|
The setup script also pins `gateway.mode=local` after onboarding so Docker CLI
|
||||||
|
commands default to local loopback targeting.
|
||||||
|
|
||||||
|
If you see `Gateway target: ws://172.x.x.x:18789` or repeated `pairing required`
|
||||||
|
errors from Docker CLI commands, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm openclaw-cli config set gateway.mode local
|
||||||
|
docker compose run --rm openclaw-cli config set gateway.bind lan
|
||||||
|
docker compose run --rm openclaw-cli devices list --url ws://127.0.0.1:18789
|
||||||
|
```
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
- Gateway bind defaults to `lan` for container use.
|
- Gateway bind defaults to `lan` for container use (`OPENCLAW_GATEWAY_BIND`).
|
||||||
- Dockerfile CMD uses `--allow-unconfigured`; mounted config with `gateway.mode` not `local` will still start. Override CMD to enforce the guard.
|
- Dockerfile CMD uses `--allow-unconfigured`; mounted config with `gateway.mode` not `local` will still start. Override CMD to enforce the guard.
|
||||||
- The gateway container is the source of truth for sessions (`~/.openclaw/agents/<agentId>/sessions/`).
|
- The gateway container is the source of truth for sessions (`~/.openclaw/agents/<agentId>/sessions/`).
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,9 @@ describe("docker-setup.sh", () => {
|
|||||||
expect(extraCompose).toContain("openclaw-home:");
|
expect(extraCompose).toContain("openclaw-home:");
|
||||||
const log = await readFile(activeSandbox.logPath, "utf8");
|
const log = await readFile(activeSandbox.logPath, "utf8");
|
||||||
expect(log).toContain("--build-arg OPENCLAW_DOCKER_APT_PACKAGES=ffmpeg build-essential");
|
expect(log).toContain("--build-arg OPENCLAW_DOCKER_APT_PACKAGES=ffmpeg build-essential");
|
||||||
|
expect(log).toContain("run --rm openclaw-cli onboard --mode local --no-install-daemon");
|
||||||
|
expect(log).toContain("run --rm openclaw-cli config set gateway.mode local");
|
||||||
|
expect(log).toContain("run --rm openclaw-cli config set gateway.bind lan");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("precreates config identity dir for CLI device auth writes", async () => {
|
it("precreates config identity dir for CLI device auth writes", async () => {
|
||||||
@@ -253,4 +256,10 @@ describe("docker-setup.sh", () => {
|
|||||||
expect(compose).not.toContain("gateway-daemon");
|
expect(compose).not.toContain("gateway-daemon");
|
||||||
expect(compose).toContain('"gateway"');
|
expect(compose).toContain('"gateway"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps docker-compose CLI network namespace settings in sync", async () => {
|
||||||
|
const compose = await readFile(join(repoRoot, "docker-compose.yml"), "utf8");
|
||||||
|
expect(compose).toContain('network_mode: "service:openclaw-gateway"');
|
||||||
|
expect(compose).toContain("depends_on:\n - openclaw-gateway");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user