Terminals (Orchestrator)
Terminals is an enterprise orchestration layer for Hrida 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.
- Need different environments per team? รขโ โ Policies guide
How it worksโ
The orchestrator sits between HridaAI and the Hrida Terminal instances:
- A user activates a terminal in HridaAI.
- HridaAI proxies the request to the orchestrator, a service that manages the lifecycle of terminal containers.
- The orchestrator provisions a personal Hrida Terminal container for that user (or reconnects to an existing one).
- All traffic is proxied through the orchestrator. The user never connects to their container directly.
- 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 Hrida Terminal, so the AI can execute commands, read files, and run code, all scoped to the requesting user's container.
Deploymentโ
- Docker
- Kubernetes Operator
Prerequisitesโ
- Docker Engine installed and running
- HridaAI running (or ready to deploy alongside)
- HridaAI Enterprise License (required for production use)
Quick start with Docker Composeโ
This Compose file deploys HridaAI and the Terminals orchestrator together.
services:
hrida-ai:
image: ghcr.io/hrida-ai/hrida-ai-studio: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:
- hrida-ai:/app/backend/data
networks:
- hridaai
depends_on:
- terminals
terminals:
image: ghcr.io/hrida-ai/terminals:latest
environment:
- TERMINALS_BACKEND=docker
- TERMINALS_API_KEY=${TERMINALS_API_KEY}
- TERMINALS_IMAGE=ghcr.io/hrida-ai/hrida-terminal:latest
- TERMINALS_NETWORK=hrida-ai-network
- TERMINALS_IDLE_TIMEOUT_MINUTES=30
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- terminals-data:/app/data
networks:
- hridaai
volumes:
hrida-ai:
terminals-data:
networks:
hridaai:
name: hrida-ai-networkSet the shared API key in a .env file next to your Compose file:
TERMINALS_API_KEY=change-me-to-a-strong-random-value
Then start everything:
docker compose up -dHridaAI will be available at http://localhost:3000. When any user activates a terminal, the orchestrator provisions their personal container automatically.
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 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 HridaAI. Required. |
TERMINALS_IMAGE | ghcr.io/hrida-ai/hrida-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_HRIDAAI_URL | (empty) | If set, validates incoming JWTs against this HridaAI 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.
Prerequisitesโ
- A running Kubernetes cluster (v1.24+)
- Helm v3 installed
kubectlconfigured to access your cluster- HridaAI Enterprise License (required for production use)
Deploy with Helmโ
The HridaAI Helm chart includes Terminals as an optional subchart. Add a terminals section to your values file:
# values.yaml
terminals:
enabled: true
apiKey: "" # Auto-generated if left empty
crd:
install: true
operator:
image:
repository: ghcr.io/hrida-ai/terminals-operator
tag: latest
orchestrator:
image:
repository: ghcr.io/hrida-ai/terminals
tag: latest
backend: kubernetes-operator
terminalImage: "ghcr.io/hrida-ai/hrida-terminal:latest"
idleTimeoutMinutes: 30Then install or upgrade:
helm upgrade --install hrida-ai hrida-ai/hrida-ai-studio \
-f values.yaml \
--namespace hrida-ai --create-namespaceWhen 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โ
# Check that all pods are running
kubectl get pods -n hrida-ai -l app.kubernetes.io/part-of=hrida-terminal
# Check that the CRD is installed
kubectl get crd terminals.hrida.aiWhat gets deployedโ
When terminals.enabled: true, the chart creates:
| Resource | Purpose |
|---|---|
CRD (terminals.hrida.ai) | 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 HridaAI 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.
# List all terminals
kubectl get terminals -n hrida-ai
# Inspect a specific terminal
kubectl describe terminal <name> -n hrida-ai
# Delete a terminal (child resources are garbage-collected automatically)
kubectl delete terminal <name> -n hrida-aiMonitoringโ
# Operator logs
kubectl logs -n hrida-ai deployment/<release>-terminals-operator --tail=50
# Orchestrator logs
kubectl logs -n hrida-ai deployment/<release>-terminals-orchestrator --tail=50Terminal CRD reference
Spec fieldsโ
| Field | Type | Default | Description |
|---|---|---|---|
userId | string | (required) | HridaAI user ID |
image | string | ghcr.io/hrida-ai/hrida-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/hrida-ai/terminals-operator | Operator image |
terminals.operator.image.tag | latest | Operator image tag |
terminals.operator.replicaCount | 1 | Operator replicas |
terminals.orchestrator.image.repository | ghcr.io/hrida-ai/terminals | Orchestrator image |
terminals.orchestrator.image.tag | latest | Orchestrator image tag |
terminals.orchestrator.backend | kubernetes-operator | Backend type |
terminals.orchestrator.terminalImage | ghcr.io/hrida-ai/hrida-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.hrida.ai | 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 |
Authenticationโ
The orchestrator supports three authentication modes:
| Mode | When to use | How to configure |
|---|---|---|
| HridaAI JWT | Production. The orchestrator validates tokens against your HridaAI instance. | Set TERMINALS_OPEN_HRIDAAI_URL on the orchestrator to your HridaAI URL. |
| Shared API key | Standard. HridaAI includes a shared secret in every request. | Set TERMINALS_API_KEY to the same value on both HridaAI and the orchestrator. |
| Open | Development only. No authentication. Do not use in production. | Leave both TERMINALS_OPEN_HRIDAAI_URL and TERMINALS_API_KEY unset. |
When deployed via Docker Compose or Helm, the shared API key is configured automatically between HridaAI and the orchestrator.
Troubleshootingโ
Terminal won't startโ
- 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.
- Verify the API key. Ensure
TERMINALS_API_KEYmatches between HridaAI and the orchestrator. A mismatch causes silent auth failures. - 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_HRIDAAI_URLpoints to a reachable HridaAI 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
401or403responses.
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_NETWORKis set so containers can communicate by name. Without it, containers use published ports and theTERMINALS_DOCKER_HOSTaddress must be reachable. - Kubernetes: verify the orchestrator Service is accessible from the HridaAI Pod. Run
kubectl get svc -n hrida-aito confirm the service exists.
Further readingโ
- Multi-User Setup: comparison of isolation approaches
- Security best practices
- Configuration reference: all Hrida Terminal container settings
Licenseโ
Terminals requires an HridaAI Enterprise License for production use. See the Terminals repository for details.