Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions docs/features/open-terminal/advanced/multi-user.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ When multiple people on your team need terminal access through Open WebUI, you h
| | Single Container | Per-User Containers |
| :--- | :--- | :--- |
| **How** | One container, separate accounts inside | Each user gets their own container |
| **Isolation** | Separate workspaces inside one shared container | A separate container per user, isolated from each other |
| **Isolation** | Separate workspaces inside one shared container | A separate container per user, with its own files, processes, and resources. On the Docker backend the containers share one network |
| **Setup** | One extra setting | Additional orchestration service |
| **Best for** | Small teams where everyone is trusted equally | Production, larger teams, untrusted users |
| **Included in** | Open Terminal (free) | [Terminals](https://github.com/open-webui/terminals) (enterprise) |
Expand All @@ -20,7 +20,7 @@ If your Open WebUI instance has **more than one user account** and the same term

**Option 1 separates workspaces. It is not a security boundary between users.** Everyone still shares one kernel, one process list, one network namespace and one set of system resources, and provisioning the per-user accounts requires elevated privileges inside that container, so a user who sets out to reach root inside it will get there. Run it where every user on the instance is trusted at the same level.

For deployments with **untrusted users** (open signup, public-facing portals, users who are not trusted with each other's data), use **Option 2 (per-user containers via Terminals)**, which is the option that puts a real boundary between users. Layering `TERMINAL_PROXY_HEADERS` on top of Option 1 restricts what a proxied response can do in the user's browser, and it changes nothing about the shared container itself.
For deployments with **untrusted users** (open signup, public-facing portals, users who are not trusted with each other's data), use **Option 2 (per-user containers via Terminals)** on its Kubernetes backend with a NetworkPolicy, which is the setup that puts a real boundary between users. Layering `TERMINAL_PROXY_HEADERS` on top of Option 1 restricts what a proxied response can do in the user's browser, and it changes nothing about the shared container itself.
:::

---
Expand Down Expand Up @@ -67,7 +67,7 @@ Each user sees only their own files in the file browser.
:::warning Good for small trusted teams, not production
This mode gives everyone their own workspace, and they all run inside the same container. Resource pressure (memory, CPU) is shared. The network namespace is shared, so a port one user binds (e.g. `python -m http.server 8080`) is reachable from any other user's shell on that container. The port listing and the port proxy are scoped to the user who owns the port, so the port stays out of other users' file navigators, and reaching it takes a direct request from a shell. The process list is shared, so users can watch each other's running commands. Root inside the container is reachable from any account, because provisioning the accounts needs those privileges in the first place.

Treat every user on one instance as equally trusted, and use **Option 2 (per-user containers)** below when users have to be protected from each other. Layering the [`TERMINAL_PROXY_HEADERS`](/reference/env-configuration#terminal_proxy_headers) configuration on top locks proxied responses into a sandbox CSP in the browser, which is worth doing and does not change what happens inside the container.
Treat every user on one instance as equally trusted, and use **Option 2 (per-user containers)** below, on its Kubernetes backend, when users have to be protected from each other. Layering the [`TERMINAL_PROXY_HEADERS`](/reference/env-configuration#terminal_proxy_headers) configuration on top locks proxied responses into a sandbox CSP in the browser, which is worth doing and does not change what happens inside the container.
:::

```mermaid
Expand Down Expand Up @@ -96,14 +96,15 @@ 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, with files, processes, and resources separate from everyone else's.

- **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
- **Resource controls**: set CPU, memory, and storage limits per user or per environment
- **Multiple environments**: different setups for different teams (e.g., data science, development)
- **Kubernetes support**: works with Docker, Kubernetes, and k3s
- **Scoped workspaces**: optionally give each saved chat or each automation its own workspace instead of one per user, via [Terminal Contexts](/features/open-terminal/terminals/orchestration/contexts)
- **One network on the Docker backend**: every workspace sits on the same Docker network so the orchestrator can reach each one by name, which also lets a workspace reach a port another user's workspace has opened. Files and processes stay separate. For mutually untrusted users, run the Kubernetes backend and add a NetworkPolicy

```mermaid
flowchart LR
Expand All @@ -123,8 +124,8 @@ flowchart LR

Two deployment backends are available:

- **[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.
- **[Docker Backend](../terminals/#deployment)**: runs on a single Docker host. Best for small-to-medium teams or environments without Kubernetes.
- **[Kubernetes Operator](../terminals/#deployment)**: 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://docs.openwebui.com/enterprise). See the [Terminals repository](https://github.com/open-webui/terminals) for license details.
Expand All @@ -133,7 +134,7 @@ Terminals requires an [Open WebUI Enterprise License](https://docs.openwebui.com
## Related

- [Terminals overview →](../terminals/)
- [Terminals: Docker Backend →](../terminals/)
- [Terminals: Kubernetes Operator →](../terminals/)
- [Terminals: Docker Backend →](../terminals/#deployment)
- [Terminals: Kubernetes Operator →](../terminals/#deployment)
- [Security best practices →](./security.md)
- [All configuration options →](./configuration.md)
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ A terminal that is **Off** for chats is not listed in the chat terminal picker,
A per chat workspace belongs to a specific conversation, so the conversation has to exist first. It does once the first message has been sent and the chat appears in the sidebar.

- In a new conversation that has not been sent yet, the terminal panel and file browser for that terminal are not available. They appear once the chat is saved.
- In a [temporary chat](/features/chat-conversations/chat-features/url-params#8-temporary-chat-sessions), a per chat terminal is not listed in the terminal picker at all. A temporary chat is never saved, so there is nothing for the workspace to belong to.
- Nothing quietly falls back to the shared workspace. Before the chat is saved the terminal is refused, and a model told to use it reports an error naming the terminal.
- In a [temporary chat](/features/chat-conversations/chat-features/url-params#8-temporary-chat-sessions), a per chat terminal has nothing to attach to, because a temporary chat is never saved. The picker drops the terminal once the temporary chat exists. Before its first message the picker still lists it, and a message sent with it selected fails with `Terminal unavailable: Terminal server '<name>' requires a saved chat context`.
- Nothing in the interface quietly falls back to the shared workspace. Before the chat is saved the terminal is refused, and a model told to use it reports an error naming the terminal.
- A request that reaches the proxy with no chat id at all, such as a direct API call, is not scoped to any chat and lands in the shared workspace even when the row is **Per chat**. The Open WebUI interface always sends the chat id.

## Capacity

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Policy env vars are visible to the user inside their terminal. Do not put secret

## Automatically Injected Resource Vars

These are set automatically by the orchestrator from the policy's `cpu_limit` and `memory_limit`, so a system prompt or tool can read the container's resource budget. They are not user-configured.
These are set automatically by the orchestrator from the policy's `cpu_limit` and `memory_limit`, so a system prompt or tool can read the container's resource budget. They are not user-configured. Inside a capped workspace, `nproc` and `free` still report the host's cores and memory, so these variables are the only in-container source of the real limits.

| Variable | Purpose |
| :--- | :--- |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A policy describes what a user's Open Terminal workspace should look like. It co

Think of policies as agent workspace profiles. A data team might get Python, notebooks, and larger storage. A training lab might get a locked-down image and scheduled resets. A software team might get Git, build tools, language runtimes, and persistent files.

In Open WebUI, go to **Settings** -> **Admin** -> **Tools** -> **Integrations** -> Open Terminal**, add an orchestrator connection, verify it, then edit the policy fields.
In Open WebUI, go to **Settings > Admin > Integrations**, add an orchestrator connection in the **Open Terminal** section, verify it, then edit the policy fields.

## Policy Fields

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ The orchestrator needs access to the Docker socket (`/var/run/docker.sock`) to m
<details>
<summary>Container lifecycle details</summary>

**Naming.** Containers are named `terminals-<user-hash>` (where `<user-hash>` is the first 12 hex chars of the SHA-256 of the user ID, for a DNS-safe name), or `terminals-<user-hash>-<policy>` for non-default policies. They are easy to filter with `docker ps --filter "label=app.kubernetes.io/managed-by=terminals"`.
**Naming.** Containers are named `terminals-<user-hash>` (where `<user-hash>` is the first 12 hex chars of the SHA-256 of the user ID, for a DNS-safe name), or `terminals-<user-hash>-<policy>` for non-default policies. They are easy to filter with `docker ps --filter "label=app.kubernetes.io/managed-by=terminals"`. One container is also provisioned under the user id `system`, which Open WebUI uses to read a policy's tool list and system prompt, so a row for a user you do not recognize is expected.

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

Expand Down
Loading