diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml new file mode 100644 index 000000000..5c25e7980 --- /dev/null +++ b/deploy/docker/docker-compose.yml @@ -0,0 +1,137 @@ +# 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 openshell-docker \ +# --endpoint http://localhost:18080 \ +# --no-tls +# +# 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 settings below use OPENSHELL_* environment variables. +# The gateway binary reads these directly, so no separate config file +# is required. See `docker run --rm ghcr.io/nvidia/openshell/gateway:latest +# --help` for the full list of supported variables. +# +# The dev image (ghcr.io/nvidia/openshell/gateway:dev) also supports a +# TOML configuration file via --config / OPENSHELL_GATEWAY_CONFIG. +# See gateway.toml in this directory for an equivalent TOML reference. +# +# 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 is not automatically added on Linux Docker. +# Add the following under the gateway service to enable it: +# extra_hosts: +# - "host.docker.internal:host-gateway" + +services: + gateway: + image: ghcr.io/nvidia/openshell/gateway:${IMAGE_TAG:-latest} + restart: unless-stopped + + # 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. + - "${OPENSHELL_PORT:-8080}:8080" + # Health endpoint (GET /healthz, GET /readyz) + - "${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 + + environment: + # ── Listener ─────────────────────────────────────────────────────────── + # bind-address is already set to 0.0.0.0 by the image's default CMD; + # these vars configure the remaining listener ports. + OPENSHELL_HEALTH_PORT: "8081" + + # ── Auth / TLS ────────────────────────────────────────────────────────── + OPENSHELL_DISABLE_TLS: "true" + + # ── Compute driver ───────────────────────────────────────────────────── + OPENSHELL_DRIVERS: "docker" + + # ── Sandbox defaults ──────────────────────────────────────────────────── + # Default image used when --from is not specified. + OPENSHELL_SANDBOX_IMAGE: "ghcr.io/nvidia/openshell-community/sandboxes/base:latest" + + # ── Docker driver ─────────────────────────────────────────────────────── + # Supervisor image used to extract the openshell-sandbox binary on first + # start. The binary is cached in /var/lib/openshell and reused on + # subsequent starts. + OPENSHELL_DOCKER_SUPERVISOR_IMAGE: "ghcr.io/nvidia/openshell/supervisor:latest" + + # Address sandbox containers use to call back to this gateway. + # The Docker driver always substitutes host.openshell.internal and the + # gateway bind port into this URL, so only the scheme (http/https) is + # meaningful here. The gateway must be published on the same port number + # on the Docker host (port 8080 by default — see ports above). + OPENSHELL_GRPC_ENDPOINT: "http://host.openshell.internal:8080" + + # ── Persistence ──────────────────────────────────────────────────────── + OPENSHELL_DB_URL: "sqlite:/var/lib/openshell/gateway.db?mode=rwc" + + # ── XDG paths ────────────────────────────────────────────────────────── + # Point XDG data home at the bind-mounted data directory so the + # extracted supervisor binary lands at a host-resolvable path. + 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..e77e7b250 --- /dev/null +++ b/deploy/docker/gateway.toml @@ -0,0 +1,59 @@ +# 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 reference. +# +# This file is the TOML equivalent of the OPENSHELL_* environment variables +# set in docker-compose.yml. It requires the dev image or a release that +# includes RFC 0003 config-file support (post-0.0.42). +# +# To use this file with docker-compose, switch the image to the dev tag and +# replace the environment section with a config file mount: +# +# services: +# gateway: +# image: ghcr.io/nvidia/openshell/gateway:dev +# volumes: +# - type: bind +# source: ./gateway.toml +# target: /etc/openshell/gateway.toml +# read_only: true +# environment: +# OPENSHELL_GATEWAY_CONFIG: /etc/openshell/gateway.toml +# XDG_DATA_HOME: /var/lib/openshell +# HOME: /var/lib/openshell +# command: [] # clear the default CMD; config drives everything +# +# grpc_endpoint note: +# host.docker.internal is automatically resolvable from containers on +# Docker Desktop (Windows / macOS). On Linux, replace with the host IP +# or add extra_hosts: ["host.docker.internal:host-gateway"] to compose. + +[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 4413f388e..2ccb189db 100644 --- a/docs/about/container-gateway.mdx +++ b/docs/about/container-gateway.mdx @@ -12,29 +12,69 @@ 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. Set this to `http://127.0.0.1:8080` so the driver knows the scheme and port. 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. Extract it before starting the gateway and mount it at the same path. + ## 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://127.0.0.1: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: +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 +82,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 mTLS to prevent unauthenticated access. +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 +100,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/` and copies the client bundle to `~/.config/openshell/gateways/openshell/mtls/` so the CLI picks it up automatically. @@ -69,10 +113,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_TLS_CERT=/home/openshell/.local/state/openshell/tls/server/tls.crt \ -e OPENSHELL_TLS_KEY=/home/openshell/.local/state/openshell/tls/server/tls.key \ @@ -91,20 +139,41 @@ 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. +The following `compose.yml` runs the gateway with the Docker driver on an immutable OS or any +Docker host. It includes all required configuration for sandbox containers to call back to the +gateway. + +Before starting, extract the supervisor binary to a host path. The path must be the same on +both the host and inside the gateway container because the host Docker daemon uses it as a +bind-mount source when creating sandbox containers: + +```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 +``` + +Save the following as `~/openshell/compose.yml`, substituting your home directory for `HOME`: ```yaml services: gateway: image: ghcr.io/nvidia/openshell/gateway:latest restart: unless-stopped + group_add: + - docker ports: - "127.0.0.1:8080:8080" volumes: - openshell-state:/var/openshell - /var/run/docker.sock:/var/run/docker.sock + - HOME/openshell/supervisor/openshell-sandbox:HOME/openshell/supervisor/openshell-sandbox:ro environment: OPENSHELL_DRIVERS: docker + OPENSHELL_GRPC_ENDPOINT: "http://127.0.0.1:8080" + OPENSHELL_DOCKER_SUPERVISOR_BIN: "HOME/openshell/supervisor/openshell-sandbox" OPENSHELL_DB_URL: "sqlite:/var/openshell/openshell.db" OPENSHELL_DISABLE_TLS: "true" @@ -118,12 +187,19 @@ Start the gateway: docker compose 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..db836b44a --- /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: + +``` +/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 c03e924f7..c6e6a3f60 100644 --- a/docs/get-started/tutorials/index.mdx +++ b/docs/get-started/tutorials/index.mdx @@ -12,6 +12,11 @@ Hands-on walkthroughs that teach OpenShell concepts by building real configurati + + +Run the OpenShell gateway as a Docker Compose service and create agent sandboxes including OpenClaw. + + Create a sandbox, observe default-deny networking, apply a read-only L7 policy, and inspect audit logs. No AI agent required.