Linux & Kubernetes
Install Helix on a Linux server or Kubernetes cluster.
Licensing
A license is required to run Helix on Linux or Kubernetes.
- Developer license: For individuals, testing, and businesses with less than $10M annual revenue or fewer than 250 employees. Buy a developer license →
- Enterprise license: For larger organizations with full RBAC, SSO, SOC 2 Type II, and dedicated onboarding. Enterprise licensing →
Helix runs on any Kubernetes cluster - whether that's on your own bare metal, a European cloud provider like Hetzner or OVH, or a major cloud platform like AWS (EKS), Google Cloud (GKE), or Azure (AKS). If you can run containers, you can run Helix.
For organisations focused on digital sovereignty, we also offer a turnkey Sovereign Server - a 4U rack server with Helix pre-installed, shipped to your data centre.
Local Setup
To install Helix on your own Linux server with Nvidia/AMD GPU support, run the following command:
curl -sL -O https://get.helixml.tech/install.sh && bash install.shKubernetes Setup
Helm charts
Helix ships as two separate Helm charts, both published to https://charts.helixml.tech:
| Chart | Purpose | Required? |
|---|---|---|
helix-controlplane | Main Helix API, UI, RAG, and orchestration. This is what users log in to. | Yes |
helix-sandbox | Docker-in-Docker host for desktop agents, Zed sessions, and spec tasks (cloud desktops with GPU video streaming). | Recommended. Required if you use desktop or agent features. |
Inference is delivered by external providers (OpenAI, Anthropic, Together, or any OpenAI-compatible endpoint such as a self-hosted vLLM). There is no separate runner chart: configure providers under controlplane.providers.* as shown below.
The sections below cover the install for both charts.
Prerequisites
- A Kubernetes cluster (1.28+). Managed control planes (AKS/EKS/GKE) are fine; the sandbox chart requires privileged pods, so AKS/EKS/GKE Autopilot is not supported.
kubectlandhelm3.x on your workstation.- A Helix license key from your account.
- A
StorageClassthat supportsReadWriteOncepersistent volumes. The control plane chart provisions several PVCs (main Postgres, pgvector, kodit-vectorchord, controlplane data). - For a minimal control-plane-only install, budget ~8 vCPU / 24 GiB RAM of worker capacity. The control plane pod ships with a Haystack sidecar that loads a ~4 GB embedding model on first start.
- An LLM provider (OpenAI, Anthropic, Together, or any OpenAI-compatible endpoint such as a self-hosted vLLM). Without one, there will be no models available after first login.
Cloud quotas
On a fresh Azure/AWS/GCP subscription, most GPU SKU families have quota 0 by default. Before starting:
- AWS:
g5/p4dEC2 quotas per region. - Azure:
NVadsV710v5(AMD V710) orNC*/ND*(NVIDIA) families, plus "Total Regional vCPUs". - GCP:
nvidia-*GPU quotas per region.
Create secrets
Replace <paste-your-license-key> with the key from your account. Generate real random values for the postgres password, runner token, and encryption key; the example commands below produce them inline.
# Create namespace and secrets
kubectl create namespace helix
kubectl config set-context --current --namespace=helix
# Postgres credentials for the bundled Postgres pod (or an external instance).
kubectl create secret generic postgresql-auth-secret \
--from-literal=username="helix" \
--from-literal=password="$(openssl rand -hex 16)" \
--from-literal=database="helix"
kubectl create secret generic pgvector-auth-secret \
--from-literal=username="postgres" \
--from-literal=password="" \
--from-literal=database="postgres"
kubectl create secret generic helix-pgvector-creds \
--from-literal=dsn="postgresql://postgres:@my-helix-controlplane-pgvector:5432/postgres"
kubectl create secret generic helix-license \
--from-literal=license-key="<paste-your-license-key>"
# Encryption key for secrets at rest (PATs, SSH keys, OAuth tokens).
# Must be a 64-character hex string (32 bytes for AES-256-GCM). DO NOT
# rotate this once data has been written - rotating invalidates every
# stored secret.
kubectl create secret generic helix-encryption-key \
--from-literal=encryption-key="$(openssl rand -hex 32)"
# Random token, used by the sandbox chart to auth back to the control plane.
kubectl create secret generic helix-runner-secrets \
--from-literal=api-token="$(openssl rand -hex 32)"Configure values.yaml
Save this as values.yaml for the chart:
# Production-ready configuration for helix-controlplane
# For more options: https://github.com/helixml/helix/blob/main/charts/helix-controlplane/values-example.yaml
global:
# Public URL of your Helix deployment. Also injected into the desktop
# sandbox containers as the URL their inner agent (Zed / Qwen Code)
# uses to reach the control plane API. The default of
# http://localhost:8080 is ONLY correct for a single-binary local
# install - inside a Kubernetes cluster localhost resolves to the
# sandbox pod itself and inference will fail with
# "error sending request for url (http://localhost:8080/...)".
#
# In Kubernetes set this to either:
# - the cluster-internal service URL of the control plane, e.g.
# http://my-helix-controlplane.helix.svc.cluster.local
# (works for port-forward / private access)
# - or your public ingress hostname, e.g. https://helix.example.com
serverUrl: http://my-helix-controlplane.helix.svc.cluster.local
# Leave image.tag unset so the chart uses its pinned appVersion.
# Do NOT set it to "latest" - no :latest tag is published to GHCR.
# To pin a specific release, use e.g. tag: "2.9.31".
searxng:
enabled: true
chrome:
enabled: true
pgvector:
enabled: true
auth:
existingSecret: "pgvector-auth-secret"
usernameKey: "username"
passwordKey: "password"
databaseKey: "database"
persistence:
enabled: true
size: 50Gi
storageClass: ""
annotations: {}
accessModes:
- ReadWriteOnce
controlplane:
licenseKeyExistingSecret: "helix-license"
licenseKeyExistingSecretKey: "license-key"
runnerTokenExistingSecret: "helix-runner-secrets"
runnerTokenExistingSecretKey: "api-token"
# Encryption key for secrets at rest (PATs, SSH keys, OAuth tokens).
# Required - the API will refuse to start without it. Generated above
# via `openssl rand -hex 32`. Do not rotate after data is written.
encryptionKeyExistingSecret: "helix-encryption-key"
encryptionKeyExistingSecretKey: "encryption-key"
admin:
userSource: "env"
userIds: "all"
haystack:
enabled: true
existingSecret: "helix-pgvector-creds"
existingSecretDsnKey: "dsn"
embeddingsModel: "MrLight/dse-qwen2-2b-mrl-v1"
embeddingsDim: "1536"
chunkSize: "1000"
chunkOverlap: "50"
chunkUnit: "word"
rag:
defaultProvider: "haystack"
embeddingsProvider: "helix"
inference:
defaultProvider: "helix"
fineTuning:
defaultProvider: "helix"
# Configure at least one LLM provider. Without this, there will be
# no models available after first login. Provide an API key inline,
# or reference a Kubernetes secret via controlplane.providers.<name>.apiKeyExistingSecret.
# providers:
# anthropic:
# apiKey: "sk-ant-..."
# openai:
# apiKey: "sk-..."
# Extra environment variables injected into the control plane pod.
# The control plane process is where settings like HELIX_ENCODER
# are read and forwarded to inner desktop containers spawned for
# spec task / Zed agent sessions.
#
# Example: opt into hardware H.264 video encoding on AMD GPUs
# (requires Helix 2.10.0+; no-op on 2.9.x). Default auto-detect uses
# software OpenH264 on AMD because of a historical gst-va crash on
# older Mesa; this flag bypasses that skip.
# extraEnv:
# - name: HELIX_ENCODER
# value: "vaapi"
persistence:
enabled: true
size: 100Gi
storageClass: ""
accessModes:
- ReadWriteOnce
volumes:
- name: data
postgresql:
enabled: true
auth:
existingSecret: "postgresql-auth-secret"
usernameKey: "username"
passwordKey: "password"
databaseKey: "database"
persistence:
enabled: true
size: 10Gi
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
memory: "1Gi"
tika:
enabled: false
typesense:
enabled: falseExternal Postgres
The chart can use an existing managed Postgres instead of the bundled one, but the bundled pgvector sidecar requires the vectorchord and vectorchord-bm25 extensions. Managed services that ship stock Postgres (Azure Flexible Server, AWS RDS, Cloud SQL) do not include these extensions out of the box, so keep pgvector.enabled: true. That pod will host the RAG vector index independently of wherever your main Postgres lives.
Install the control plane
# Add Helix Helm repository
helm repo add helix https://charts.helixml.tech
helm repo update
# Install the control plane with your generated values.yaml and secrets.
# By default the chart uses its pinned appVersion. Do NOT set image.tag
# to "latest" - no :latest tag is published. To pin a specific release,
# add --set image.tag="<version>" (e.g. "2.9.31") or --version "<chart-version>".
helm upgrade --install my-helix-controlplane helix/helix-controlplane \
-f values.yamlFirst boot pulls the embedding model (~4 GB) into the Haystack sidecar; budget 5 to 10 minutes before the main pod becomes Ready.
External inference providers
Helix can route inference to external providers (OpenAI, Anthropic, Together, or any self-hosted OpenAI-compatible endpoint like vLLM) instead of running a local runner. Each provider is configured under controlplane.providers.* in the control-plane values. Use existingSecret for API keys so they stay out of your values file and git history.
First, create a Kubernetes Secret for each provider you want to use:
kubectl create secret generic openai-api-key \
--from-literal=api-key="sk-..."
kubectl create secret generic anthropic-api-key \
--from-literal=api-key="sk-ant-..."
kubectl create secret generic togetherai-api-key \
--from-literal=api-key="..."Then add a providers: block inside the controlplane: section of your values.yaml. Only include the providers you actually use:
controlplane:
# ... your existing controlplane config ...
providers:
openai:
existingSecret: "openai-api-key"
existingSecretApiKeyKey: "api-key"
# baseUrl: "https://api.openai.com/v1" # override for Azure OpenAI or proxies
anthropic:
existingSecret: "anthropic-api-key"
existingSecretApiKeyKey: "api-key"
togetherai:
existingSecret: "togetherai-api-key"
existingSecretApiKeyKey: "api-key"
vllm:
# Self-hosted vLLM speaks the OpenAI API. Point at its cluster-internal service.
# Most vLLM deployments need no API key; pass one via existingSecret if yours does.
baseUrl: "http://my-vllm.vllm.svc.cluster.local:8000/v1"Anthropic can also be used via GCP Vertex AI - set vertexProjectID, vertexRegion, and either vertexCredentialsJSON or vertexCredentialsSecret under providers.anthropic. See values.yaml in the chart for the full shape.
Re-run helm upgrade on the control plane to apply the change.
Install the sandbox chart (cloud desktops / Zed agent)
The helix-sandbox chart powers Helix's cloud desktop and in-browser Zed IDE features. It is optional; install it only if you want to run spec tasks or open the IDE in the browser.
The sandbox runs a Docker-in-Docker daemon which needs privileged pods, a GPU node pool (for desktop rendering), and large persistent volumes.
helm upgrade --install my-helix-sandbox helix/helix-sandbox \
--namespace helix \
--set sandbox.apiUrl="http://my-helix-controlplane.helix.svc.cluster.local" \
--set sandbox.runnerTokenExistingSecret=helix-runner-secrets \
--set sandbox.runnerTokenExistingSecretKey=api-tokenPer-cloud override values are shipped with the chart: values-aks.yaml, values-eks.yaml, values-gke.yaml, values-bare-metal.yaml.
Access the control plane
kubectl port-forward -n helix svc/my-helix-controlplane 8080:80Then open http://localhost:8080. The first registered user becomes an admin (because of controlplane.admin.userIds: "all").
For a production deployment you should configure an ingress with TLS termination and set global.serverUrl to the public URL. See the values-example.yaml for nginx + cert-manager, GKE managed-certificate, and cert-manager + DNS-01 examples.
Environment variables
Most options have first-class chart values - reach for those before resorting to env vars. The variables in this section are operator-tunable runtime knobs that don't have a structured chart key. They're passed through one of three extraEnv lists, depending on which component reads them:
| Scope | Where it's set | Applies to |
|---|---|---|
global.extraEnv | helix-controlplane chart | controlplane + chrome + searxng pods. Use this for cluster-wide settings like HTTPS_PROXY / HTTP_PROXY / NO_PROXY that every outbound HTTP client needs. |
controlplane.extraEnv | helix-controlplane chart | controlplane pod only. Use this for desktop-bridge tunables (forwarded into dev containers) and control-plane process flags. |
sandbox.extraEnv | helix-sandbox chart | sandbox pod only. Use this for hydra-side flags read inside the sandbox before the dev container starts. |
# helix-controlplane values.yaml
global:
extraEnv:
- name: HTTPS_PROXY
value: "http://proxy.internal:8080"
- name: NO_PROXY
value: "localhost,127.0.0.1,.svc,.cluster.local"
controlplane:
extraEnv:
- name: HELIX_VIDEO_MODE
value: shmRe-run helm upgrade after changing any of these. The desktop-bridge env vars (video mode, encoder, GOP, render node) are read when a new dev container is created, so existing spec-task sessions keep their old values - start a new session to pick up the change.
Desktop streaming (controlplane.extraEnv)
These are read inside each spec-task / dev container by desktop-bridge. Helix 2.11.3+ forwards them from the controlplane env into dev containers automatically.
| Variable | Default | Notes |
|---|---|---|
HELIX_VIDEO_MODE | zerocopy | PipeWire capture path: zerocopy (DMA-BUF -> CUDA, fastest), native (DMA-BUF via GStreamer 1.24+), or shm (shared memory, slower but most compatible). Set to shm if the browser shows endless reconnects and desktop-bridge logs failed to set pipeline to playing after Received CUDA context via set_context - typically seen on managed-GPU clusters where the DMA-BUF -> CUDA import fails (e.g. GKE T4). NVENC is still used for the encode, so quality is unchanged. |
HELIX_ENCODER | auto-detect | H.264 encoder selection: nvenc (NVIDIA), vaapi / vaapi-legacy (Intel/AMD), openh264 / x264 (software fallback). Useful when auto-detect picks the wrong encoder for your hardware. |
HELIX_GOP_SIZE | 120 | GOP (Group of Pictures) size in frames. Default is 2 seconds at 60fps. Lower values make mid-stream joins faster (less catchup data) at the cost of slightly larger bitrate. |
HELIX_RENDER_NODE | unset | VA-API render device path (e.g. /dev/dri/renderD129). Set explicitly on multi-GPU hosts where auto-detection picks the wrong one. Set to SOFTWARE to disable VA-API. |
Control plane behaviour (controlplane.extraEnv)
| Variable | Default | Notes |
|---|---|---|
HELIX_DISABLE_VERSION_CHECK | unset | Set to any non-empty value to disable the upstream version-check ping on startup. Useful in air-gapped deployments. |
HELIX_SKIP_AUTOMIGRATE | unset | Set to 1 to skip GORM AutoMigrate on the public schema at boot. Only set this if you migrate the schema out-of-band - otherwise the control plane may crash on schema-mismatch errors. |
Sandbox host (sandbox.extraEnv)
These are read by hydra inside the sandbox pod, before the dev container starts. Requires Helix 2.11.3+ for the sandbox.extraEnv plumbing.
| Variable | Default | Notes |
|---|---|---|
HELIX_SANDBOX_APT_MIRROR | unset | Override the Debian/Ubuntu APT mirror used when bootstrapping dev container images. Useful in air-gapped or geo-restricted environments. |