> This page is for version v0.1.7.
> For other versions, use one of these documentation indexes:
> - Latest (v0.1.7) (default): https://docs.nvidia.com/openshell/latest/llms.txt
> - Dev: https://docs.nvidia.com/openshell/dev/llms.txt
> - v0.1.7: https://docs.nvidia.com/openshell/v0.1.7/llms.txt
> - v0.1.6: https://docs.nvidia.com/openshell/v0.1.6/llms.txt
> - v0.1.5: https://docs.nvidia.com/openshell/v0.1.5/llms.txt
> - v0.1.4: https://docs.nvidia.com/openshell/v0.1.4/llms.txt
> - v0.1.3: https://docs.nvidia.com/openshell/v0.1.3/llms.txt
> - v0.1.2: https://docs.nvidia.com/openshell/v0.1.2/llms.txt
> - v0.1.1: https://docs.nvidia.com/openshell/v0.1.1/llms.txt
> - v0.1.0: https://docs.nvidia.com/openshell/v0.1.0/llms.txt
> - v0.0.116: https://docs.nvidia.com/openshell/v0.0.116/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/openshell/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/openshell/_mcp/server.

# Running the Gateway as a Container

> Run the OpenShell gateway using docker run or docker-compose without the installer.

Use this approach when you want to run the OpenShell gateway as a container instead of installing it with the system package manager. This is useful on immutable OS distributions (Fedora CoreOS, bootc-based images, Silverblue) where the standard installer is not appropriate, or anywhere you prefer a container-first workflow.

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

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_COMPUTE_DRIVER=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
```

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
openshell status
```

> **Warning**
>
> 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

To run the gateway with mutual TLS, generate the PKI bundle first, then start the gateway with the cert paths configured.

Bootstrap the PKI into a local state directory:

```shell
mkdir -p ~/.local/state/openshell/tls

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 \
    --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.

Start the gateway with mTLS enabled:

```shell
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_COMPUTE_DRIVER=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 \
  -e OPENSHELL_TLS_KEY=/home/openshell/.local/state/openshell/tls/server/tls.key \
  -e OPENSHELL_TLS_CLIENT_CA=/home/openshell/.local/state/openshell/tls/ca.crt \
  -e OPENSHELL_ENABLE_MTLS_AUTH=true \
  -e OPENSHELL_DOCKER_TLS_CA=/home/openshell/.local/state/openshell/tls/ca.crt \
  -e OPENSHELL_DOCKER_TLS_CERT=/home/openshell/.local/state/openshell/tls/client/tls.crt \
  -e OPENSHELL_DOCKER_TLS_KEY=/home/openshell/.local/state/openshell/tls/client/tls.key \
  ghcr.io/nvidia/openshell/gateway:latest
```

Register the gateway with mTLS:

```shell
openshell gateway add https://127.0.0.1:8080 --local --name local
```

## Docker Compose

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:

| File                 | Purpose                                                       |
| -------------------- | ------------------------------------------------------------- |
| `docker-compose.yml` | Gateway and init services, volumes, and environment variables |
| `gateway.toml`       | TOML configuration mounted into the container                 |

Clone or copy those files, then start the gateway:

```shell
docker compose -f deploy/docker/docker-compose.yml up -d
```

The `init` service runs first and writes the sandbox JWT signing keys to `/var/lib/openshell/tls` on the host. The Docker driver requires them, and existing keys are kept. Do not commit them. This setup disables TLS and user authentication and publishes the port on loopback only.

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
```

### Enable mTLS

The `init` service already writes the server, client, and CA certificates to `/var/lib/openshell/tls`, so enabling mTLS only needs configuration changes:

1. In `gateway.toml`, remove `disable_tls = true` and the `[openshell.gateway.auth]` table.
2. In `docker-compose.yml`, add `OPENSHELL_LOCAL_TLS_DIR: /var/lib/openshell/tls` under the `gateway` service `environment`. The gateway loads its server certificate, client CA, and sandbox client bundle from that directory, and turns on mTLS user authentication.
3. Apply the change:

   ```shell
   docker compose -f deploy/docker/docker-compose.yml up -d
   ```

Then copy the client bundle to `~/.config/openshell/gateways/local/mtls/`, where the CLI loads it (refer to [Gateway Authentication](/openshell/v0.1.7/how-it-works/gateways/authentication#mtls)). The files are owned by root, so copy them with `sudo` and hand them to your user. If you registered the plaintext gateway as `local`, run `openshell gateway remove local` first.

```shell
mtls=~/.config/openshell/gateways/local/mtls
mkdir -p "$mtls"
sudo cp /var/lib/openshell/tls/ca.crt \
  /var/lib/openshell/tls/client/tls.crt \
  /var/lib/openshell/tls/client/tls.key "$mtls"/
sudo chown "$USER" "$mtls"/*
openshell gateway add https://127.0.0.1:8080 --local --name local
```

## Using Podman

The Podman driver runs sandboxes as rootless containers through the user Podman socket, and [Sandbox Runtimes](/openshell/v0.1.7/how-it-works/sandboxes/runtimes#podman-driver) lists its requirements. The commands in this section assume rootless Podman. Enable the socket for your user:

```shell
systemctl --user enable --now podman.socket
```

Running the gateway as a rootless Podman container needs five settings beyond the Docker commands above:

1. **Writable state directory.** The image runs as user `1000` and cannot write to its default home directory. Mount a host directory, set `HOME` and `XDG_DATA_HOME` to it, and add `--userns=keep-id:uid=1000,gid=1000` so the container can read and write it.

2. **Same-path data directory.** The driver bind-mounts files from the data directory into each sandbox, and the host Podman resolves the path on the host. Mount the directory at the same absolute path inside the container. A named volume does not work.

3. **Signing keys and TLS certificates.** Generate them with `generate-certs` and set `OPENSHELL_LOCAL_TLS_DIR` to the output directory. The gateway then serves HTTPS and requires client certificates.

4. **SELinux type.** On SELinux hosts, add `--security-opt label=type:container_runtime_t`. Without it, the container cannot read the data directory or connect to the Podman socket. Podman ignores the option on other hosts. The gateway can start containers as your user through that socket, so run only the published gateway image.

5. **Matching ports.** Supervisor containers call back to `127.0.0.1` on the gateway's in-container port. Publish the same port number on the host, or set `grpc_endpoint` as described below.

Generate the keys and certificates:

```shell
data_dir="$HOME/.local/share/openshell-gateway"
mkdir -p "$data_dir"

podman run --rm \
  --userns=keep-id:uid=1000,gid=1000 \
  --security-opt label=type:container_runtime_t \
  -v "$data_dir:$data_dir" \
  -e HOME="$data_dir" \
  ghcr.io/nvidia/openshell/gateway:latest \
  generate-certs --output-dir "$data_dir/tls"
```

Start the gateway:

```shell
podman run -d \
  --name openshell-gateway \
  --userns=keep-id:uid=1000,gid=1000 \
  --security-opt label=type:container_runtime_t \
  -p 127.0.0.1:8080:8080 \
  -v "$data_dir:$data_dir" \
  -v "$XDG_RUNTIME_DIR/podman/podman.sock:/var/run/podman.sock" \
  -e HOME="$data_dir" \
  -e XDG_DATA_HOME="$data_dir" \
  -e OPENSHELL_LOCAL_TLS_DIR="$data_dir/tls" \
  -e OPENSHELL_COMPUTE_DRIVER=podman \
  -e OPENSHELL_PODMAN_SOCKET=/var/run/podman.sock \
  -e "OPENSHELL_DB_URL=sqlite:$data_dir/gateway.db" \
  ghcr.io/nvidia/openshell/gateway:latest
```

If the gateway container exits at startup with `--tls-cert is required when TLS is enabled`, the gateway cannot read its certificates. `OPENSHELL_LOCAL_TLS_DIR` is unset, or it points at a directory that is empty or that the container cannot read. Run `generate-certs`, set the variable, and check `--userns=keep-id:uid=1000,gid=1000` and, on SELinux hosts, `--security-opt label=type:container_runtime_t`. Disabling TLS, as the error message suggests, would remove authentication.

The gateway takes a few seconds to start listening. Register it with the CLI and confirm the connection:

```shell
OPENSHELL_LOCAL_TLS_DIR="$data_dir/tls" \
  openshell gateway add https://127.0.0.1:8080 --local --name local
openshell status
```

The CLI imports its client certificates from `OPENSHELL_LOCAL_TLS_DIR`. Without it, the CLI reads `~/.local/state/openshell/tls`, which can hold another gateway's certificates and fails with `BadSignature`. If a gateway named `local` is already registered, run `openshell gateway remove local` first.

To publish a different host port, set `grpc_endpoint` to the host address under `[openshell.drivers.podman]` in a gateway TOML file. For example, with `-p 127.0.0.1:9090:8080`, the file needs only `version = 2` under `[openshell]` and `grpc_endpoint = "https://127.0.0.1:9090"`. Save it as `$data_dir/gateway.toml`, add `-e OPENSHELL_GATEWAY_CONFIG="$data_dir/gateway.toml"` to the gateway command, and register the gateway at `https://127.0.0.1:9090`. The [gateway configuration reference](/openshell/v0.1.7/how-it-works/gateways/configuration#podman) lists the other Podman options.

## Next Steps

* To prepare an image and launch an agent, refer to [Run Your First Agent](/openshell/v0.1.7/about/run-your-first-agent).
* To control what the agent can access, refer to [Policies](/openshell/v0.1.7/how-it-works/policies/overview).
* For environment variable reference, refer to [Sandbox Runtimes](/openshell/v0.1.7/how-it-works/sandboxes/runtimes).