diff --git a/docs/features/open-terminal/advanced/multi-user.md b/docs/features/open-terminal/advanced/multi-user.md
index 05de6afa..d5edc544 100644
--- a/docs/features/open-terminal/advanced/multi-user.md
+++ b/docs/features/open-terminal/advanced/multi-user.md
@@ -84,7 +84,7 @@ flowchart LR
## Option 2: Per-user containers with Terminals
-For larger deployments or when you need real isolation, [**Terminals**](./terminals/) gives each user their own container, completely separate from everyone else.
+For larger deployments or when you need real isolation, [**Terminals**](../terminals/) gives each user their own container, completely separate from everyone else.
- **Full isolation** — each user's container is independent with its own files, processes, and resources
- **On-demand provisioning** — containers are created when users start a session and cleaned up when idle
@@ -110,8 +110,8 @@ flowchart LR
Two deployment backends are available:
-- **[Docker Backend](./terminals/docker-backend)** — runs on a single Docker host. Best for small-to-medium teams or environments without Kubernetes.
-- **[Kubernetes Operator](./terminals/kubernetes-operator)** — production-grade deployment using a CRD-based operator. Deploys alongside Open WebUI via the Helm chart.
+- **[Docker Backend](../terminals/)** — runs on a single Docker host. Best for small-to-medium teams or environments without Kubernetes.
+- **[Kubernetes Operator](../terminals/)** — production-grade deployment using a CRD-based operator. Deploys alongside Open WebUI via the Helm chart.
:::info Enterprise license required
Terminals requires an [Open WebUI Enterprise License](https://openwebui.com/enterprise). See the [Terminals repository](https://github.com/open-webui/terminals) for license details.
@@ -119,8 +119,8 @@ Terminals requires an [Open WebUI Enterprise License](https://openwebui.com/ente
## Related
-- [Terminals overview →](./terminals/)
-- [Terminals: Docker Backend →](./terminals/docker-backend)
-- [Terminals: Kubernetes Operator →](./terminals/kubernetes-operator)
+- [Terminals overview →](../terminals/)
+- [Terminals: Docker Backend →](../terminals/)
+- [Terminals: Kubernetes Operator →](../terminals/)
- [Security best practices →](./security)
- [All configuration options →](./configuration)
diff --git a/docs/features/open-terminal/advanced/terminals/_category_.json b/docs/features/open-terminal/advanced/terminals/_category_.json
deleted file mode 100644
index 5f50cf5a..00000000
--- a/docs/features/open-terminal/advanced/terminals/_category_.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "position": 1,
- "label": "Terminals",
- "collapsible": true,
- "collapsed": false
-}
diff --git a/docs/features/open-terminal/advanced/terminals/docker-backend.md b/docs/features/open-terminal/advanced/terminals/docker-backend.md
deleted file mode 100644
index 296ee437..00000000
--- a/docs/features/open-terminal/advanced/terminals/docker-backend.md
+++ /dev/null
@@ -1,217 +0,0 @@
----
-sidebar_position: 1
-title: "Docker Backend"
----
-
-# Docker Backend
-
-Terminals with the Docker backend runs on a single Docker host and provisions an isolated [Open Terminal](/features/open-terminal) container for every user. Each person gets their own filesystem, processes, and resource limits — without needing Kubernetes.
-
-```mermaid
-flowchart LR
- OW["Open WebUI"]
-
- subgraph docker ["Docker Host"]
- OR["Orchestrator"]
- CA["Container
User A"]
- CB["Container
User B"]
- OR --> CA
- OR --> CB
- end
-
- OW --> OR
-
- style OW fill:#4a90d9,color:#fff
- style OR fill:#e67e22,color:#fff
- style CA fill:#27ae60,color:#fff
- style CB fill:#27ae60,color:#fff
- style docker fill:#f0f0f0,stroke:#ccc
-```
-
----
-
-## How it works
-
-1. A user opens a terminal in Open WebUI.
-2. Open WebUI proxies the request to the **Terminals orchestrator**.
-3. The orchestrator checks if the user already has a running container.
- - If not, it pulls the Open Terminal image and creates a new container for that user.
- - If the container exists but is stopped, it starts it back up.
-4. Once the container is healthy (responds to `/health`), the orchestrator proxies all traffic to it.
-5. A background cleanup loop tears down containers that have been idle longer than the configured timeout.
-
-On restart, the orchestrator **reconciles** — it rediscovers existing containers by label so no work is lost.
-
----
-
-## Prerequisites
-
-- Docker Engine installed
-- Open WebUI running (or ready to deploy alongside)
-- [Open WebUI Enterprise License](https://openwebui.com/enterprise)
-
----
-
-## Quick start with Docker Compose
-
-This Compose file deploys Open WebUI and the Terminals orchestrator together. The orchestrator manages per-user Open Terminal containers automatically.
-
-```yaml
-services:
- open-webui:
- image: ghcr.io/open-webui/open-webui:main
- ports:
- - "3000:8080"
- environment:
- # Point Open WebUI at the orchestrator.
- # This is auto-detected as an orchestrator connection.
- - TERMINAL_SERVER_CONNECTIONS=[{"id":"terminals","name":"Terminals","enabled":true,"url":"http://terminals:3000","key":"${TERMINALS_API_KEY}","auth_type":"bearer","config":{"access_grants":[{"principal_type":"user","principal_id":"*","permission":"read"}]}}]
- volumes:
- - open-webui:/app/backend/data
- networks:
- - webui
- depends_on:
- - terminals
-
- terminals:
- image: ghcr.io/open-webui/terminals:latest
- environment:
- - TERMINALS_BACKEND=docker
- - TERMINALS_API_KEY=${TERMINALS_API_KEY}
- - TERMINALS_IMAGE=ghcr.io/open-webui/open-terminal:latest
- - TERMINALS_NETWORK=open-webui-network
- - TERMINALS_IDLE_TIMEOUT_MINUTES=30
- volumes:
- # The orchestrator needs Docker socket access to manage containers.
- - /var/run/docker.sock:/var/run/docker.sock
- # Persistent data for terminal workspaces.
- - terminals-data:/app/data
- networks:
- - webui
-
-volumes:
- open-webui:
- terminals-data:
-
-networks:
- webui:
- name: open-webui-network
-```
-
-:::warning Docker socket access
-The orchestrator mounts the Docker socket so it can create and manage containers. This grants broad control over the Docker daemon. In production, consider using a Docker socket proxy like [Tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy) to restrict which API calls are allowed.
-:::
-
-Set the shared API key in a `.env` file next to your Compose file:
-
-```env
-TERMINALS_API_KEY=change-me-to-a-strong-random-value
-```
-
-Then start everything:
-
-```bash
-docker compose up -d
-```
-
-Open WebUI will be available at `http://localhost:3000`. When any user activates a terminal in chat, the orchestrator provisions their personal container automatically.
-
----
-
-## Configuration reference
-
-All orchestrator settings are configured via environment variables prefixed with `TERMINALS_`.
-
-### Core settings
-
-| Variable | Default | Description |
-| :--- | :--- | :--- |
-| `TERMINALS_BACKEND` | `docker` | Backend type. Set to `docker` for this deployment mode. |
-| `TERMINALS_API_KEY` | (empty) | Shared secret for authenticating requests from Open WebUI. Required. |
-| `TERMINALS_IMAGE` | `ghcr.io/open-webui/open-terminal:latest` | Default Open Terminal container image for user instances. |
-| `TERMINALS_PORT` | `3000` | Port the orchestrator listens on. |
-| `TERMINALS_HOST` | `0.0.0.0` | Address the orchestrator binds to. |
-
-### Docker-specific settings
-
-| Variable | Default | Description |
-| :--- | :--- | :--- |
-| `TERMINALS_NETWORK` | (empty) | Docker network to attach user containers to. When set, containers communicate by name instead of published ports. |
-| `TERMINALS_DOCKER_HOST` | `127.0.0.1` | Address used to reach published container ports. Only relevant when `TERMINALS_NETWORK` is not set. |
-| `TERMINALS_DATA_DIR` | `data/terminals` | Host directory where per-user workspace data is stored. Each user gets a subdirectory mounted to `/home/user` inside their container. |
-
-### Lifecycle
-
-| Variable | Default | Description |
-| :--- | :--- | :--- |
-| `TERMINALS_IDLE_TIMEOUT_MINUTES` | `0` (disabled) | Minutes of inactivity before a user's container is stopped and removed. Set to `30` for typical usage. |
-
-### Resource limits
-
-| Variable | Default | Description |
-| :--- | :--- | :--- |
-| `TERMINALS_MAX_CPU` | (empty) | Hard cap on CPU for user containers (e.g., `2`). |
-| `TERMINALS_MAX_MEMORY` | (empty) | Hard cap on memory for user containers (e.g., `4Gi`). |
-
-### Authentication
-
-| Variable | Default | Description |
-| :--- | :--- | :--- |
-| `TERMINALS_OPEN_WEBUI_URL` | (empty) | If set, validates incoming JWTs against this Open WebUI instance instead of using `TERMINALS_API_KEY`. |
-
----
-
-## Policies
-
-The orchestrator supports **policies** — named environment configurations that let you offer different setups to different teams. For example, a `data-science` policy might use a larger image with pre-installed Python packages, while a `development` policy uses the default slim image.
-
-Policies are managed via the orchestrator's REST API (`/api/v1/policies`). Each policy is then referenced by a terminal connection in Open WebUI under **Settings → Connections**.
-
-When a policy is configured, requests are routed through `/p/{policy_id}/` — for example, `/p/data-science/execute`.
-
-👉 **[See the Policies guide for full details →](./policies.md)**
-
----
-
-## Container lifecycle details
-
-### Naming
-
-Containers are named `terminals-{policy_id}-{user_id}`, which makes them easy to identify:
-
-```bash
-docker ps --filter "label=app.kubernetes.io/managed-by=terminals"
-```
-
-### Health checks
-
-After creating a container, the orchestrator polls its `/health` endpoint until it returns HTTP 200 (up to 15 seconds). Only then does it start proxying traffic.
-
-### Reconciliation
-
-If the orchestrator restarts, it rediscovers existing running containers by their labels and recovers their API keys from the container configuration. This prevents duplicate containers from being created.
-
-### Conflict handling
-
-If a container with the same name already exists (e.g., from a previous failed cleanup), the orchestrator force-removes the old container and retries up to 3 times.
-
----
-
-## Limitations
-
-- **Single host** — all user containers run on one Docker host. For high availability or larger teams, use the [Kubernetes Operator](./kubernetes-operator).
-- **No built-in HA** — if the orchestrator goes down, active terminal sessions are interrupted (though containers keep running and are reconciled on restart).
-- **Docker socket required** — the orchestrator needs access to the Docker socket to manage containers.
-
----
-
-## Next steps
-
-- [Kubernetes Operator](./kubernetes-operator) — production-grade deployment with CRD-based lifecycle management
-- [Multi-User Setup](../multi-user) — comparison of isolation approaches
-- [Security best practices](../security)
-- [Configuration reference](../configuration) — all Open Terminal container settings
-
-:::info Enterprise license required
-Terminals requires an [Open WebUI Enterprise License](https://openwebui.com/enterprise). See the [Terminals repository](https://github.com/open-webui/terminals) for license details.
-:::
diff --git a/docs/features/open-terminal/advanced/terminals/index.md b/docs/features/open-terminal/advanced/terminals/index.md
deleted file mode 100644
index 3891a3ce..00000000
--- a/docs/features/open-terminal/advanced/terminals/index.md
+++ /dev/null
@@ -1,117 +0,0 @@
----
-sidebar_position: 0
-title: "Overview"
----
-
-# Terminals
-
-**Terminals** is an enterprise orchestration layer for [Open Terminal](/features/open-terminal) that provisions a fully isolated terminal container for every user. Instead of sharing a single container, each person gets their own — with separate files, processes, resource limits, and network isolation.
-
-```mermaid
-flowchart LR
- OW["Open WebUI"]
- OR["Orchestrator"]
- OW -->|X-User-Id| OR
- OR --> TA["Open Terminal
User A"]
- OR --> TB["Open Terminal
User B"]
- OR --> TC["Open Terminal
User C"]
-
- style OW fill:#4a90d9,color:#fff
- style OR fill:#e67e22,color:#fff
- style TA fill:#27ae60,color:#fff
- style TB fill:#27ae60,color:#fff
- style TC fill:#27ae60,color:#fff
-```
-
----
-
-## Why Terminals?
-
-Open Terminal's [built-in multi-user mode](../multi-user#option-1-built-in-multi-user-mode) works well for small, trusted teams — but everyone shares the same container, CPU, memory, and network. This approach does not scale well beyond a few users, or enable multi-team approaches. Terminals solves these issues by giving each user a dedicated container:
-
-| | Built-in multi-user | Terminals |
-| :--- | :--- | :--- |
-| **Isolation** | Separate files, shared system | Fully separate containers |
-| **Resources** | Shared CPU, memory, network | Per-user CPU, memory, and storage limits |
-| **Provisioning** | Always running | On-demand. Created on first use, cleaned up when idle |
-| **Environments** | One setup for everyone | Multiple policies for different teams |
-| **Infrastructure** | Single container | Docker host or Kubernetes cluster |
-| **Best for** | Small trusted teams | Production, larger teams, untrusted users |
-
----
-
-## How it works
-
-Terminals sits between Open WebUI and the Open Terminal instances:
-
-1. A user activates a terminal in Open WebUI.
-2. Open WebUI proxies the request to the **Terminals orchestrator**.
-3. The orchestrator provisions a personal Open Terminal container for that user (or reconnects to an existing one).
-4. All traffic is proxied through the orchestrator. The user never connects to their container directly.
-5. Idle containers are automatically cleaned up after a configurable timeout. Data optionally persists across sessions.
-
-The orchestrator also exposes the same OpenAPI-based tool interface as Open Terminal, so the AI can execute commands, read files, and run code — all scoped to the requesting user's container.
-
----
-
-## Policies
-
-Policies let you define different terminal environments for different teams or use cases. Each policy can specify:
-
-- **Container image** — use a custom image with pre-installed tools for data science, web development, etc.
-- **Resource limits** — set CPU, memory, and storage caps per policy
-- **Environment variables** — inject API keys, egress filtering rules, or custom configuration into terminal containers
-- **Persistent storage** — choose per-user or shared volumes with configurable size
-- **Idle timeout** — automatically reclaim resources after a period of inactivity
-
-Policies are managed via the orchestrator's REST API. Each policy is then wired to a terminal connection in Open WebUI under **Settings → Connections**, where you can also restrict access by group.
-
-When multiple policies are configured, Open WebUI shows them as separate terminal connections that users (or groups) can be granted access to.
-
-👉 **[Policies deep-dive →](./policies.md)**
-
----
-
-## Deployment options
-
-Terminals supports two deployment backends:
-
-### [Docker Backend](./docker-backend)
-
-Runs on a single Docker host. The orchestrator uses the Docker API to create and manage containers. Best for:
-
-- Small-to-medium teams
-- Environments without Kubernetes
-- Quick evaluation and development
-
-Includes a ready-to-use Docker Compose file. **[Get started →](./docker-backend)**
-
-### [Kubernetes Operator](./kubernetes-operator)
-
-Production-grade deployment using a CRD-based Kopf operator. Deploys alongside Open WebUI via the Helm chart. Best for:
-
-- Production environments
-- Larger teams requiring scalability
-- Organizations already running Kubernetes
-
-Integrates as a subchart of the Open WebUI Helm chart — enable with `terminals.enabled: true`. **[Get started →](./kubernetes-operator)**
-
----
-
-## Authentication
-
-The orchestrator supports three authentication modes:
-
-| Mode | When to use |
-| :--- | :--- |
-| **Open WebUI JWT** | Production. Set `TERMINALS_OPEN_WEBUI_URL` and the orchestrator validates tokens against your Open WebUI instance. |
-| **Shared API key** | Standard. Set `TERMINALS_API_KEY` to a shared secret that Open WebUI includes in requests. |
-| **Open** | Development only. No auth — do not use in production. |
-
-When deployed via Docker Compose or Helm, the shared API key is configured automatically between Open WebUI and the orchestrator.
-
----
-
-## License
-
-Terminals requires an [Open WebUI Enterprise License](https://openwebui.com/enterprise). See the [Terminals repository](https://github.com/open-webui/terminals) for details.
diff --git a/docs/features/open-terminal/advanced/terminals/kubernetes-operator.md b/docs/features/open-terminal/advanced/terminals/kubernetes-operator.md
deleted file mode 100644
index 1f1d4ee5..00000000
--- a/docs/features/open-terminal/advanced/terminals/kubernetes-operator.md
+++ /dev/null
@@ -1,349 +0,0 @@
----
-sidebar_position: 2
-title: "Kubernetes Operator"
----
-
-# Kubernetes Operator
-
-Terminals with the Kubernetes Operator backend is the production-grade deployment for multi-tenant terminals on Kubernetes. A [Kopf](https://kopf.readthedocs.io/)-based operator watches `Terminal` custom resources and manages the full lifecycle of per-user Pods, Services, PVCs, and Secrets.
-
-```mermaid
-flowchart LR
- OW["Open WebUI"]
-
- subgraph k8s ["Kubernetes Cluster"]
- OR["Orchestrator"]
- CR["Terminal CR"]
- OP["Kopf Operator"]
- Resources["Pod + Service +
PVC + Secret"]
- OR -->|creates| CR
- OP -->|watches| CR
- OP -->|provisions| Resources
- OR -->|proxies to| Resources
- end
-
- OW --> OR
-
- style OW fill:#4a90d9,color:#fff
- style OR fill:#e67e22,color:#fff
- style OP fill:#8e44ad,color:#fff
- style CR fill:#f39c12,color:#fff
- style Resources fill:#27ae60,color:#fff
- style k8s fill:#f0f0f0,stroke:#ccc
-```
-
----
-
-## Architecture
-
-The Kubernetes deployment includes three components:
-
-| Component | Role |
-| :--- | :--- |
-| **Orchestrator** | FastAPI service that receives requests from Open WebUI, creates `Terminal` custom resources, and proxies traffic to user Pods once they're running. |
-| **Operator** | Kopf controller that watches `Terminal` CRs and reconciles the underlying infrastructure — creates Pods, Services, Secrets, and PVCs for each terminal. |
-| **Terminal CRD** | A `Terminal` custom resource (`terminals.openwebui.com/v1alpha1`) that declaratively represents a user's terminal instance. |
-
-When a user opens a terminal in Open WebUI:
-
-1. Open WebUI proxies the request to the **orchestrator**.
-2. The orchestrator creates a `Terminal` CR in the cluster.
-3. The **operator** detects the new CR and provisions a Pod, Service, Secret (API key), and optionally a PVC.
-4. Once the Pod passes readiness checks, the operator updates the CR's status to `Running`.
-5. The orchestrator reads the service URL and API key from the CR status, then proxies all traffic.
-6. When the terminal is idle past the configured timeout, the operator deletes the Pod (the PVC and Secret survive so the workspace persists).
-7. On the next request, the orchestrator creates a new CR and the cycle repeats — the existing PVC is reattached.
-
----
-
-## Deployment with Helm
-
-The recommended deployment method is through the **Open WebUI Helm chart**, which includes Terminals as an optional subchart.
-
-### 1. Enable Terminals in your values
-
-Add a `terminals` section to your Helm values file:
-
-```yaml
-# values.yaml
-terminals:
- enabled: true
-
- # Shared API key between Open WebUI, orchestrator, and terminal Pods.
- # If left empty, the chart auto-generates a random key.
- apiKey: ""
-
- # Or reference a pre-existing Secret:
- # existingSecret: "my-terminals-secret" # must contain key: api-key
-
- crd:
- install: true # Set false if the CRD is already installed cluster-wide
-
- operator:
- image:
- repository: ghcr.io/open-webui/terminals-operator
- tag: latest
- resources:
- requests:
- cpu: 50m
- memory: 64Mi
- limits:
- cpu: 200m
- memory: 256Mi
-
- orchestrator:
- image:
- repository: ghcr.io/open-webui/terminals
- tag: latest
- backend: kubernetes-operator
- terminalImage: "ghcr.io/open-webui/open-terminal:latest"
- idleTimeoutMinutes: 30
- service:
- type: ClusterIP
- port: 8080
- resources:
- requests:
- cpu: 50m
- memory: 128Mi
- limits:
- cpu: 200m
- memory: 256Mi
-```
-
-### 2. Install or upgrade
-
-```bash
-helm upgrade --install open-webui open-webui/open-webui \
- -f values.yaml \
- --namespace open-webui --create-namespace
-```
-
-:::tip Auto-configured connection
-When `terminals.enabled` is `true`, the Open WebUI chart automatically sets the `TERMINAL_SERVER_CONNECTIONS` environment variable to point at the orchestrator's in-cluster service. No manual connection setup is needed — terminals appear in the UI immediately.
-:::
-
-### 3. Verify
-
-```bash
-# Check that all pods are running
-kubectl get pods -n open-webui -l app.kubernetes.io/part-of=open-terminal
-
-# Check that the CRD is installed
-kubectl get crd terminals.openwebui.com
-```
-
----
-
-## What gets deployed
-
-When `terminals.enabled: true`, the Helm chart creates the following Kubernetes resources:
-
-| Resource | Name | Purpose |
-| :--- | :--- | :--- |
-| **CustomResourceDefinition** | `terminals.openwebui.com` | Defines the `Terminal` CR schema |
-| **Deployment** | `{release}-terminals-operator` | Runs the Kopf operator |
-| **Deployment** | `{release}-terminals-orchestrator` | Runs the FastAPI orchestrator |
-| **Service** | `{release}-terminals-orchestrator` | Exposes the orchestrator to Open WebUI |
-| **ServiceAccount** | `{release}-terminals-operator` | Identity for the operator |
-| **ClusterRole / ClusterRoleBinding** | `{release}-terminals-operator` | RBAC permissions for the operator |
-| **Secret** | `{release}-terminals-api-key` | Shared API key (auto-generated if not provided) |
-
-Additionally, for **each user terminal** the operator creates:
-
-| Resource | Name | Purpose |
-| :--- | :--- | :--- |
-| **Pod** | `{terminal-name}-pod` | The user's Open Terminal instance |
-| **Service** | `{terminal-name}-svc` | ClusterIP service for the Pod |
-| **Secret** | `{terminal-name}-apikey` | Per-terminal API key |
-| **PersistentVolumeClaim** | `{terminal-name}-pvc` | Workspace storage (if persistence is enabled). Intentionally survives terminal deletion so data is retained. |
-
----
-
-## Terminal CRD reference
-
-The `Terminal` custom resource is the declarative API for managing terminal instances.
-
-### Spec fields
-
-| Field | Type | Default | Description |
-| :--- | :--- | :--- | :--- |
-| `userId` | string | *(required)* | Open WebUI user ID that owns this terminal |
-| `image` | string | `ghcr.io/open-webui/open-terminal:latest` | Container image for the terminal Pod |
-| `resources.requests.cpu` | string | `100m` | CPU request |
-| `resources.requests.memory` | string | `256Mi` | Memory request |
-| `resources.limits.cpu` | string | `1` | CPU limit |
-| `resources.limits.memory` | string | `1Gi` | Memory limit |
-| `idleTimeoutMinutes` | integer | `30` | Minutes of inactivity before the Pod is stopped |
-| `packages` | array | `[]` | Apt packages to pre-install in the terminal |
-| `pipPackages` | array | `[]` | Pip packages to pre-install in the terminal |
-| `persistence.enabled` | boolean | `true` | Enable persistent storage via PVC |
-| `persistence.size` | string | `1Gi` | PVC size |
-| `persistence.storageClass` | string | *(cluster default)* | Storage class for the PVC |
-
-### Status fields
-
-| Field | Description |
-| :--- | :--- |
-| `phase` | Current lifecycle phase: `Pending`, `Provisioning`, `Running`, `Idle`, `Error` |
-| `podName` | Name of the terminal Pod |
-| `serviceName` | Name of the terminal Service |
-| `serviceUrl` | Full in-cluster URL (e.g., `http://terminal-abc123-svc.terminals.svc:8000`) |
-| `apiKeySecret` | Name of the Secret holding the terminal's API key |
-| `lastActivityAt` | ISO 8601 timestamp of the last proxied request |
-| `message` | Human-readable status message |
-| `conditions` | Array of condition objects (`Ready`, with reason and message) |
-
-### Printer columns
-
-```bash
-kubectl get terminals -n open-webui -o wide
-```
-
-```
-NAME USER PHASE SERVICE URL LAST ACTIVITY AGE
-terminal-a1b2c3-default user-123 Running http://terminal-a1b2c3-default-svc.open-webui.svc:8000 5m 5m
-terminal-d4e5f6-datascience user-456 Idle http://terminal-d4e5f6-datascience-svc.open-webui.svc:8000 1h 1h
-```
-
----
-
-## Lifecycle
-
-### Provisioning
-
-```
-User request → Orchestrator creates Terminal CR
- ↓
- Operator detects new CR
- ↓
- Creates Secret (API key)
- Creates Service (ClusterIP)
- Creates PVC (if persistence enabled)
- Creates Pod (with probes, resource limits, env vars)
- ↓
- Pod passes readiness check
- ↓
- Operator sets status.phase = Running
- ↓
- Orchestrator reads serviceUrl + apiKeySecret
- Proxies traffic to Pod
-```
-
-### Idle cleanup
-
-The operator checks terminal activity every 60 seconds. If a terminal has been idle longer than `spec.idleTimeoutMinutes`:
-
-1. The operator **deletes the Pod** (not the CR, PVC, or Secret).
-2. The status is set to `phase: Idle`.
-3. On the next user request, the orchestrator creates a new Terminal CR, and the operator provisions a fresh Pod with the same PVC reattached.
-
-This means **user data persists** across idle cycles while cluster resources are reclaimed.
-
-### Manual management
-
-```bash
-# List all terminals
-kubectl get terminals -n open-webui
-
-# Inspect a specific terminal
-kubectl describe terminal terminal-a1b2c3-default -n open-webui
-
-# Delete a terminal (Pod, Service, and Secret are garbage-collected via ownerReferences)
-kubectl delete terminal terminal-a1b2c3-default -n open-webui
-```
-
----
-
-## Helm values reference
-
-All configurable values under the `terminals` key:
-
-### Top-level
-
-| Key | Default | Description |
-| :--- | :--- | :--- |
-| `terminals.enabled` | `false` | Enable the Terminals subchart |
-| `terminals.apiKey` | (empty) | Shared API key. Auto-generated if not set. |
-| `terminals.existingSecret` | (empty) | Name of a pre-existing Secret containing the API key (key: `api-key`) |
-| `terminals.crd.install` | `true` | Install the Terminal CRD. Set `false` if already installed cluster-wide. |
-
-### Operator
-
-| Key | Default | Description |
-| :--- | :--- | :--- |
-| `terminals.operator.image.repository` | `ghcr.io/open-webui/terminals-operator` | Operator container image |
-| `terminals.operator.image.tag` | `latest` | Image tag |
-| `terminals.operator.image.pullPolicy` | `IfNotPresent` | Pull policy |
-| `terminals.operator.replicaCount` | `1` | Number of operator replicas |
-| `terminals.operator.resources` | 50m/64Mi → 200m/256Mi | CPU and memory requests/limits |
-
-### Orchestrator
-
-| Key | Default | Description |
-| :--- | :--- | :--- |
-| `terminals.orchestrator.image.repository` | `ghcr.io/open-webui/terminals` | Orchestrator container image |
-| `terminals.orchestrator.image.tag` | `latest` | Image tag |
-| `terminals.orchestrator.image.pullPolicy` | `IfNotPresent` | Pull policy |
-| `terminals.orchestrator.replicaCount` | `1` | Number of orchestrator replicas |
-| `terminals.orchestrator.backend` | `kubernetes-operator` | Backend type |
-| `terminals.orchestrator.terminalImage` | `ghcr.io/open-webui/open-terminal:latest` | Default image for spawned terminal Pods |
-| `terminals.orchestrator.terminalImagePullPolicy` | `IfNotPresent` | Pull policy for terminal Pods |
-| `terminals.orchestrator.idleTimeoutMinutes` | `30` | Idle timeout for spawned terminals (minutes) |
-| `terminals.orchestrator.service.type` | `ClusterIP` | Orchestrator Service type |
-| `terminals.orchestrator.service.port` | `8080` | Orchestrator Service port |
-| `terminals.orchestrator.resources` | 50m/128Mi → 200m/256Mi | CPU and memory requests/limits |
-
----
-
-## RBAC requirements
-
-If you're not using the Helm chart, the operator's ServiceAccount needs a ClusterRole with these permissions:
-
-| Resource | Verbs |
-| :--- | :--- |
-| `terminals.openwebui.com` (CRD instances) | get, list, watch, create, update, patch, delete |
-| `pods`, `services`, `persistentvolumeclaims`, `secrets` | get, list, watch, create, update, patch, delete |
-| `events` | create |
-| `configmaps`, `leases` | get, list, watch, create, update, patch *(Kopf leader election)* |
-
----
-
-## Monitoring
-
-### Terminal status
-
-```bash
-# Overview of all terminals
-kubectl get terminals -n open-webui
-
-# Detailed status with conditions
-kubectl describe terminal -n open-webui
-
-# Watch for changes
-kubectl get terminals -n open-webui -w
-```
-
-### Operator logs
-
-```bash
-kubectl logs -n open-webui deployment/-terminals-operator --tail=50
-```
-
-### Orchestrator logs
-
-```bash
-kubectl logs -n open-webui deployment/-terminals-orchestrator --tail=50
-```
-
----
-
-## Next steps
-
-- [Docker Backend](./docker-backend) — simpler single-host deployment without Kubernetes
-- [Multi-User Setup](../multi-user) — comparison of isolation approaches
-- [Security best practices](../security)
-- [Configuration reference](../configuration) — all Open Terminal container settings
-
-:::info Enterprise license required
-Terminals requires an [Open WebUI Enterprise License](https://openwebui.com/enterprise). See the [Terminals repository](https://github.com/open-webui/terminals) for license details.
-:::
diff --git a/docs/features/open-terminal/index.md b/docs/features/open-terminal/index.md
index 2d1e49d1..929f9d95 100644
--- a/docs/features/open-terminal/index.md
+++ b/docs/features/open-terminal/index.md
@@ -83,4 +83,4 @@ Open Terminal requires models with **native function calling** support. Frontier
## Enterprise Multi-User
-Need isolated, per-user terminal containers for your team? **[Terminals](./advanced/terminals/)** provisions a dedicated Open Terminal instance for every user with automatic lifecycle management, resource controls, and policy-based environments.
+Need isolated, per-user terminal containers for your team? **[Terminals](./terminals/)** provisions a dedicated Open Terminal instance for every user with automatic lifecycle management, resource controls, and policy-based environments.
diff --git a/docs/features/open-terminal/terminals/_category_.json b/docs/features/open-terminal/terminals/_category_.json
new file mode 100644
index 00000000..f576e687
--- /dev/null
+++ b/docs/features/open-terminal/terminals/_category_.json
@@ -0,0 +1,6 @@
+{
+ "position": 15,
+ "label": "Terminals (Orchestrator)",
+ "collapsible": true,
+ "collapsed": true
+}
diff --git a/docs/features/open-terminal/terminals/index.mdx b/docs/features/open-terminal/terminals/index.mdx
new file mode 100644
index 00000000..fc0c8d53
--- /dev/null
+++ b/docs/features/open-terminal/terminals/index.mdx
@@ -0,0 +1,118 @@
+---
+sidebar_position: 0
+title: "Terminals (Orchestrator)"
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+import Docker from './tab-deployment/Docker.md';
+import Kubernetes from './tab-deployment/Kubernetes.md';
+
+# Terminals (Orchestrator)
+
+**Terminals** is an enterprise orchestration layer for [Open Terminal](/features/open-terminal) that provisions a fully isolated terminal container for every user. Instead of sharing a single container, each person gets their own, complete with separate files, processes, resource limits, and network isolation.
+
+:::tip Quick navigation
+- **Need different environments per team?** → [Policies guide](./policies)
+:::
+
+```mermaid
+flowchart LR
+ OW["Open WebUI"]
+ OR["Orchestrator"]
+ OW -->|X-User-Id| OR
+ OR --> TA["Open Terminal
User A"]
+ OR --> TB["Open Terminal
User B"]
+ OR --> TC["Open Terminal
User C"]
+
+ style OW fill:#4a90d9,color:#fff
+ style OR fill:#e67e22,color:#fff
+ style TA fill:#27ae60,color:#fff
+ style TB fill:#27ae60,color:#fff
+ style TC fill:#27ae60,color:#fff
+```
+
+---
+
+## How it works
+
+The orchestrator sits between Open WebUI and the Open Terminal instances:
+
+1. A user activates a terminal in Open WebUI.
+2. Open WebUI proxies the request to the **orchestrator**, a service that manages the lifecycle of terminal containers.
+3. The orchestrator provisions a personal Open Terminal container for that user (or reconnects to an existing one).
+4. All traffic is proxied through the orchestrator. The user never connects to their container directly.
+5. Idle containers are automatically cleaned up after a configurable timeout. Data optionally persists across sessions.
+
+The orchestrator also exposes the same OpenAPI-based tool interface as Open Terminal, so the AI can execute commands, read files, and run code, all scoped to the requesting user's container.
+
+---
+
+## Deployment
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+## Authentication
+
+The orchestrator supports three authentication modes:
+
+| Mode | When to use | How to configure |
+| :--- | :--- | :--- |
+| **Open WebUI JWT** | Production. The orchestrator validates tokens against your Open WebUI instance. | Set `TERMINALS_OPEN_WEBUI_URL` on the orchestrator to your Open WebUI URL. |
+| **Shared API key** | Standard. Open WebUI includes a shared secret in every request. | Set `TERMINALS_API_KEY` to the same value on both Open WebUI and the orchestrator. |
+| **Open** | Development only. No authentication. Do not use in production. | Leave both `TERMINALS_OPEN_WEBUI_URL` and `TERMINALS_API_KEY` unset. |
+
+When deployed via Docker Compose or Helm, the shared API key is configured automatically between Open WebUI and the orchestrator.
+
+---
+
+## Troubleshooting
+
+### Terminal won't start
+
+1. **Check orchestrator logs.** The orchestrator logs the full provisioning flow, including image pull and container creation. Look for errors related to image availability or resource limits.
+2. **Verify the API key.** Ensure `TERMINALS_API_KEY` matches between Open WebUI and the orchestrator. A mismatch causes silent auth failures.
+3. **Check image pull access.** If using a private container registry, make sure the orchestrator (Docker) or cluster (Kubernetes) has pull credentials configured.
+
+### Authentication failures
+
+- If using **JWT mode**, confirm `TERMINALS_OPEN_WEBUI_URL` points to a reachable Open WebUI instance.
+- If using **API key mode**, confirm the key is set identically on both sides. Check for extra whitespace or newlines.
+- Check the orchestrator logs for `401` or `403` responses.
+
+### Container is reaped too quickly
+
+Increase `TERMINALS_IDLE_TIMEOUT_MINUTES` (or `idle_timeout_minutes` in a policy). The default is `0` (disabled), but if set too low, containers may be cleaned up while users are still working. A value of `30` is typical.
+
+### Connection refused
+
+- **Docker:** ensure `TERMINALS_NETWORK` is set so containers can communicate by name. Without it, containers use published ports and the `TERMINALS_DOCKER_HOST` address must be reachable.
+- **Kubernetes:** verify the orchestrator Service is accessible from the Open WebUI Pod. Run `kubectl get svc -n open-webui` to confirm the service exists.
+
+---
+
+## Further reading
+
+- [Multi-User Setup](../advanced/multi-user): comparison of isolation approaches
+- [Security best practices](../advanced/security)
+- [Configuration reference](../advanced/configuration): all Open Terminal container settings
+
+---
+
+## License
+
+Terminals requires an [Open WebUI Enterprise License](https://openwebui.com/enterprise) for production use. See the [Terminals repository](https://github.com/open-webui/terminals) for details.
diff --git a/docs/features/open-terminal/advanced/terminals/policies.md b/docs/features/open-terminal/terminals/policies.md
similarity index 64%
rename from docs/features/open-terminal/advanced/terminals/policies.md
rename to docs/features/open-terminal/terminals/policies.md
index 6d85eaee..c4ac4a58 100644
--- a/docs/features/open-terminal/advanced/terminals/policies.md
+++ b/docs/features/open-terminal/terminals/policies.md
@@ -5,7 +5,7 @@ title: "Policies"
# Policies
-Policies are named environment profiles that define what a terminal container looks like — its image, resource limits, storage, environment variables, and idle timeout. They let you offer different terminal environments to different teams from a single orchestrator.
+Policies are named environment profiles that define what a terminal container looks like: its image, resource limits, storage, environment variables, and idle timeout. They let you offer different terminal environments to different teams from a single orchestrator.
For example, you might create a `data-science` policy with a large image, 4 CPU cores, and 16 GiB of memory, while a `development` policy uses the default slim image with 1 CPU and 2 GiB.
@@ -24,7 +24,7 @@ flowchart LR
1. **Admin creates policies** on the orchestrator via its REST API (see [API reference](#api-reference) below).
2. **Admin creates terminal connections** in Open WebUI under **Settings → Connections → Open Terminal**. Each connection includes a `policy_id` field that maps it to a policy on the orchestrator.
-3. **Users open a terminal** — Open WebUI routes the request through `/p/{policy_id}/...`, and the orchestrator provisions (or reuses) a container matching that policy's spec.
+3. **Users open a terminal.** Open WebUI routes the request through `/p/{policy_id}/...`, and the orchestrator provisions (or reuses) a container matching that policy's spec.
Each user gets their own isolated container per policy. If a user has access to two connections with different policies, they get two independent terminals.
@@ -52,15 +52,17 @@ The `storage_mode` field controls how persistent volumes are allocated on Kubern
| :--- | :--- | :--- |
| `per-user` | Each user gets their own PVC. Full isolation. | ReadWriteOnce |
| `shared` | A single PVC is shared by all users, with each user's data in a `subPath` under their user ID. Requires a storage class that supports ReadWriteMany (e.g., NFS, EFS). | ReadWriteMany |
-| `shared-rwo` | A single ReadWriteOnce PVC is shared. All terminal pods are scheduled to the same node via pod affinity. Useful when RWX storage is unavailable. | ReadWriteOnce |
+| `shared-rwo` | A single ReadWriteOnce PVC is shared. All terminal pods are scheduled to the same node via pod affinity (Kubernetes ensures they all land on the machine that has the volume mounted). Useful when ReadWriteMany storage is unavailable. | ReadWriteOnce |
+
+**ReadWriteOnce (RWO)** means the volume can only be mounted by pods on a single node at a time. **ReadWriteMany (RWX)** means multiple nodes can mount and write to the volume simultaneously.
### Environment variables
The `env` field injects arbitrary key-value pairs as environment variables in the terminal container. Common uses:
-- **API keys** — give users access to LLM APIs, cloud services, etc.
-- **Egress filtering** — set `OPEN_TERMINAL_ALLOWED_DOMAINS` to restrict outbound network access (e.g., `"*.pypi.org,github.com"`). When this variable is present, the Docker backend automatically adds the `NET_ADMIN` capability to the container.
-- **Custom configuration** — any setting your terminal image supports
+- **API keys:** give users access to LLM APIs, cloud services, etc.
+- **Egress filtering:** set `OPEN_TERMINAL_ALLOWED_DOMAINS` to restrict outbound network access (e.g., `"*.pypi.org,github.com"`). When this variable is present, the Docker backend automatically adds the `NET_ADMIN` capability to the container.
+- **Custom configuration:** any setting your terminal image supports
:::warning
Environment variables in a policy are visible to the terminal user (they can run `env` in the shell). Do not store secrets here that users should not see.
@@ -70,65 +72,12 @@ Environment variables in a policy are visible to the terminal user (they can run
## Managing policies
-Policies are managed via the orchestrator's REST API. All endpoints require authentication with the orchestrator's API key.
-
-### Create a policy
-
-```bash
-curl -X PUT http://localhost:3000/api/v1/policies/data-science \
- -H "Authorization: Bearer $TERMINALS_API_KEY" \
- -H "Content-Type: application/json" \
- -d '{
- "image": "ghcr.io/open-webui/open-terminal:latest",
- "cpu_limit": "4",
- "memory_limit": "16Gi",
- "storage": "20Gi",
- "env": {
- "OPEN_TERMINAL_ALLOWED_DOMAINS": "*.pypi.org,github.com,huggingface.co"
- },
- "idle_timeout_minutes": 60
- }'
-```
-
-### List policies
-
-```bash
-curl http://localhost:3000/api/v1/policies \
- -H "Authorization: Bearer $TERMINALS_API_KEY"
-```
-
-### Get a single policy
-
-```bash
-curl http://localhost:3000/api/v1/policies/data-science \
- -H "Authorization: Bearer $TERMINALS_API_KEY"
-```
-
-### Update a policy
-
-Use `PUT` to upsert — it creates the policy if it doesn't exist:
-
-```bash
-curl -X PUT http://localhost:3000/api/v1/policies/data-science \
- -H "Authorization: Bearer $TERMINALS_API_KEY" \
- -H "Content-Type: application/json" \
- -d '{
- "cpu_limit": "8",
- "memory_limit": "32Gi"
- }'
-```
+Policies are managed from the Open WebUI admin panel under **Settings → Connections → Open Terminal**. From there you can create, edit, and delete policies, assign them to terminal connections, and restrict access by group.
:::info
Updating a policy does not affect running terminals. The new spec applies the next time a container is provisioned for that policy (e.g., after the old one is reaped by idle timeout or manually deleted).
:::
-### Delete a policy
-
-```bash
-curl -X DELETE http://localhost:3000/api/v1/policies/data-science \
- -H "Authorization: Bearer $TERMINALS_API_KEY"
-```
-
---
## Connecting policies to Open WebUI
@@ -180,7 +129,7 @@ In this example, the engineering group sees only the `development` terminal, whi
---
-## Hard caps
+## Global Resource Limits
Administrators can set global limits on the orchestrator that **clamp** policy values, preventing any policy from exceeding the allowed maximums. These are set as environment variables on the orchestrator itself:
@@ -191,17 +140,17 @@ Administrators can set global limits on the orchestrator that **clamp** policy v
| `TERMINALS_MAX_STORAGE` | `100Gi` | Maximum persistent storage any policy can request |
| `TERMINALS_ALLOWED_IMAGES` | `ghcr.io/open-webui/*,gcr.io/my-org/*` | Comma-separated glob patterns. If set, a policy's `image` must match at least one pattern or the request is rejected with HTTP 400. |
-Hard caps are enforced at policy creation and update time. If a policy's `cpu_limit` is `"16"` but `TERMINALS_MAX_CPU` is `"8"`, the stored value is clamped to `"8"`.
+These limits are enforced at policy creation and update time. If a policy's `cpu_limit` is `"16"` but `TERMINALS_MAX_CPU` is `"8"`, the stored value is silently clamped to `"8"`.
:::tip
-Hard caps give platform administrators a safety net — they can delegate policy creation to team leads while ensuring no single policy can consume an unreasonable amount of cluster resources.
+Global resource limits give platform administrators a safety net. They can delegate policy creation to team leads while ensuring no single policy can consume an unreasonable amount of cluster resources.
:::
---
## The "default" policy
-When a terminal connection in Open WebUI has no `policy_id` set (or the orchestrator receives a request without a `/p/` prefix), the orchestrator uses its **global settings** as the effective policy:
+If you haven't created any policies, you don't need to. The orchestrator works out of the box using its global environment variables as the effective default:
| Setting | Environment variable |
| :--- | :--- |
@@ -209,25 +158,26 @@ When a terminal connection in Open WebUI has no `policy_id` set (or the orchestr
| Idle timeout | `TERMINALS_IDLE_TIMEOUT_MINUTES` |
| Storage mode | `TERMINALS_KUBERNETES_STORAGE_MODE` |
-This is equivalent to a single-policy deployment. No database entry is needed.
+This zero-config fallback applies when a terminal connection in Open WebUI has no `policy_id` set (or the orchestrator receives a request without a `/p/` prefix). No database entry is needed; it's equivalent to a single-policy deployment.
-If you create a policy named `default` in the database, its fields are merged with the global settings (policy values take precedence).
+If you later create a policy named `default` in the database, its fields are merged with the global settings (policy values take precedence).
---
-## API reference
+
+API reference (for programmatic access)
All endpoints are prefixed with `/api/v1` on the orchestrator and require the `Authorization: Bearer {TERMINALS_API_KEY}` header.
| Method | Endpoint | Description |
| :--- | :--- | :--- |
| `GET` | `/policies` | List all policies |
-| `POST` | `/policies` | Create a new policy (body: `{ "id": "...", "data": { ... } }`) — returns 409 if it already exists |
+| `POST` | `/policies` | Create a new policy (body: `{ "id": "...", "data": { ... } }`). Returns 409 if it already exists. |
| `GET` | `/policies/{policy_id}` | Get a single policy |
| `PUT` | `/policies/{policy_id}` | Create or update a policy (body: `PolicyData` fields) |
| `DELETE` | `/policies/{policy_id}` | Delete a policy |
-### Request body — PolicyData
+### Request body: PolicyData
```json
{
@@ -243,7 +193,7 @@ All endpoints are prefixed with `/api/v1` on the orchestrator and require the `A
All fields are optional. Omitted fields inherit from the orchestrator's global defaults.
-### Response body — PolicyResponse
+### Response body: PolicyResponse
```json
{
@@ -261,32 +211,25 @@ All fields are optional. Omitted fields inherit from the orchestrator's global d
}
```
+
+
---
## Example: multi-team setup
-A company with three teams — Engineering, Data Science, and Interns — wants different terminal environments:
+A company with three teams (Engineering, Data Science, and Interns) wants different terminal environments.
-### 1. Create the policies
+### 1. Create policies in Open WebUI
-```bash
-# Engineering: default image, moderate resources, persistent storage
-curl -X PUT http://orchestrator:3000/api/v1/policies/engineering \
- -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
- -d '{"cpu_limit":"2","memory_limit":"4Gi","storage":"10Gi","idle_timeout_minutes":120}'
+In the admin panel under **Settings → Connections → Open Terminal**, create three policies:
-# Data Science: custom image, high resources, large storage
-curl -X PUT http://orchestrator:3000/api/v1/policies/data-science \
- -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
- -d '{"image":"ghcr.io/open-webui/open-terminal:latest","cpu_limit":"8","memory_limit":"32Gi","storage":"50Gi","env":{"OPEN_TERMINAL_ALLOWED_DOMAINS":"*.pypi.org,huggingface.co"}}'
+| Policy | Image | CPU | Memory | Storage | Idle timeout |
+| :--- | :--- | :--- | :--- | :--- | :--- |
+| `engineering` | Default | 2 | 4Gi | 10Gi | 120 min |
+| `data-science` | Custom data science image | 8 | 32Gi | 50Gi | 60 min |
+| `intern` | Default | 1 | 1Gi | None | 15 min |
-# Interns: limited resources, no persistent storage, short idle timeout
-curl -X PUT http://orchestrator:3000/api/v1/policies/intern \
- -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
- -d '{"cpu_limit":"1","memory_limit":"1Gi","idle_timeout_minutes":15}'
-```
-
-### 2. Create terminal connections in Open WebUI
+### 2. Create terminal connections
Add three connections, each pointing to the same orchestrator URL but with different `policy_id` values: `engineering`, `data-science`, and `intern`.
diff --git a/docs/features/open-terminal/terminals/tab-deployment/Docker.md b/docs/features/open-terminal/terminals/tab-deployment/Docker.md
new file mode 100644
index 00000000..fd1bdf71
--- /dev/null
+++ b/docs/features/open-terminal/terminals/tab-deployment/Docker.md
@@ -0,0 +1,125 @@
+## Prerequisites
+
+- [Docker Engine](https://docs.docker.com/engine/install/) installed and running
+- Open WebUI running (or ready to deploy alongside)
+- [Open WebUI Enterprise License](https://openwebui.com/enterprise) (required for production use)
+
+## Quick start with Docker Compose
+
+This Compose file deploys Open WebUI and the Terminals orchestrator together.
+
+```yaml
+services:
+ open-webui:
+ image: ghcr.io/open-webui/open-webui:main
+ ports:
+ - "3000:8080"
+ environment:
+ - >-
+ TERMINAL_SERVER_CONNECTIONS=[{
+ "id": "terminals",
+ "name": "Terminals",
+ "enabled": true,
+ "url": "http://terminals:3000",
+ "key": "${TERMINALS_API_KEY}",
+ "auth_type": "bearer",
+ "config": {
+ "access_grants": [{
+ "principal_type": "user",
+ "principal_id": "*",
+ "permission": "read"
+ }]
+ }
+ }]
+ volumes:
+ - open-webui:/app/backend/data
+ networks:
+ - webui
+ depends_on:
+ - terminals
+
+ terminals:
+ image: ghcr.io/open-webui/terminals:latest
+ environment:
+ - TERMINALS_BACKEND=docker
+ - TERMINALS_API_KEY=${TERMINALS_API_KEY}
+ - TERMINALS_IMAGE=ghcr.io/open-webui/open-terminal:latest
+ - TERMINALS_NETWORK=open-webui-network
+ - TERMINALS_IDLE_TIMEOUT_MINUTES=30
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - terminals-data:/app/data
+ networks:
+ - webui
+
+volumes:
+ open-webui:
+ terminals-data:
+
+networks:
+ webui:
+ name: open-webui-network
+```
+
+Set the shared API key in a `.env` file next to your Compose file:
+
+```env
+TERMINALS_API_KEY=change-me-to-a-strong-random-value
+```
+
+Then start everything:
+
+```bash
+docker compose up -d
+```
+
+Open WebUI will be available at `http://localhost:3000`. When any user activates a terminal, the orchestrator provisions their personal container automatically.
+
+:::warning Docker socket access
+The orchestrator needs access to the Docker socket (`/var/run/docker.sock`) to manage containers. For production, use a Docker socket proxy like [Tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy) to restrict the API calls it can make.
+:::
+
+---
+
+
+Configuration reference
+
+| Variable | Default | Description |
+| :--- | :--- | :--- |
+| `TERMINALS_BACKEND` | `docker` | Backend type. Set to `docker` for this deployment mode. |
+| `TERMINALS_API_KEY` | (empty) | Shared secret for authenticating requests from Open WebUI. Required. |
+| `TERMINALS_IMAGE` | `ghcr.io/open-webui/open-terminal:latest` | Default container image for user terminals. |
+| `TERMINALS_PORT` | `3000` | Port the orchestrator listens on. |
+| `TERMINALS_HOST` | `0.0.0.0` | Address the orchestrator binds to. |
+| `TERMINALS_NETWORK` | (empty) | Docker network for user containers. When set, containers communicate by name. |
+| `TERMINALS_DOCKER_HOST` | `127.0.0.1` | Address for published container ports. Only relevant without `TERMINALS_NETWORK`. |
+| `TERMINALS_DATA_DIR` | `data/terminals` | Host directory for per-user workspace data. |
+| `TERMINALS_IDLE_TIMEOUT_MINUTES` | `0` (disabled) | Minutes of inactivity before a container is stopped. Set to `30` for typical usage. |
+| `TERMINALS_MAX_CPU` | (empty) | CPU limit for user containers (e.g., `2`). |
+| `TERMINALS_MAX_MEMORY` | (empty) | Memory limit for user containers (e.g., `4Gi`). |
+| `TERMINALS_OPEN_WEBUI_URL` | (empty) | If set, validates incoming JWTs against this Open WebUI instance instead of using `TERMINALS_API_KEY`. |
+
+
+
+---
+
+
+Container lifecycle details
+
+**Naming.** Containers are named `terminals-{policy_id}-{user_id}`, making them easy to filter with `docker ps --filter "label=managed-by=terminals"`.
+
+**Health checks.** After creating a container, the orchestrator polls its `/health` endpoint until it returns HTTP 200 (up to 15 seconds). Only then does it start proxying traffic.
+
+**Reconciliation.** If the orchestrator restarts, it rediscovers existing running containers by their labels and recovers their API keys from the container configuration. This prevents duplicate containers from being created.
+
+**Conflict handling.** If a container with the same name already exists (e.g., from a previous failed cleanup), the orchestrator force-removes the old container and retries up to 3 times.
+
+
+
+---
+
+## Limitations
+
+- **Single host.** All user containers run on one Docker host. For high availability or larger teams, use the Kubernetes Operator backend.
+- **No built-in HA.** If the orchestrator goes down, active terminal sessions are interrupted (though containers keep running and are reconciled on restart).
+- **Docker socket required.** The orchestrator needs access to the Docker socket to manage containers.
diff --git a/docs/features/open-terminal/terminals/tab-deployment/Kubernetes.md b/docs/features/open-terminal/terminals/tab-deployment/Kubernetes.md
new file mode 100644
index 00000000..e00a61e5
--- /dev/null
+++ b/docs/features/open-terminal/terminals/tab-deployment/Kubernetes.md
@@ -0,0 +1,171 @@
+## Prerequisites
+
+- A running Kubernetes cluster (v1.24+)
+- [Helm](https://helm.sh/docs/intro/install/) v3 installed
+- `kubectl` configured to access your cluster
+- [Open WebUI Enterprise License](https://openwebui.com/enterprise) (required for production use)
+
+## Deploy with Helm
+
+The Open WebUI Helm chart includes Terminals as an optional subchart. Add a `terminals` section to your values file:
+
+```yaml
+# values.yaml
+terminals:
+ enabled: true
+ apiKey: "" # Auto-generated if left empty
+
+ crd:
+ install: true
+
+ operator:
+ image:
+ repository: ghcr.io/open-webui/terminals-operator
+ tag: latest
+
+ orchestrator:
+ image:
+ repository: ghcr.io/open-webui/terminals
+ tag: latest
+ backend: kubernetes-operator
+ terminalImage: "ghcr.io/open-webui/open-terminal:latest"
+ idleTimeoutMinutes: 30
+```
+
+Then install or upgrade:
+
+```bash
+helm upgrade --install open-webui open-webui/open-webui \
+ -f values.yaml \
+ --namespace open-webui --create-namespace
+```
+
+:::tip Auto-configured connection
+When `terminals.enabled` is `true`, the chart automatically sets `TERMINAL_SERVER_CONNECTIONS` to point at the in-cluster orchestrator. No manual connection setup is needed.
+:::
+
+### Verify
+
+```bash
+# Check that all pods are running
+kubectl get pods -n open-webui -l app.kubernetes.io/part-of=open-terminal
+
+# Check that the CRD is installed
+kubectl get crd terminals.openwebui.com
+```
+
+---
+
+## What gets deployed
+
+When `terminals.enabled: true`, the chart creates:
+
+| Resource | Purpose |
+| :--- | :--- |
+| **CRD** (`terminals.openwebui.com`) | Defines the `Terminal` custom resource |
+| **Operator Deployment** | Kopf controller that watches Terminal CRs and provisions Pods, Services, PVCs, Secrets |
+| **Orchestrator Deployment + Service** | FastAPI service that receives requests from Open WebUI and proxies to user Pods |
+| **Secret** | Shared API key (auto-generated if not provided) |
+
+For **each user terminal**, the operator creates a Pod, Service, Secret (API key), and optionally a PVC for persistent storage.
+
+---
+
+## Lifecycle
+
+When a user activates a terminal, the orchestrator creates a `Terminal` CR. The operator provisions a Pod with a Service, Secret, and optional PVC. Once the Pod passes readiness checks, the orchestrator proxies traffic to it.
+
+When a terminal has been idle longer than `idleTimeoutMinutes`, the operator deletes the Pod but keeps the PVC and Secret. On the next request, a fresh Pod is created with the same PVC reattached, so **user data persists** across idle cycles.
+
+```bash
+# List all terminals
+kubectl get terminals -n open-webui
+
+# Inspect a specific terminal
+kubectl describe terminal -n open-webui
+
+# Delete a terminal (child resources are garbage-collected automatically)
+kubectl delete terminal -n open-webui
+```
+
+---
+
+## Monitoring
+
+```bash
+# Operator logs
+kubectl logs -n open-webui deployment/-terminals-operator --tail=50
+
+# Orchestrator logs
+kubectl logs -n open-webui deployment/-terminals-orchestrator --tail=50
+```
+
+---
+
+
+Terminal CRD reference
+
+### Spec fields
+
+| Field | Type | Default | Description |
+| :--- | :--- | :--- | :--- |
+| `userId` | string | *(required)* | Open WebUI user ID |
+| `image` | string | `ghcr.io/open-webui/open-terminal:latest` | Container image |
+| `resources.requests.cpu` | string | `100m` | CPU request |
+| `resources.requests.memory` | string | `256Mi` | Memory request |
+| `resources.limits.cpu` | string | `1` | CPU limit |
+| `resources.limits.memory` | string | `1Gi` | Memory limit |
+| `idleTimeoutMinutes` | integer | `30` | Idle timeout before Pod is stopped |
+| `packages` | array | `[]` | Apt packages to pre-install |
+| `pipPackages` | array | `[]` | Pip packages to pre-install |
+| `persistence.enabled` | boolean | `true` | Enable persistent storage |
+| `persistence.size` | string | `1Gi` | PVC size |
+| `persistence.storageClass` | string | *(cluster default)* | Storage class |
+
+### Status fields
+
+| Field | Description |
+| :--- | :--- |
+| `phase` | `Pending`, `Provisioning`, `Running`, `Idle`, or `Error` |
+| `podName` | Name of the terminal Pod |
+| `serviceUrl` | In-cluster URL for the terminal |
+| `apiKeySecret` | Secret holding the terminal's API key |
+| `lastActivityAt` | Timestamp of last proxied request |
+
+
+
+
+Full Helm values reference
+
+| Key | Default | Description |
+| :--- | :--- | :--- |
+| `terminals.enabled` | `false` | Enable the Terminals subchart |
+| `terminals.apiKey` | (empty) | Shared API key (auto-generated if empty) |
+| `terminals.existingSecret` | (empty) | Pre-existing Secret name (key: `api-key`) |
+| `terminals.crd.install` | `true` | Install the Terminal CRD |
+| `terminals.operator.image.repository` | `ghcr.io/open-webui/terminals-operator` | Operator image |
+| `terminals.operator.image.tag` | `latest` | Operator image tag |
+| `terminals.operator.replicaCount` | `1` | Operator replicas |
+| `terminals.orchestrator.image.repository` | `ghcr.io/open-webui/terminals` | Orchestrator image |
+| `terminals.orchestrator.image.tag` | `latest` | Orchestrator image tag |
+| `terminals.orchestrator.backend` | `kubernetes-operator` | Backend type |
+| `terminals.orchestrator.terminalImage` | `ghcr.io/open-webui/open-terminal:latest` | Default image for user Pods |
+| `terminals.orchestrator.idleTimeoutMinutes` | `30` | Idle timeout (minutes) |
+| `terminals.orchestrator.service.type` | `ClusterIP` | Orchestrator Service type |
+| `terminals.orchestrator.service.port` | `8080` | Orchestrator Service port |
+
+
+
+
+RBAC requirements (manual install only)
+
+If not using the Helm chart, the operator's ServiceAccount needs a ClusterRole with:
+
+| Resource | Verbs |
+| :--- | :--- |
+| `terminals.openwebui.com` | get, list, watch, create, update, patch, delete |
+| `pods`, `services`, `persistentvolumeclaims`, `secrets` | get, list, watch, create, update, patch, delete |
+| `events` | create |
+| `configmaps`, `leases` | get, list, watch, create, update, patch |
+
+
diff --git a/docs/reference/env-configuration.mdx b/docs/reference/env-configuration.mdx
index b037d7b1..d54bfde3 100644
--- a/docs/reference/env-configuration.mdx
+++ b/docs/reference/env-configuration.mdx
@@ -1151,7 +1151,7 @@ The JSON data structure of `TOOL_SERVER_CONNECTIONS` might evolve over time as n
- Type: `str` (JSON array)
- Default: `[]`
-- Description: Specifies a JSON array of terminal server connection configurations. Each connection defines the parameters needed to connect to an [Open Terminal](/features/open-terminal) instance or a [Terminals orchestrator](/features/open-terminal/advanced/terminals/docker-backend). Unlike user-level tool server connections, these are admin-configured and proxied through Open WebUI, which means the terminal URL and API key are never exposed to the browser. Supports group-based access control via `access_grants`.
+- Description: Specifies a JSON array of terminal server connection configurations. Each connection defines the parameters needed to connect to an [Open Terminal](/features/open-terminal) instance or a [Terminals orchestrator](/features/open-terminal/terminals/). Unlike user-level tool server connections, these are admin-configured and proxied through Open WebUI, which means the terminal URL and API key are never exposed to the browser. Supports group-based access control via `access_grants`.
- Example (direct Open Terminal connection):
```json
[
@@ -1187,7 +1187,7 @@ The JSON data structure of `TOOL_SERVER_CONNECTIONS` might evolve over time as n
- Persistence: This environment variable is a `PersistentConfig` variable.
:::tip Helm chart auto-configuration
-When deploying on Kubernetes with the Open WebUI Helm chart and `terminals.enabled: true`, this variable is set automatically to point at the in-cluster orchestrator service. See the [Kubernetes Operator guide](/features/open-terminal/advanced/terminals/kubernetes-operator) for details.
+When deploying on Kubernetes with the Open WebUI Helm chart and `terminals.enabled: true`, this variable is set automatically to point at the in-cluster orchestrator service. See the [Terminals (Orchestrator) guide](/features/open-terminal/terminals/) for details.
:::
:::warning
diff --git a/docs/team.mdx b/docs/team.mdx
index e38d18e0..f0cf083d 100644
--- a/docs/team.mdx
+++ b/docs/team.mdx
@@ -10,7 +10,7 @@ title: "👥 Our Team"
### 🚀 We're Actively Hiring!
-Passionate about open-source AI? [Join our team →](https://careers.openwebui.com/)
+Passionate about the future of AI? [Join our team →](https://careers.openwebui.com/)
:::