{
  "title": "Cache dependencies unless package metadata changes",
  "content": "COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./\nCOPY ui/package.json ./ui/package.json\nCOPY scripts ./scripts\n\nRUN pnpm install --frozen-lockfile\n\nCOPY . .\nRUN pnpm build\nRUN pnpm ui:install\nRUN pnpm ui:build\n\nENV NODE_ENV=production\n\nCMD [\"node\",\"dist/index.js\"]\nbash theme={null}\ndocker compose run --rm openclaw-cli channels login\nbash theme={null}\ndocker compose run --rm openclaw-cli channels add --channel telegram --token \"<token>\"\nbash theme={null}\ndocker compose run --rm openclaw-cli channels add --channel discord --token \"<token>\"\nbash theme={null}\ndocker compose exec openclaw-gateway node dist/index.js health --token \"$OPENCLAW_GATEWAY_TOKEN\"\nbash theme={null}\nscripts/e2e/onboard-docker.sh\nbash theme={null}\npnpm test:docker:qr\njson5 theme={null}\n{\n  agents: {\n    defaults: {\n      sandbox: {\n        mode: \"non-main\", // off | non-main | all\n        scope: \"agent\", // session | agent | shared (agent is default)\n        workspaceAccess: \"none\", // none | ro | rw\n        workspaceRoot: \"~/.openclaw/sandboxes\",\n        docker: {\n          image: \"openclaw-sandbox:bookworm-slim\",\n          workdir: \"/workspace\",\n          readOnlyRoot: true,\n          tmpfs: [\"/tmp\", \"/var/tmp\", \"/run\"],\n          network: \"none\",\n          user: \"1000:1000\",\n          capDrop: [\"ALL\"],\n          env: { LANG: \"C.UTF-8\" },\n          setupCommand: \"apt-get update && apt-get install -y git curl jq\",\n          pidsLimit: 256,\n          memory: \"1g\",\n          memorySwap: \"2g\",\n          cpus: 1,\n          ulimits: {\n            nofile: { soft: 1024, hard: 2048 },\n            nproc: 256,\n          },\n          seccompProfile: \"/path/to/seccomp.json\",\n          apparmorProfile: \"openclaw-sandbox\",\n          dns: [\"1.1.1.1\", \"8.8.8.8\"],\n          extraHosts: [\"internal.service:10.0.0.5\"],\n        },\n        prune: {\n          idleHours: 24, // 0 disables idle pruning\n          maxAgeDays: 7, // 0 disables max-age pruning\n        },\n      },\n    },\n  },\n  tools: {\n    sandbox: {\n      tools: {\n        allow: [\n          \"exec\",\n          \"process\",\n          \"read\",\n          \"write\",\n          \"edit\",\n          \"sessions_list\",\n          \"sessions_history\",\n          \"sessions_send\",\n          \"sessions_spawn\",\n          \"session_status\",\n        ],\n        deny: [\"browser\", \"canvas\", \"nodes\", \"cron\", \"discord\", \"gateway\"],\n      },\n    },\n  },\n}\nbash theme={null}\nscripts/sandbox-setup.sh\nbash theme={null}\nscripts/sandbox-common-setup.sh\njson5 theme={null}\n{\n  agents: {\n    defaults: {\n      sandbox: { docker: { image: \"openclaw-sandbox-common:bookworm-slim\" } },\n    },\n  },\n}\nbash theme={null}\nscripts/sandbox-browser-setup.sh\njson5 theme={null}\n{\n  agents: {\n    defaults: {\n      sandbox: {\n        browser: { enabled: true },\n      },\n    },\n  },\n}\njson5 theme={null}\n{\n  agents: {\n    defaults: {\n      sandbox: { browser: { image: \"my-openclaw-browser\" } },\n    },\n  },\n}\nbash theme={null}\ndocker build -t my-openclaw-sbx -f Dockerfile.sandbox .\njson5 theme={null}\n{\n  agents: {\n    defaults: {\n      sandbox: { docker: { image: \"my-openclaw-sbx\" } },\n    },\n  },\n}\n```\n\n### Tool policy (allow/deny)\n\n* `deny` wins over `allow`.\n* If `allow` is empty: all tools (except deny) are available.\n* If `allow` is non-empty: only tools in `allow` are available (minus deny).\n\n* `prune.idleHours`: remove containers not used in X hours (0 = disable)\n* `prune.maxAgeDays`: remove containers older than X days (0 = disable)\n\n* Keep busy sessions but cap lifetime:\n  `idleHours: 24`, `maxAgeDays: 7`\n* Never prune:\n  `idleHours: 0`, `maxAgeDays: 0`\n\n* Hard wall only applies to **tools** (exec/read/write/edit/apply\\_patch).\n* Host-only tools like browser/camera/canvas are blocked by default.\n* Allowing `browser` in sandbox **breaks isolation** (browser runs on host).\n\n* Image missing: build with [`scripts/sandbox-setup.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/sandbox-setup.sh) or set `agents.defaults.sandbox.docker.image`.\n* Container not running: it will auto-create per session on demand.\n* Permission errors in sandbox: set `docker.user` to a UID:GID that matches your\n  mounted workspace ownership (or chown the workspace folder).\n* Custom tools not found: OpenClaw runs commands with `sh -lc` (login shell), which\n  sources `/etc/profile` and may reset PATH. Set `docker.env.PATH` to prepend your\n  custom tool paths (e.g., `/custom/bin:/usr/local/share/npm-global/bin`), or add\n  a script under `/etc/profile.d/` in your Dockerfile.",
  "code_samples": [
    {
      "code": "### Channel setup (optional)\n\nUse the CLI container to configure channels, then restart the gateway if needed.\n\nWhatsApp (QR):",
      "language": "unknown"
    },
    {
      "code": "Telegram (bot token):",
      "language": "unknown"
    },
    {
      "code": "Discord (bot token):",
      "language": "unknown"
    },
    {
      "code": "Docs: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](/channels/discord)\n\n### OpenAI Codex OAuth (headless Docker)\n\nIf you pick OpenAI Codex OAuth in the wizard, it opens a browser URL and tries\nto capture a callback on `http://127.0.0.1:1455/auth/callback`. In Docker or\nheadless setups that callback can show a browser error. Copy the full redirect\nURL you land on and paste it back into the wizard to finish auth.\n\n### Health check",
      "language": "unknown"
    },
    {
      "code": "### E2E smoke test (Docker)",
      "language": "unknown"
    },
    {
      "code": "### QR import smoke test (Docker)",
      "language": "unknown"
    },
    {
      "code": "### Notes\n\n* Gateway bind defaults to `lan` for container use.\n* Dockerfile CMD uses `--allow-unconfigured`; mounted config with `gateway.mode` not `local` will still start. Override CMD to enforce the guard.\n* The gateway container is the source of truth for sessions (`~/.openclaw/agents/<agentId>/sessions/`).\n\n## Agent Sandbox (host gateway + Docker tools)\n\nDeep dive: [Sandboxing](/gateway/sandboxing)\n\n### What it does\n\nWhen `agents.defaults.sandbox` is enabled, **non-main sessions** run tools inside a Docker\ncontainer. The gateway stays on your host, but the tool execution is isolated:\n\n* scope: `\"agent\"` by default (one container + workspace per agent)\n* scope: `\"session\"` for per-session isolation\n* per-scope workspace folder mounted at `/workspace`\n* optional agent workspace access (`agents.defaults.sandbox.workspaceAccess`)\n* allow/deny tool policy (deny wins)\n* inbound media is copied into the active sandbox workspace (`media/inbound/*`) so tools can read it (with `workspaceAccess: \"rw\"`, this lands in the agent workspace)\n\nWarning: `scope: \"shared\"` disables cross-session isolation. All sessions share\none container and one workspace.\n\n### Per-agent sandbox profiles (multi-agent)\n\nIf you use multi-agent routing, each agent can override sandbox + tool settings:\n`agents.list[].sandbox` and `agents.list[].tools` (plus `agents.list[].tools.sandbox.tools`). This lets you run\nmixed access levels in one gateway:\n\n* Full access (personal agent)\n* Read-only tools + read-only workspace (family/work agent)\n* No filesystem/shell tools (public agent)\n\nSee [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for examples,\nprecedence, and troubleshooting.\n\n### Default behavior\n\n* Image: `openclaw-sandbox:bookworm-slim`\n* One container per agent\n* Agent workspace access: `workspaceAccess: \"none\"` (default) uses `~/.openclaw/sandboxes`\n  * `\"ro\"` keeps the sandbox workspace at `/workspace` and mounts the agent workspace read-only at `/agent` (disables `write`/`edit`/`apply_patch`)\n  * `\"rw\"` mounts the agent workspace read/write at `/workspace`\n* Auto-prune: idle > 24h OR age > 7d\n* Network: `none` by default (explicitly opt-in if you need egress)\n* Default allow: `exec`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`, `session_status`\n* Default deny: `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`\n\n### Enable sandboxing\n\nIf you plan to install packages in `setupCommand`, note:\n\n* Default `docker.network` is `\"none\"` (no egress).\n* `readOnlyRoot: true` blocks package installs.\n* `user` must be root for `apt-get` (omit `user` or set `user: \"0:0\"`).\n  OpenClaw auto-recreates containers when `setupCommand` (or docker config) changes\n  unless the container was **recently used** (within \\~5 minutes). Hot containers\n  log a warning with the exact `openclaw sandbox recreate ...` command.",
      "language": "unknown"
    },
    {
      "code": "Hardening knobs live under `agents.defaults.sandbox.docker`:\n`network`, `user`, `pidsLimit`, `memory`, `memorySwap`, `cpus`, `ulimits`,\n`seccompProfile`, `apparmorProfile`, `dns`, `extraHosts`.\n\nMulti-agent: override `agents.defaults.sandbox.{docker,browser,prune}.*` per agent via `agents.list[].sandbox.{docker,browser,prune}.*`\n(ignored when `agents.defaults.sandbox.scope` / `agents.list[].sandbox.scope` is `\"shared\"`).\n\n### Build the default sandbox image",
      "language": "unknown"
    },
    {
      "code": "This builds `openclaw-sandbox:bookworm-slim` using `Dockerfile.sandbox`.\n\n### Sandbox common image (optional)\n\nIf you want a sandbox image with common build tooling (Node, Go, Rust, etc.), build the common image:",
      "language": "unknown"
    },
    {
      "code": "This builds `openclaw-sandbox-common:bookworm-slim`. To use it:",
      "language": "unknown"
    },
    {
      "code": "### Sandbox browser image\n\nTo run the browser tool inside the sandbox, build the browser image:",
      "language": "unknown"
    },
    {
      "code": "This builds `openclaw-sandbox-browser:bookworm-slim` using\n`Dockerfile.sandbox-browser`. The container runs Chromium with CDP enabled and\nan optional noVNC observer (headful via Xvfb).\n\nNotes:\n\n* Headful (Xvfb) reduces bot blocking vs headless.\n* Headless can still be used by setting `agents.defaults.sandbox.browser.headless=true`.\n* No full desktop environment (GNOME) is needed; Xvfb provides the display.\n\nUse config:",
      "language": "unknown"
    },
    {
      "code": "Custom browser image:",
      "language": "unknown"
    },
    {
      "code": "When enabled, the agent receives:\n\n* a sandbox browser control URL (for the `browser` tool)\n* a noVNC URL (if enabled and headless=false)\n\nRemember: if you use an allowlist for tools, add `browser` (and remove it from\ndeny) or the tool remains blocked.\nPrune rules (`agents.defaults.sandbox.prune`) apply to browser containers too.\n\n### Custom sandbox image\n\nBuild your own image and point config to it:",
      "language": "unknown"
    },
    {
      "code": "",
      "language": "unknown"
    }
  ],
  "headings": [
    {
      "level": "h3",
      "text": "Channel setup (optional)",
      "id": "channel-setup-(optional)"
    },
    {
      "level": "h3",
      "text": "OpenAI Codex OAuth (headless Docker)",
      "id": "openai-codex-oauth-(headless-docker)"
    },
    {
      "level": "h3",
      "text": "Health check",
      "id": "health-check"
    },
    {
      "level": "h3",
      "text": "E2E smoke test (Docker)",
      "id": "e2e-smoke-test-(docker)"
    },
    {
      "level": "h3",
      "text": "QR import smoke test (Docker)",
      "id": "qr-import-smoke-test-(docker)"
    },
    {
      "level": "h3",
      "text": "Notes",
      "id": "notes"
    },
    {
      "level": "h2",
      "text": "Agent Sandbox (host gateway + Docker tools)",
      "id": "agent-sandbox-(host-gateway-+-docker-tools)"
    },
    {
      "level": "h3",
      "text": "What it does",
      "id": "what-it-does"
    },
    {
      "level": "h3",
      "text": "Per-agent sandbox profiles (multi-agent)",
      "id": "per-agent-sandbox-profiles-(multi-agent)"
    },
    {
      "level": "h3",
      "text": "Default behavior",
      "id": "default-behavior"
    },
    {
      "level": "h3",
      "text": "Enable sandboxing",
      "id": "enable-sandboxing"
    },
    {
      "level": "h3",
      "text": "Build the default sandbox image",
      "id": "build-the-default-sandbox-image"
    },
    {
      "level": "h3",
      "text": "Sandbox common image (optional)",
      "id": "sandbox-common-image-(optional)"
    },
    {
      "level": "h3",
      "text": "Sandbox browser image",
      "id": "sandbox-browser-image"
    },
    {
      "level": "h3",
      "text": "Custom sandbox image",
      "id": "custom-sandbox-image"
    },
    {
      "level": "h3",
      "text": "Tool policy (allow/deny)",
      "id": "tool-policy-(allow/deny)"
    },
    {
      "level": "h3",
      "text": "Pruning strategy",
      "id": "pruning-strategy"
    },
    {
      "level": "h3",
      "text": "Security notes",
      "id": "security-notes"
    },
    {
      "level": "h2",
      "text": "Troubleshooting",
      "id": "troubleshooting"
    }
  ],
  "url": "llms-txt#cache-dependencies-unless-package-metadata-changes",
  "links": []
}