From 2953e1cc1ad847bde4595231ed8ceae23abe81a0 Mon Sep 17 00:00:00 2001 From: Eric Curtin Date: Wed, 3 Jun 2026 15:46:19 +0100 Subject: [PATCH] docs(container-gateway): fix Docker driver setup for containerized gateway MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing docs omitted or misstated several requirements when running the gateway as a container with the Docker compute driver: - OPENSHELL_GRPC_ENDPOINT is required; the Docker driver uses only the scheme (http/https) — host and port are substituted automatically with host.openshell.internal and the gateway's own bind port - Supervisor binary must be extracted to a host path before starting the gateway; bind-mount sources are resolved by the host Docker daemon so the path must be identical inside and outside the gateway container - Docker socket access requires adding the docker group (UID 1000 default) - Port binding should remain 127.0.0.1; Docker driver adds a bridge listener automatically - add --server-san host.openshell.internal to generate-certs for mTLS - Complete the mTLS docker run with all Docker driver requirements - Add deploy/docker/gateway.toml — TOML config for the Docker driver - Add deploy/docker/docker-compose.yml referencing the TOML - Add docs/get-started/tutorials/docker-compose.mdx tutorial page - Remote gateway registration instructions (--remote flag) Address reviewer feedback: - Move Docker Compose tutorials card to the bottom of the list - Replace inline YAML snippet in Docker Compose section with a reference to deploy/docker/ to avoid drift - Clarify OPENSHELL_DB_URL is safe in compose.yml (plain SQLite path, no credentials); the TOML block targets credential-bearing DSNs - Note that ./ in source: resolves relative to the compose file directory - Clarify that only the scheme from OPENSHELL_GRPC_ENDPOINT matters - Add note that the tilde volume mount resolves to the same absolute path on both host and container --- deploy/docker/docker-compose.yml | 133 ++++++++++++ deploy/docker/gateway.toml | 51 +++++ docs/about/container-gateway.mdx | 106 ++++++--- docs/get-started/tutorials/docker-compose.mdx | 204 ++++++++++++++++++ docs/get-started/tutorials/index.mdx | 6 + 5 files changed, 473 insertions(+), 27 deletions(-) create mode 100644 deploy/docker/docker-compose.yml create mode 100644 deploy/docker/gateway.toml create mode 100644 docs/get-started/tutorials/docker-compose.mdx diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml new file mode 100644 index 000000000..9f6926142 --- /dev/null +++ b/deploy/docker/docker-compose.yml @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# OpenShell gateway — docker-compose setup (Docker compute driver) +# +# Prerequisites: +# - Docker Desktop (Windows / macOS) or Docker Engine + Compose plugin (Linux) +# - The openshell CLI installed on your workstation +# +# Quick start: +# +# 1. Start the gateway: +# docker compose up -d +# +# 2. Register the gateway with the CLI (one-time): +# openshell gateway add http://localhost:8080 --name openshell-docker +# +# 3. Configure an AI provider (example: Anthropic): +# ANTHROPIC_API_KEY=sk-ant-... \ +# openshell provider create --type anthropic --from-existing +# +# 4. Create a sandboxed agent — Claude Code or OpenClaw: +# openshell sandbox create -- claude +# openshell sandbox create --from openclaw +# +# Sandbox containers are managed by the gateway, not by this Compose file. +# Each `openshell sandbox create` call launches a fresh container; the gateway +# tracks their lifecycle. +# +# Configuration: +# All gateway and driver settings live in gateway.toml in this directory. +# Three values cannot be expressed in the TOML file and remain as env vars: +# - OPENSHELL_DB_URL (explicitly blocked from the config file to prevent +# secrets from being committed to VCS) +# - XDG_DATA_HOME / HOME (OS-level path-resolution vars outside the +# gateway config schema) +# +# command: [] note: +# The gateway image's default CMD is ["--bind-address", "0.0.0.0", "--port", +# "8080"]. CLI flags beat TOML in the merge order, so without clearing the +# CMD the TOML's bind_address = "127.0.0.1:8080" is silently ignored and the +# gateway binds 0.0.0.0. Setting command: [] lets the TOML file own all +# gateway settings. +# +# Data directory note: +# /var/lib/openshell is bind-mounted at the SAME absolute path in both the +# host and the container. This is required so that the supervisor binary +# extracted from the supervisor image can be passed to Docker as a host-side +# bind-mount source when sandbox containers are created. Named volumes +# cannot be used here because Docker resolves bind-mount sources against the +# host filesystem, not the container filesystem. +# +# Linux note: +# host.docker.internal and host.openshell.internal are not automatically +# added on Linux Docker. Add the following under the gateway service: +# extra_hosts: +# - "host.docker.internal:host-gateway" +# - "host.openshell.internal:host-gateway" + +services: + gateway: + image: ghcr.io/nvidia/openshell/gateway:${IMAGE_TAG:-latest} + restart: unless-stopped + + # Clear the default CMD so gateway.toml owns all settings (see note above). + command: [] + + # This setup is Docker-outside-of-Docker (DooD), not Docker-in-Docker (DinD). + # The gateway uses the host's Docker socket to create sibling containers on the + # host, rather than running a nested Docker daemon. DooD does NOT require + # --privileged; it only needs read/write access to /var/run/docker.sock. + # + # Run as UID 0 so the gateway can: + # - write the extracted supervisor binary to /var/lib/openshell + # - access /var/run/docker.sock (typically owned by root or the docker group) + # Distroless images have no /etc/passwd, so the numeric UID must be used. + # This is appropriate for local development. Production deployments + # should use a dedicated non-root UID with explicit docker-group membership. + user: "0" + + ports: + # gRPC / control-plane API (used by the openshell CLI and sandbox callbacks) + # The Docker driver injects host.openshell.internal: into sandbox + # containers as the callback endpoint. The gateway's internal port is 8080, so + # host port 8080 must be published at the same number so that + # host.openshell.internal:8080 routes to the gateway container. + # gateway.toml binds to 127.0.0.1 — the Docker driver adds the bridge listener + # automatically so sandbox containers can reach the gateway without 0.0.0.0. + - "127.0.0.1:${OPENSHELL_PORT:-8080}:8080" + # Health endpoint (GET /healthz, GET /readyz) + - "127.0.0.1:${OPENSHELL_HEALTH_PORT:-8081}:8081" + + volumes: + # Docker socket — lets the gateway create and manage sandbox containers. + - /var/run/docker.sock:/var/run/docker.sock + + # Data directory — must be a bind-mount with source == target so that + # paths written inside the container are resolvable by Docker when it + # creates sandbox containers (see note above). + # /var/lib/openshell is intentionally not namespaced to a sub-path + # (e.g. /var/lib/openshell/gateway): the path must match exactly on + # both the host and inside the container, and a single gateway per host + # is the expected topology. + - type: bind + source: /var/lib/openshell + target: /var/lib/openshell + bind: + create_host_path: true + + # TOML config — all gateway and driver settings live here. + # Docker Compose resolves ./ relative to the directory that contains this + # docker-compose.yml file (deploy/docker/), not the CWD of the caller. + - type: bind + source: ./gateway.toml + target: /etc/openshell/gateway.toml + read_only: true + + environment: + # Point the gateway at the TOML config file mounted above. + OPENSHELL_GATEWAY_CONFIG: /etc/openshell/gateway.toml + + # Database URL cannot be set in the TOML config file — it is explicitly + # blocked there to prevent credential-bearing URLs (e.g. postgresql://user:pass@host/db) + # from being committed to VCS. A plain SQLite path contains no credentials, so it + # is safe to include here. Swap this for a real DSN via an env file or secret if + # you switch to an external database. + OPENSHELL_DB_URL: "sqlite:/var/lib/openshell/gateway.db?mode=rwc" + + # XDG path variables are OS-level; they are not part of the gateway config + # schema. Setting them ensures the extracted supervisor binary lands in the + # bind-mounted directory so its path is resolvable by the host Docker daemon. + XDG_DATA_HOME: /var/lib/openshell + HOME: /var/lib/openshell diff --git a/deploy/docker/gateway.toml b/deploy/docker/gateway.toml new file mode 100644 index 000000000..4fe84d633 --- /dev/null +++ b/deploy/docker/gateway.toml @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# OpenShell gateway TOML configuration — Docker compute driver. +# +# This file is the primary configuration source for docker-compose.yml in this +# directory. It is mounted read-only at /etc/openshell/gateway.toml inside the +# gateway container and loaded via OPENSHELL_GATEWAY_CONFIG. +# +# Why docker-compose.yml sets command: []: +# The gateway image's default CMD passes --bind-address 0.0.0.0 --port 8080 +# as explicit CLI flags. CLI flags beat the TOML file in the merge order, so +# bind_address = "127.0.0.1:8080" below would be silently ignored without +# clearing the CMD first. +# +# grpc_endpoint note: +# host.docker.internal is automatically resolvable from containers on +# Docker Desktop (Windows / macOS). On Linux, add extra_hosts to the +# gateway service: +# extra_hosts: +# - "host.docker.internal:host-gateway" +# - "host.openshell.internal:host-gateway" + +[openshell] +version = 1 + +[openshell.gateway] +# Bind to loopback only. The Docker driver adds an extra listener on the +# bridge interface automatically so sandbox containers can reach the gateway. +bind_address = "127.0.0.1:8080" +health_bind_address = "127.0.0.1:8081" +log_level = "info" +compute_drivers = ["docker"] +disable_tls = true + +[openshell.drivers.docker] +# Default image pulled for `openshell sandbox create` without --from. +default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest" +# Supervisor image from which the openshell-sandbox binary is extracted on +# first start. The binary is cached to XDG_DATA_HOME and reused on restart. +supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest" +# Only pull images that are not already cached locally. +image_pull_policy = "IfNotPresent" +# Prefix applied to sandbox container names. +sandbox_namespace = "openshell" +# Address sandbox containers use to call back to the gateway. +# The Docker driver replaces the host with host.openshell.internal and the +# port with the gateway's own bind port (8080). Only the scheme survives. +# The gateway must be published on port 8080 on the Docker host so that +# host.openshell.internal:8080 resolves to the gateway container. +grpc_endpoint = "http://host.openshell.internal:8080" diff --git a/docs/about/container-gateway.mdx b/docs/about/container-gateway.mdx index 370d7f146..42fe2c785 100644 --- a/docs/about/container-gateway.mdx +++ b/docs/about/container-gateway.mdx @@ -12,29 +12,79 @@ Use this approach when you want to run the OpenShell gateway as a container inst The gateway image is published at `ghcr.io/nvidia/openshell/gateway`. +## Prerequisites for the Docker Driver + +When the gateway runs as a container and creates Docker-backed sandboxes, the gateway container +communicates with the host Docker daemon via the mounted socket. This requires three things beyond +a basic `docker run`: + +1. **Docker socket access.** The gateway process must be able to read and write the Docker socket. + Add the `docker` group (or the GID of `/var/run/docker.sock`) so the socket is accessible + without running as root. + +2. **gRPC endpoint.** Sandbox containers call back to the gateway over the `OPENSHELL_GRPC_ENDPOINT` + address. The Docker driver substitutes `host.openshell.internal` as the host and the gateway's + own bind port as the port — only the **scheme** (`http` or `https`) is preserved. Use + `http://host.openshell.internal:8080` when TLS is disabled and `https://host.openshell.internal:8080` + when mTLS is enabled. The docker driver automatically binds the gateway to the bridge network + interface so sandbox containers can reach it — you do not need to expose the port on `0.0.0.0`. + +3. **Supervisor binary on the host.** The gateway bind-mounts the `openshell-sandbox` supervisor + binary into each sandbox container. Because bind-mount paths are resolved by the host Docker + daemon (not inside the gateway container), the binary must exist at a path on the **host** + filesystem and be mounted at the **same absolute path** inside the gateway container. That way + the path the gateway records internally matches what Docker can find on the host when it + creates sandbox containers. + ## Quick Start -This example runs the gateway locally with TLS disabled. It is suitable for development on a single machine. Binding to `127.0.0.1` prevents remote access without authentication. +Extract the supervisor binary to the host once, then start the gateway: + +```shell +mkdir -p ~/openshell/supervisor +docker create --name tmp-supervisor ghcr.io/nvidia/openshell/supervisor:latest +docker cp tmp-supervisor:/openshell-sandbox ~/openshell/supervisor/openshell-sandbox +docker rm tmp-supervisor +chmod +x ~/openshell/supervisor/openshell-sandbox +``` + +Start the gateway: ```shell docker run -d \ --name openshell-gateway \ --restart unless-stopped \ + --group-add docker \ -p 127.0.0.1:8080:8080 \ -v openshell-state:/var/openshell \ -v /var/run/docker.sock:/var/run/docker.sock \ + -v ~/openshell/supervisor/openshell-sandbox:~/openshell/supervisor/openshell-sandbox:ro \ -e OPENSHELL_DRIVERS=docker \ + -e OPENSHELL_GRPC_ENDPOINT=http://host.openshell.internal:8080 \ + -e OPENSHELL_DOCKER_SUPERVISOR_BIN=~/openshell/supervisor/openshell-sandbox \ -e OPENSHELL_DB_URL=sqlite:/var/openshell/openshell.db \ -e OPENSHELL_DISABLE_TLS=true \ ghcr.io/nvidia/openshell/gateway:latest ``` -Register the gateway with the CLI: +The volume mount uses `~/openshell/supervisor/openshell-sandbox` for both the host and container +paths. The shell expands `~` in both halves before passing the argument to Docker, so both sides +resolve to the same absolute path (e.g., `/home/user/openshell/supervisor/openshell-sandbox`). +This satisfies the same-path requirement so the host Docker daemon can find the binary when +creating sandbox containers. + +Register the gateway with the CLI. If running on the same machine, use `--local`: ```shell openshell gateway add http://127.0.0.1:8080 --local --name local ``` +If registering from a different machine on the same network, use the host IP and `--remote`: + +```shell +openshell gateway add http://HOST_IP:8080 --remote --name remote +``` + Confirm the CLI can reach the gateway: ```shell @@ -42,7 +92,9 @@ openshell status ``` -Disabling TLS removes authentication. Binding to `127.0.0.1` limits access to the local machine. If you expose the port on `0.0.0.0`, enable TLS and local mTLS user authentication, or put the gateway behind a trusted proxy with its own authentication. +Disabling TLS removes authentication. This example binds to `127.0.0.1` so only local +connections are accepted. To accept remote connections, enable mTLS or restrict access with +a firewall rule. ## Full mTLS Setup @@ -58,7 +110,9 @@ docker run --rm \ -v "$HOME/.local/state/openshell:/home/openshell/.local/state/openshell" \ -v "$HOME/.config/openshell:/home/openshell/.config/openshell" \ ghcr.io/nvidia/openshell/gateway:latest \ - generate-certs --output-dir /home/openshell/.local/state/openshell/tls + generate-certs \ + --output-dir /home/openshell/.local/state/openshell/tls \ + --server-san host.openshell.internal ``` This writes the server and client certificates under `~/.local/state/openshell/tls/`, writes sandbox JWT signing keys under `~/.local/state/openshell/tls/jwt/`, and copies the client bundle to `~/.config/openshell/gateways/openshell/mtls/` so the CLI picks it up automatically. @@ -69,10 +123,14 @@ Start the gateway with mTLS enabled: docker run -d \ --name openshell-gateway \ --restart unless-stopped \ + --group-add docker \ -p 127.0.0.1:8080:8080 \ -v "$HOME/.local/state/openshell:/home/openshell/.local/state/openshell" \ -v /var/run/docker.sock:/var/run/docker.sock \ + -v ~/openshell/supervisor/openshell-sandbox:~/openshell/supervisor/openshell-sandbox:ro \ -e OPENSHELL_DRIVERS=docker \ + -e OPENSHELL_GRPC_ENDPOINT=https://127.0.0.1:8080 \ + -e OPENSHELL_DOCKER_SUPERVISOR_BIN=~/openshell/supervisor/openshell-sandbox \ -e OPENSHELL_DB_URL=sqlite:/home/openshell/.local/state/openshell/openshell.db \ -e OPENSHELL_LOCAL_TLS_DIR=/home/openshell/.local/state/openshell/tls \ -e OPENSHELL_TLS_CERT=/home/openshell/.local/state/openshell/tls/server/tls.crt \ @@ -93,39 +151,33 @@ openshell gateway add https://127.0.0.1:8080 --local --name local ## Docker Compose -Save the following as `compose.yml`. This uses the TLS-disabled configuration bound to localhost, suitable for local development. - -```yaml -services: - gateway: - image: ghcr.io/nvidia/openshell/gateway:latest - restart: unless-stopped - ports: - - "127.0.0.1:8080:8080" - volumes: - - openshell-state:/var/openshell - - /var/run/docker.sock:/var/run/docker.sock - environment: - OPENSHELL_DRIVERS: docker - OPENSHELL_DB_URL: "sqlite:/var/openshell/openshell.db" - OPENSHELL_DISABLE_TLS: "true" - -volumes: - openshell-state: -``` +The [`deploy/docker/`](https://github.com/NVIDIA/OpenShell/tree/main/deploy/docker) directory in +the repository contains a production-ready Compose setup with full inline documentation: -Start the gateway: +| File | Purpose | +|---|---| +| `docker-compose.yml` | Gateway service, volumes, and environment variables | +| `gateway.toml` | TOML configuration mounted into the container | + +Clone or copy those files, then start the gateway: ```shell -docker compose up -d +docker compose -f deploy/docker/docker-compose.yml up -d ``` -Register the gateway with the CLI: +Register the gateway with the CLI. If registering from the same machine: ```shell openshell gateway add http://127.0.0.1:8080 --local --name local ``` +If registering from a different machine on the same network, replace `HOST_IP` with the +machine's LAN address: + +```shell +openshell gateway add http://HOST_IP:8080 --remote --name remote +``` + ## Using Podman Replace `docker` with `podman` in the commands above. Mount the Podman socket instead of the Docker socket and set the driver to `podman`: diff --git a/docs/get-started/tutorials/docker-compose.mdx b/docs/get-started/tutorials/docker-compose.mdx new file mode 100644 index 000000000..645b5ddc9 --- /dev/null +++ b/docs/get-started/tutorials/docker-compose.mdx @@ -0,0 +1,204 @@ +--- +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +title: "Run the Gateway with Docker Compose" +sidebar-title: "Docker Compose Setup" +slug: "get-started/tutorials/docker-compose" +description: "Run the OpenShell gateway as a Docker Compose service and create agent sandboxes including OpenClaw." +keywords: "Generative AI, Docker Compose, Gateway, Sandbox, OpenClaw, Docker, Installation" +--- + +This tutorial shows how to run the OpenShell gateway as a Docker Compose service on a Linux host or on a machine running Docker Desktop (Windows or macOS). + +After completing this tutorial you have: + +- An OpenShell gateway running as a Compose service. +- The `openshell` CLI registered against that gateway. +- An AI provider configured with your API key. +- A running OpenClaw sandbox. + +## Prerequisites + +- Docker Desktop (Windows or macOS) or Docker Engine with the Compose plugin (Linux). +- The `openshell` CLI installed on your workstation. See [Install the CLI](#install-the-cli) below. +- Port 8080 available on the host. + +## Compose files + +The Compose configuration lives at [`deploy/docker/`](https://github.com/NVIDIA/OpenShell/tree/main/deploy/docker) in the repository. + +| File | Purpose | +|---|---| +| `docker-compose.yml` | Gateway service, volumes, and environment variables | +| `gateway.toml` | TOML reference for release builds with config-file support | + +## Port note + +The Docker compute driver injects `host.openshell.internal:` into every sandbox container as its callback address. The gateway listens on port 8080 inside the container, so **port 8080 must be published at the same number on the Docker host**. Publishing it as a different host port (for example `18080:8080`) causes sandbox containers to call back to the wrong port and remain stuck in the `Provisioning` phase. + +If port 8080 is taken, change `OPENSHELL_SERVER_PORT` and update the port mapping to `:8080`, then set `OPENSHELL_PORT=` in an `.env` file. + +## Data directory + +The gateway extracts the `openshell-sandbox` supervisor binary from `ghcr.io/nvidia/openshell/supervisor:latest` on first start and caches it at: + +```text +/var/lib/openshell/openshell/docker-supervisor//openshell-sandbox +``` + +This path is used as a bind-mount source when Docker creates sandbox containers. +Docker resolves bind-mount sources against the **host filesystem**, not the container filesystem, so the data directory must be bind-mounted at the **same absolute path** in both the host and the container. + +The Compose file uses `/var/lib/openshell` for this purpose and sets `create_host_path: true` so Docker creates it on first run. + +## Start the gateway + +```shell +cd deploy/docker +docker compose up -d +``` + +Verify the gateway is healthy: + +```shell +curl -sf http://localhost:8080/healthz +``` + +## Install the CLI + +**Binary (recommended — macOS / Linux / WSL):** + +```shell +curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh +``` + +**From PyPI (any platform with [uv](https://docs.astral.sh/uv/)):** + +```shell +uv tool install -U openshell +``` + + +On Windows without WSL, install the CLI inside a WSL 2 distribution (for example AlmaLinux or Ubuntu) and run all `openshell` commands from that distribution. + + +## Register the gateway + +Run this once after the gateway starts: + +```shell +openshell gateway add http://localhost:8080 --name openshell-docker +``` + +Verify the connection: + +```shell +openshell status +``` + +The output should show `Status: Connected`. + +## Configure an AI provider + +Set your API key as an environment variable and create a provider: + + + + +```shell +ANTHROPIC_API_KEY=sk-ant-... \ + openshell provider create --name anthropic --type anthropic --from-existing +``` + + + + +```shell +OPENAI_API_KEY=sk-... \ + openshell provider create --name openai --type openai --from-existing +``` + + + + +Confirm the provider was stored: + +```shell +openshell provider list +``` + +## Pre-pull sandbox images (optional) + +Sandbox images are pulled automatically on first use, but the initial pull can take several minutes for large images. Pre-pull to avoid long waits at sandbox creation time: + +```shell +# Base image — includes Claude Code, OpenCode, Codex, and Copilot +docker pull ghcr.io/nvidia/openshell-community/sandboxes/base:latest + +# OpenClaw image +docker pull ghcr.io/nvidia/openshell-community/sandboxes/openclaw:latest +``` + +## Create a sandbox + + + + +```shell +openshell sandbox create --from openclaw +``` + +OpenClaw launches directly. The first run pulls the image if it is not cached. + + + + +```shell +openshell sandbox create -- claude +``` + + + + +```shell +openshell sandbox create -- opencode +``` + + + + +Wait for the phase to change from `Provisioning` to `Ready`: + +```shell +openshell sandbox list +``` + +Then connect: + +```shell +openshell sandbox connect +``` + +## Manage the gateway + +| Command | Purpose | +|---|---| +| `docker compose up -d` | Start or restart the gateway | +| `docker compose down` | Stop the gateway and remove the container | +| `docker compose logs -f` | Tail gateway logs | +| `docker compose pull` | Pull a new gateway image version | + +## Linux notes + +On Linux, `host.docker.internal` and `host.openshell.internal` are not automatically resolvable from containers. Add the following under the `gateway` service in `docker-compose.yml`: + +```yaml +extra_hosts: + - "host.docker.internal:host-gateway" + - "host.openshell.internal:host-gateway" +``` + +## Next steps + +- [First Network Policy](/get-started/tutorials/first-network-policy) — apply L7 policies to your sandbox. +- [GitHub Push Access](/get-started/tutorials/github-sandbox) — grant a sandbox scoped GitHub access. diff --git a/docs/get-started/tutorials/index.mdx b/docs/get-started/tutorials/index.mdx index 0d82509ad..bc30bcfc4 100644 --- a/docs/get-started/tutorials/index.mdx +++ b/docs/get-started/tutorials/index.mdx @@ -36,4 +36,10 @@ Route inference through Ollama using cloud-hosted or local models, and verify it Route inference to a local LM Studio server using the OpenAI-compatible or Anthropic-compatible APIs. + + + +Run the OpenShell gateway as a Docker Compose service and create agent sandboxes including OpenClaw. + +