# Enable the local Docker sandbox

> Toggle Use local Docker VM, start grok-bot-local-vm, wait for http://127.0.0.1:1340/health, and roll back to remote on start failure.

- Repository: sashimikun/grok-bot-0.18-reconstructed
- GitHub: https://github.com/sashimikun/grok-bot-0.18-reconstructed
- Human docs: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15
- Complete Markdown: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15/llms-full.txt

## Source Files

- `source/electron-main/box/local-docker-host-connector.ts`
- `source/shared/box-runtime.ts`
- `source/electron-main/main-edge.ts`
- `source/electron-main/box/box-host-connector.ts`
- `scripts/lib/router-renderer-patch.mjs`
- `source/shared/node/settings/sand-settings-store.ts`

---

---
title: "Enable the local Docker sandbox"
description: "Toggle Use local Docker VM, start grok-bot-local-vm, wait for http://127.0.0.1:1340/health, and roll back to remote on start failure."
---

Settings → Router → **Computer** owns the local sandbox. The **Use local Docker VM** switch calls `window.desktop.agent.setBoxRuntime` with `mode` `"local-docker"` or `"remote"`. Main persists `boxRuntime` in `settings.json`, starts or stops container `grok-bot-local-vm`, waits for `GET http://127.0.0.1:1340/health` with the stored gateway token, then restarts the coordinator. A start or stop failure writes the opposite `boxRuntime` and does not restart the coordinator. Default remains `"remote"`.

<Frame caption="Settings → Router overlay. The Computer card with Use local Docker VM is injected on this page.">
<img src="docs/assets/router-settings.png" alt="Settings Router overlay with provider, account, and usage cards" />
</Frame>

<Info>
Docker Desktop (or another local `docker` daemon) is optional at install time and required only for this toggle. Inference provider choice is independent: Cursor, Claude Code, Codex, and OpenRouter keep their own auth paths.
</Info>

## Prerequisites

- Packaged reconstructed macOS app (the connector refuses a stock image host if `host-main.cjs` and `box-exec-daemon/main.cjs` are missing).
- Docker daemon reachable as the `docker` CLI (`docker info`).
- Loopback ports `1337`, `1339`, `1340`, `6080`, `6081`, and `8790` free on `127.0.0.1`.
- No unowned container already named `grok-bot-local-vm`.

<Warning>
The run uses `--platform linux/amd64`. On Apple Silicon, Docker must be able to pull and run that image. The connector binds published ports to `127.0.0.1` only.
</Warning>

## Enable

<Steps>
<Step title="Open Settings → Router">
Open the app, then **Settings → Router**. The switch lives in the **Computer** card (`aria-label` `Use local Docker VM`, `role="switch"`).

Off copy: `Shell, files and computer use run on Grok Bot's remote computer.`

On copy: `status.detail` from `getBoxRuntime`, or `Shell, files and computer use run in a Docker container on this Mac.`
</Step>
<Step title="Turn on Use local Docker VM">
The renderer sets local UI state to `"local-docker"` and `busy: true`, then:

```ts
window.desktop.agent.setBoxRuntime("local-docker")
```

Preload forwards `{ mode }`. Main rejects any other string with `Unknown box runtime.`
</Step>
<Step title="Wait for persist, container, and health">
`setBoxRuntime` writes `boxRuntime: "local-docker"` first, then `startLocalDockerBox`. That path:

1. Ensures a 32+ character hex token in `local-docker-vm.json` next to `settings.json`.
2. Stages content-addressed `host-main.cjs` and `box-exec-daemon/main.cjs` under `local-docker-runtime/<host-sha256>-<daemon-sha256>/`.
3. Requires `docker info`.
4. Creates or starts `grok-bot-local-vm` with owner label `com.grok-bot.local-vm=1` and schema version `6`.
5. Polls `http://127.0.0.1:1340/health` with `Authorization: Bearer <token>` every 1s, 2s probe timeout, 180s overall.

Ready status: `Local Docker VM is ready.`
</Step>
<Step title="Confirm coordinator reconnect">
On success, main calls `boxRecovery.restartCoordinator` and returns `{ mode, status }`. The switch stays on. The coordinator then connects through the settings-routed host connector to `http://127.0.0.1:1340` instead of the remote broker.
</Step>
</Steps>

<RequestExample>
```json title="setBoxRuntime enable"
{ "mode": "local-docker" }
```
</RequestExample>

<ResponseExample>
```json title="setBoxRuntime success"
{
  "mode": "local-docker",
  "status": {
    "available": true,
    "running": true,
    "ready": true,
    "containerName": "grok-bot-local-vm",
    "image": "public.ecr.aws/k0i0n2g5/cursorenvironments/universal:sand-box-latest",
    "detail": "Local Docker VM is ready."
  }
}
```
</ResponseExample>

## Disable

Turn the same switch off. Main writes `boxRuntime: "remote"`, then `stopLocalDockerBox`:

- Missing or already stopped container: no-op.
- Owned running container: `docker stop grok-bot-local-vm`.
- Running **unowned** container: throws `Refusing to stop unowned container grok-bot-local-vm.`, reverts `boxRuntime` to `"local-docker"`, and does not restart the coordinator.

Stop does **not** `docker rm` the container or delete volumes `grok-bot-local-vm-workspace` and `grok-bot-local-vm-data`. Success still restarts the coordinator so later `connect()` uses the remote host connector.

## Persist and RPC

`SandBoxRuntime` is `"remote" | "local-docker"`. `DEFAULT_SAND_BOX_RUNTIME` is `"remote"`. Invalid `boxRuntime` values in `settings.json` are dropped; load then returns the default.

<ParamField body="boxRuntime" type="SandBoxRuntime" default="remote">
Stored on `SandStoredSettings` version `1`. Written atomically via a pid-scoped temp file next to `settings.json`.
</ParamField>

<ParamField body="mode" type="string" required>
`setBoxRuntime` argument. Must pass `isSandBoxRuntime`.
</ParamField>

<ResponseField name="mode" type="SandBoxRuntime">
Persisted runtime after a successful start or stop.
</ResponseField>

<ResponseField name="status" type="LocalDockerStatus">
`available`, `running`, `ready`, `containerName`, `image`, `detail` from `getLocalDockerStatus`.
</ResponseField>

`getBoxRuntime` returns the stored mode plus a live Docker inspect/health snapshot. It does not start the VM.

```mermaid
sequenceDiagram
  participant UI as Settings Router
  participant Edge as setBoxRuntime
  participant Store as settings.json
  participant VM as grok-bot-local-vm
  participant Health as 127.0.0.1:1340/health
  participant Coord as Coordinator

  UI->>Edge: mode local-docker
  Edge->>Store: boxRuntime = local-docker
  Edge->>VM: docker run or start
  loop every 1s up to 180s
    Edge->>Health: GET /health Bearer token
    alt response.ok
      Edge->>Coord: restartCoordinator
      Edge-->>UI: mode + LocalDockerStatus
    else container exited
      Edge->>Store: boxRuntime = remote
      Edge-->>UI: throw
    end
  end
```

## What the container does

Image: `public.ecr.aws/k0i0n2g5/cursorenvironments/universal:sand-box-latest`.

| Fact | Value |
| --- | --- |
| Name | `grok-bot-local-vm` |
| Gateway | `http://127.0.0.1:1340` |
| Health | `GET /health`, Bearer `SAND_GATEWAY_TOKEN` |
| Owner label | `com.grok-bot.local-vm=1` |
| Schema label | `com.grok-bot.local-vm.schema-version=6` |
| Restart policy | `unless-stopped` |
| Auto-update | `SAND_BOX_AUTO_UPDATE=0` |
| Gateway bind in-box | `SAND_GATEWAY_BIND_HOST=0.0.0.0`, `SAND_HOST_PORT=1340` |

Host mounts are read-only content-addressed files, not the stock image copies. Optional read-only binds of `~/.codex` → `/root/.codex` and `~/.claude` → `/root/.claude` apply when those directories exist. Published ports are loopback-only.

After a successful toggle, coordinator `connect()` may still call remote `issueInferenceCredential` with a 3s timeout. If a token arrives, the connector can replace the container so it mounts `local-docker-credential/inference.json`. Toggle-on `startLocalDockerBox` itself does not wait for that credential.

Computer recreate while local: `docker restart grok-bot-local-vm`, then wait for health (`started-untrackable`). Force recreate: `docker rm --force`, then create again.

## Status strings

| Condition | `available` | `running` | `ready` | `detail` |
| --- | --- | --- | --- | --- |
| `docker info` fails | `false` | `false` | `false` | CLI output, or `Docker is not running.` |
| No container | `true` | `false` | `false` | `Ready to create the local VM.` |
| Name taken, label missing | `true` | inspect | `false` | `Container grok-bot-local-vm exists but is not owned by Grok Bot.` |
| Running, health OK | `true` | `true` | `true` | `Local Docker VM is ready.` |
| Running, health not OK | `true` | `true` | `false` | `Container is starting.` |
| Owned, stopped | `true` | `false` | `false` | `Local Docker VM is stopped.` |

## Failure and rollback

Optimistic UI flips the switch immediately. If `setBoxRuntime` throws, the renderer restores the previous mode and shows `error.message`.

Main rollback: failed `"local-docker"` → persist `"remote"`; failed `"remote"` → persist `"local-docker"`. Coordinator restart runs only after start/stop succeeds.

| Error | Typical cause |
| --- | --- |
| `Unknown box runtime.` | `mode` is not `"remote"` or `"local-docker"`. |
| `Local Docker VM is selected, but Docker is unavailable: …` | Daemon not running or `docker` missing. |
| `Local Docker VM cannot use grok-bot-local-vm: an unowned container already has that name.` | Name collision; connector will not take over. |
| `Local Docker VM container uses unexpected image …` | Existing owned container image ≠ pinned image. |
| `The reconstructed runtime is unavailable … refusing to start a stock local VM.` | `host-main.cjs` / `box-exec-daemon/main.cjs` not next to the compiled main. |
| `Could not create` / `Could not start` / `Could not replace` | `docker run` / `start` / `rm --force` non-zero. |
| `Local Docker VM stopped before its gateway became ready.` | Container exited during the 180s wait; last 80 log lines attached. |
| `Local Docker VM did not expose its gateway within three minutes.` | Process still running, `/health` never OK. |
| `Refusing to stop unowned container grok-bot-local-vm.` | Disable path while a foreign container is running. |

Replace (rm + recreate) happens when schema ≠ `6`, host SHA-256 label mismatches the staged `host-main.cjs`, or an inference credential is present but the container lacks `com.grok-bot.local-vm.inference-credential=1`.

<AccordionGroup>
<Accordion title="Inspect the VM from a shell">
```sh
docker inspect grok-bot-local-vm --format '{{index .Config.Labels "com.grok-bot.local-vm"}} {{index .Config.Labels "com.grok-bot.local-vm.schema-version"}}'
docker logs --tail 80 grok-bot-local-vm
```

Health from the host needs the token in `local-docker-vm.json` beside `settings.json` (`schemaVersion: 1`).
</Accordion>
</AccordionGroup>

## Next

<CardGroup>
<Card title="Box runtime" href="/box-runtime">
`SandBoxRuntime` values, default `remote`, and coordinator restart on switch.
</Card>
<Card title="Local Docker connector" href="/docker-sandbox-reference">
Image, labels, schema 6, mounts, and loopback gateway.
</Card>
<Card title="Router and sandbox failures" href="/router-failures">
Unowned `grok-bot-local-vm` and other router/sandbox errors.
</Card>
<Card title="Desktop RPC" href="/desktop-rpc">
`getBoxRuntime` / `setBoxRuntime` on `window.desktop.agent`.
</Card>
</CardGroup>
