> ## Documentation Index
> Fetch the complete documentation index at: https://allhandsai-docs-litellm-proxy-screenshot.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Install with Helm

> End-to-end installation of OpenHands Enterprise on Kubernetes using Helm

OpenHands Enterprise is distributed as a Helm chart through the Replicated registry.
Your license credentials authenticate the chart download, and the chart embeds your
license automatically at install time.

<Note>
  Helm-based installation requires an OpenHands Enterprise license. If you don't have
  one yet, [register for a free 30-day trial](https://install.r9.all-hands.dev/openhands/signup)
  or [contact our team](https://openhands.dev/contact) to get set up.
  The license YAML from the install portal is all the licensing material you need:
  use `spec.customerEmail` as the registry username and `spec.licenseID` as its
  password. The embedded-cluster installer assets on that portal are for VM
  installations, not Helm.
</Note>

## Prerequisites

* A Kubernetes cluster with a default storage class and an ingress controller
  (see [Resource Limits](/enterprise/k8s-install/resource-limits) for sizing guidance)
* **Helm v4 or later**
* `kubectl` access to the target cluster
* Your **license ID** and the **email address** registered with your license
  (both provided by our team)
* **LLM credentials** from your chosen provider, for example an Anthropic API key
  from the [Anthropic Console](https://console.anthropic.com/)
* DNS records you control, following the layout used throughout this guide
  (with `openhands.example.com` as the base domain):
  `app.openhands.example.com` (application), `auth.openhands.example.com`
  (login), `runtime-api.openhands.example.com`, and
  `<id>-runtime.openhands.example.com` for the per-session sandboxes. Every
  hostname sits one label under the base domain, so a single **wildcard**
  record `*.openhands.example.com` pointing at your cluster's ingress covers
  all of them; see [DNS and TLS](/enterprise/k8s-install/dns-and-tls).
* A **wildcard TLS certificate** for `*.openhands.example.com`, which you provide.
* An **authentication method** for user login — GitLab, Bitbucket Data Center,
  and more are supported; this guide uses a **GitHub App**. See
  [Creating a GitHub App](/enterprise/quick-start#create-a-github-app).

## Step 1: Log in to the registry

Authenticate Helm against the Replicated registry using your license. Supply
the license ID on standard input so it does not appear in the command history:

```bash theme={null}
read -r -s -p "License ID: " OH_LICENSE_ID; echo
printf '%s' "$OH_LICENSE_ID" | helm registry login registry.replicated.com \
  --username <your-license-email> \
  --password-stdin
unset OH_LICENSE_ID
```

## Step 2: Create the namespaces and secrets

We recommend running agent sandboxes in a namespace separate from the
application. Sandboxes run agent-authored code, so a dedicated namespace keeps
them isolated from the application, database, and secrets. Create both
namespaces now:

```bash theme={null}
kubectl create namespace openhands
kubectl create namespace openhands-runtimes
```

The chart references several Kubernetes secrets that you create ahead of
installation, all in the `openhands` namespace:

```bash theme={null}
kubectl -n openhands create secret generic jwt-secret \
  --from-literal=jwt-secret=<random-string>

kubectl -n openhands create secret generic keycloak-admin \
  --from-literal=admin-password=<random-string>

kubectl -n openhands create secret generic keycloak-realm \
  --from-literal=realm-name=allhands \
  --from-literal=server-url=http://keycloak \
  --from-literal=client-id=allhands \
  --from-literal=client-secret=<random-string> \
  --from-literal=smtp-password=<smtp-password>

OH_POSTGRES_PASSWORD="$(openssl rand -hex 32)"
kubectl -n openhands create secret generic postgres-password \
  --from-literal=username=postgres \
  --from-literal=password="$OH_POSTGRES_PASSWORD" \
  --from-literal=postgres-password="$OH_POSTGRES_PASSWORD"
unset OH_POSTGRES_PASSWORD

kubectl -n openhands create secret generic redis \
  --from-literal=redis-password=<random-string>

kubectl -n openhands create secret generic lite-llm-api-key \
  --from-literal=lite-llm-api-key=<random-string>

kubectl -n openhands create secret generic admin-password \
  --from-literal=admin-password=<random-string>

kubectl -n openhands create secret generic litellm-env-secrets \
  --from-literal=ANTHROPIC_API_KEY=<your-llm-api-key>
```

The application and Runtime API must use the **same** key. Create both Secrets
from one generated value:

```bash theme={null}
OH_RUNTIME_SHARED_KEY="$(openssl rand -hex 32)"
kubectl -n openhands create secret generic default-api-key \
  --from-literal=default-api-key="$OH_RUNTIME_SHARED_KEY"
kubectl -n openhands create secret generic sandbox-api-key \
  --from-literal=sandbox-api-key="$OH_RUNTIME_SHARED_KEY"
unset OH_RUNTIME_SHARED_KEY
```

Then create the secret for user authentication. Other providers (GitLab,
Bitbucket Data Center, and more) are supported, but this guide uses GitHub
throughout. If you don't have a GitHub App yet, run our
[script](/enterprise/quick-start#create-a-github-app) — its output provides
every value below, and the private key file is written to its `keys`
directory:

```bash theme={null}
kubectl -n openhands create secret generic github-app \
  --from-literal=app-id=<github-app-id> \
  --from-literal=app-slug=<github-app-slug> \
  --from-literal=client-id=<github-app-client-id> \
  --from-literal=client-secret=<github-app-client-secret> \
  --from-literal=private-key="$(cat <github-app-private-key>.pem)" \
  --from-literal=webhook-secret=<github-app-webhook-secret>
```

<Tip>
  Generate strong random values (for example with `openssl rand -hex 32`) for each
  remaining `<random-string>` placeholder, and store them in your secret manager.
  Use one value for both Runtime API Secrets. Since this example connects to
  PostgreSQL as `postgres`, use one value for its `password` and
  `postgres-password` fields too.
  To use an existing PostgreSQL instance instead of the bundled one, see
  [External PostgreSQL](/enterprise/external-postgres).
</Tip>

## Step 3: Configure values

Create a `values.yaml` with your environment-specific configuration. The
minimum for a working installation covers application ingress and TLS,
user authentication, the runtime (sandbox) endpoints, conversation
storage, and your LLM provider. PostgreSQL and Redis run embedded in the
cluster; the bundled PostgreSQL needs a database name and database creation
turned on, both shown below (to use your own database instead, see
[External PostgreSQL](/enterprise/external-postgres)).

<Warning>
  The embedded PostgreSQL is intended for proof-of-concept and evaluation use
  only, not production. For production deployments we recommend bringing your
  own managed PostgreSQL — see
  [External PostgreSQL](/enterprise/external-postgres). There is no officially
  supported migration path from the embedded PostgreSQL instance to an external
  one, so plan to switch to an external database before you load production
  data.
</Warning>

The example below uses Traefik, the chart's default ingress class; set
`ingress.class` and the annotations to match your controller.

```yaml theme={null}
ingress:
  enabled: true
  host: app.openhands.example.com
  class: traefik

  # This guide brings its own certificate, terminated at the ingress controller,
  # so the chart's per-ingress TLS is disabled (see the note below the example).
  tls:
    enabled: false

# Enables login via the GitHub App created in Step 2
github:
  enabled: true

# Bundled PostgreSQL: name the application database and let the chart create
# the databases it needs on first start
postgresql:
  auth:
    database: openhands
  primary:
    persistence:
      enabled: true
      storageClass: <your-storage-class>
databaseMigrations:
  createDatabases: true

# Login is served by the bundled Keycloak — both the component and its
# ingress must be enabled for users to be able to log in
keycloak:
  enabled: true
  ingress:
    enabled: true
    hostname: auth.openhands.example.com
    tls: false

# Where agent sandboxes run. The runtime API needs its own hostname, and each
# sandbox gets its own hostname under your wildcard DNS record.
sandbox:
  apiHostname: https://runtime-api.openhands.example.com

env:
  RUNTIME_URL_PATTERN: "https://{runtime_id}-runtime.openhands.example.com"
  LITELLM_DEFAULT_MODEL: litellm_proxy/claude-sonnet-4-5

runtime-api:
  # Create sandboxes in the dedicated namespace from Step 2, isolated from the
  # application workloads.
  sandbox_namespace: openhands-runtimes
  ingress:
    enabled: true
    host: runtime-api.openhands.example.com
    tls: false
  databaseMigrations:
    createDatabases: true
  env:
    # Sandbox hostnames are built as {runtime_id}<separator><RUNTIME_BASE_URL>;
    # together these must match RUNTIME_URL_PATTERN above. RUNTIME_DISABLE_SSL
    # defaults to "true"; it must be "false" so sandbox URLs are served over https.
    RUNTIME_BASE_URL: runtime.openhands.example.com
    RUNTIME_URL_SEPARATOR: "-"
    RUNTIME_DISABLE_SSL: "false"
    # Storage class for sandbox volumes. The chart default (standard-rwo) only
    # exists on GKE — set a storage class from `kubectl get storageclass` or
    # sandboxes will never start.
    STORAGE_CLASS: <your-storage-class>

# On EKS, provide S3 access using the IAM role or Secret in the EKS guide.
# Replace the bucket and region with your values.
filestore:
  ephemeral: false
  type: s3
  bucket: <your-bucket>
  region: <your-region>
minio:
  enabled: false

litellm-helm:
  enabled: true
  proxy_config:
    model_list:
      - model_name: claude-sonnet-4-5
        litellm_params:
          model: anthropic/claude-sonnet-4-5
          api_key: os.environ/ANTHROPIC_API_KEY
```

This example uses Amazon S3 for conversation storage. Follow the
[EKS object storage steps](/enterprise/k8s-install/eks#object-storage) to grant
the application access to the bucket. On another Kubernetes provider,
configure a supported external object store and its credentials before
installing.

<Note>
  Chart `0.71.1` redirects signed-in users to `/canvas` but does not enable
  that frontend by default. For this chart version, add the following to
  `values.yaml` so the first conversation can start:

  ```yaml theme={null}
  agent-canvas:
    enabled: true
    ingress:
      enabled: true
      host: app.openhands.example.com
      className: traefik
      path: /canvas
      tls:
        enabled: false
    staticServer:
      authRequired: false
      lockToCloud: https://app.openhands.example.com
  ```

  The static frontend can load without a second API-key prompt; Enterprise
  API requests still use the signed-in session. Later chart versions may
  provide a working default UI without these overrides.
</Note>

## Step 4: Install

```bash theme={null}
helm install openhands oci://registry.replicated.com/openhands/openhands \
  --namespace openhands \
  --values values.yaml
```

Watch the workloads come up:

```bash theme={null}
kubectl get pods -n openhands --watch
```

The first install pulls all container images, which can take a while. Along with the
application components you'll see a `replicated` pod — the Replicated SDK, which
handles license verification and powers the support tooling below.

## Step 5: Validate the installation

The chart ships preflight checks that validate your cluster against the
application's requirements. Run them with the
[`preflight` CLI](https://troubleshoot.sh/docs/preflight/introduction/):

```bash theme={null}
preflight secret/openhands/openhands-preflight
```

<Tip>
  The `preflight` and `support-bundle` CLIs are both part of
  [Troubleshoot](https://troubleshoot.sh/docs/#installation). Install them with:

  ```bash theme={null}
  curl -L https://krew.sh/preflight | bash
  curl -L https://krew.sh/support-bundle | bash
  ```
</Tip>

Then confirm the application is reachable at your configured hostname and log
in. A complete first-use check goes beyond Ready pods and preflight:

1. Open `https://app.openhands.example.com` and sign in through your configured
   identity provider. The conversation UI should load without an additional
   backend URL or API-key prompt.
2. Start a new conversation and ask the agent to run `pwd`. Confirm a sandbox
   starts in `openhands-runtimes`, the command returns a path, and the agent
   gives a completed reply.
3. If the conversation cannot start, use the
   [first-conversation checks](/enterprise/troubleshooting#first-conversation-checks)
   before changing the deployment.

<Note>
  On chart `0.71.1`, a fresh user's selected `Default` LLM profile may not
  match `LITELLM_DEFAULT_MODEL`. If the first request reports an invalid proxy
  token or model, inspect the selected profile and the bundled LiteLLM model
  list using the troubleshooting checks. Do not enter an infrastructure API key
  into the browser to work around this error.
</Note>

## Next Steps

The install above is a minimal working baseline. Features and tuning are values
overrides on the same release — edit your `values.yaml` and apply with
`helm upgrade` using the chart URL from Step 4:

<CardGroup cols={2}>
  <Card title="Resource Limits" icon="gauge-high" href="/enterprise/k8s-install/resource-limits">
    Size memory, CPU, and replicas for production workloads.
  </Card>

  <Card title="External PostgreSQL" icon="database" href="/enterprise/external-postgres">
    Use your own PostgreSQL instead of the embedded instance.
  </Card>

  <Card title="Analytics" icon="chart-line" href="/enterprise/analytics">
    Enable conversation analytics with Laminar.
  </Card>

  <Card title="Automations" icon="clock" href="/enterprise/k8s-install/automations">
    Run scheduled or event-triggered tasks on a Helm installation.
  </Card>

  <Card title="Plugin Marketplace" icon="puzzle-piece" href="/enterprise/plugin-marketplace">
    Offer curated plugins to your users.
  </Card>
</CardGroup>

## Troubleshooting

For a guided diagnostic workflow and a map of OHE components, see
[Troubleshooting](/enterprise/troubleshooting).

### Generate a support bundle

If something isn't working, generate a support bundle with the
[`support-bundle` CLI](https://troubleshoot.sh/docs/support-bundle/introduction/).
It discovers the diagnostic specs that ship with the chart and collects logs,
resource states, and health checks from the installation:

```bash theme={null}
kubectl support-bundle --load-cluster-specs --namespace openhands
```

### Send it to us

Upload the resulting archive directly to our support team — the upload
authenticates with the license embedded in the bundle:

```bash theme={null}
kubectl support-bundle upload support-bundle-<timestamp>.tar.gz
```

### Common issues

| Symptom | Likely cause |
| - | - |
| `helm install` fails with a template error mentioning `replicated` | Helm version too old — upgrade to v4+ |
| `helm registry login` or chart pull returns 401/403 | License credentials incorrect, or the license isn't enabled for Helm installs — contact support |
| Preflight warns about node memory | Cluster nodes below the recommended sizing — see [Resource Limits](/enterprise/k8s-install/resource-limits) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.