> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensormesh.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Install with Helm

> Install the Tensormesh Operator from the official Helm chart.

Helm is the primary install path for the Tensormesh Operator. A single `helm install`
command deploys the operator, registers the `LMCacheEngine` CRD, and (by default) creates
an engine CR that the operator reconciles into a DaemonSet on your GPU nodes.

<Note>
  The chart is distributed through a private registry. To install it, **request an access
  token from the Tensormesh team.** You'll use it to authenticate before installing (shown
  below).
</Note>

<Note>
  If your target is OpenShift, install with Helm as described here and add the
  `openshift.enabled=true` flag — see [Install on OpenShift](/operator/installation/openshift)
  for the full overlay.
</Note>

## Prerequisites

* Helm `3.8` or newer (`helm version`)
* Cluster access with permission to create cluster-scoped resources
* A reachable Kubernetes 1.28+ cluster — see [Getting Started](/operator/installation/getting-started)
* **An access token from the Tensormesh team**

## Install

Authenticate to the registry with the token Tensormesh provided, then install:

```bash theme={null}
# One-time: log in with the token Tensormesh gave you. When authenticating with a token,
# the username is not validated — any non-empty value works (we use "tensormesh" here).
echo '<TOKEN_FROM_TENSORMESH>' | helm registry login ghcr.io -u tensormesh --password-stdin

helm install tensormesh-operator \
  oci://ghcr.io/tensormesh-production/charts/tensormesh-operator \
  --version 0.4.5 \
  --namespace tensormesh-operator \
  --create-namespace \
  -f my-values.yaml \
  --wait
```

The login is stored locally, so subsequent `helm upgrade`/`helm pull` commands just work
until you `helm registry logout ghcr.io`.

Put all configuration in `my-values.yaml` (see [Customizing the install](#customizing-the-install))
rather than `--set` flags, and **pin explicit image tags** there — never rely on `latest`.

<Tip>
  **For re-runs, use `helm upgrade --install` instead of `helm install`.** The `--install`
  flag makes `upgrade` idempotent — it installs if missing, upgrades if present. Plain
  `helm install` errors with `cannot reuse a name that is still in use` on the second run.
</Tip>

<Note>
  **If install fails, jump to [Troubleshooting](/operator/installation/troubleshooting).**
  Common failure modes covered there: CRD ownership conflict from a previous install
  (`exists and cannot be imported`), engine pod stuck `Pending` (missing GPU node label),
  `ImagePullBackOff`, and OpenShift `CreateContainerError` (missing `openshift.enabled=true`).
</Note>

<Tip>
  `--wait` blocks until the operator Deployment is Ready, so you don't have to poll. Pair
  it with `--timeout 5m` on slow clusters.
</Tip>

## Verify

<Steps>
  <Step title="The release exists">
    ```bash theme={null}
    helm list -n tensormesh-operator
    ```

    Example:

    ```text theme={null}
    NAME                  NAMESPACE             REVISION  UPDATED                   STATUS    CHART                      APP VERSION
    tensormesh-operator   tensormesh-operator   1         2026-05-24 02:05:00 UTC   deployed  tensormesh-operator-0.4.5  nightly-20260508-c7394b8
    ```

    `STATUS` should be `deployed`.
  </Step>

  <Step title="The operator pod is Running">
    ```bash theme={null}
    kubectl get pods -n tensormesh-operator
    ```

    Example:

    ```text theme={null}
    NAME                                  READY   STATUS    RESTARTS   AGE
    tensormesh-operator-5f47f76bd6-bzmqf  1/1     Running   0          2m
    ```
  </Step>

  <Step title="The CRD is registered">
    ```bash theme={null}
    kubectl get crd lmcacheengines.lmcache.lmcache.ai
    ```

    Example:

    ```text theme={null}
    NAME                              CREATED AT
    lmcacheengines.lmcache.lmcache.ai 2026-05-24T02:04:10Z
    ```
  </Step>

  <Step title="An engine CR exists and is being reconciled">
    ```bash theme={null}
    kubectl get lmcacheengines -n tensormesh-operator
    kubectl describe lmcacheengine -n tensormesh-operator
    ```

    Example:

    ```text theme={null}
    NAME                                PHASE     READY   DESIRED   AGE
    tensormesh-operator-default-engine  Running   1       1         2m
    ```

    In `kubectl describe`, the healthy state is `Phase: Running` with `1/1 instances ready`.
  </Step>

  <Step title="The engine DaemonSet landed on your GPU nodes">
    ```bash theme={null}
    kubectl get pods -n tensormesh-operator -o wide \
      -l app.kubernetes.io/component=cache-engine
    ```

    Example:

    ```text theme={null}
    NAME                                      READY   STATUS    RESTARTS   AGE   IP            NODE
    tensormesh-operator-default-engine-gfj2k  1/1     Running   0          2m    10.129.6.79   worker-gpu-a100-1
    ```

    Expect one pod per GPU node. On a cluster with no GPU nodes this list is empty — which
    is correct, not a failure.
  </Step>

  <Step title="Run the bundled smoke tests">
    ```bash theme={null}
    helm test tensormesh-operator -n tensormesh-operator
    ```

    Example:

    ```text theme={null}
    NAME: tensormesh-operator
    LAST DEPLOYED: Sat May 24 02:05:00 2026
    NAMESPACE: tensormesh-operator
    STATUS: deployed
    TEST SUITE:     tensormesh-operator-test-basic
    Last Started:   2026-05-24 02:08:00 +0000 UTC
    Last Completed: 2026-05-24 02:08:20 +0000 UTC
    Phase:          Succeeded
    ```
  </Step>
</Steps>

## Install modes

The chart toggles the operator and the engine independently, so the same chart supports
several deployment shapes:

| `operator.enabled` | `engine.enabled` | Use case                                                                     |
| ------------------ | ---------------- | ---------------------------------------------------------------------------- |
| `true` (default)   | `true` (default) | **Single-tenant.** Full stack from one release.                              |
| `true`             | `false`          | **Multi-tenant.** Operator only; tenants ship their own `LMCacheEngine` CRs. |
| `false`            | `true`           | **CR-only.** Operator already running cluster-wide.                          |
| `false`            | `false`          | **CRDs-only.** Use with `crds.enabled=true` to ship the schema alone.        |

## Customizing the install

The chart exposes its full configuration through `values.yaml`. Keep your overrides in a
values file (`-f my-values.yaml`) rather than `--set` flags — it's reproducible,
reviewable, and the place to **pin explicit image tags**. Common knobs:

```yaml my-values.yaml theme={null}
operator:
  replicas: 1                       # leader-elected; set to 2 for HA standby
  # operator image is supplied by the chart version (--version 0.4.5); override only to test a build
  resources:
    requests: { cpu: 10m, memory: 256Mi }
    limits: { cpu: 500m, memory: 1Gi }
  serviceMonitor:
    enabled: false                   # set true if you run Prometheus Operator

engine:
  enabled: true
  spec:
    l1:
      sizeGB: 60                     # per-node L1 cache size
    image:
      repository: lmcache/vllm-openai
      tag: v0.4.5                     # pin explicitly (matches chart version)
      pullPolicy: IfNotPresent
    nodeSelector:
      nvidia.com/gpu.present: "true" # default selector — override for custom GPU labels

crds:
  enabled: true                      # set false to manage CRDs out-of-band

openshift:
  enabled: false                     # set true on OpenShift
```

<Warning>
  **Pin to immutable tags, never `latest`.** Pin the **chart** with `--version 0.4.5` (which
  selects the matched operator image) and the **vLLM image** with `tag: v0.4.5`. With
  `imagePullPolicy: IfNotPresent`, a node that already cached an image under a mutable tag
  like `latest` keeps the stale copy — so `latest` can silently run an old build. Immutable
  tags make the version deterministic.
</Warning>

Pass it with `-f my-values.yaml` on the [install command above](#install).

<Tip>
  The chart ships a `values.schema.json`, so typos and wrong types are caught at parse time
  rather than at apply time. Run
  `helm show values oci://ghcr.io/tensormesh-production/charts/tensormesh-operator --version 0.4.5`
  to see every available key with its default.
</Tip>

## Upgrade

```bash theme={null}
helm upgrade tensormesh-operator \
  oci://ghcr.io/tensormesh-production/charts/tensormesh-operator \
  --version 0.4.5 -n tensormesh-operator \
  -f my-values.yaml
```

Roll back if needed:

```bash theme={null}
helm history tensormesh-operator -n tensormesh-operator
helm rollback tensormesh-operator <revision> -n tensormesh-operator
```

## Uninstall

```bash theme={null}
helm uninstall tensormesh-operator -n tensormesh-operator
```

A pre-delete hook removes the `LMCacheEngine` CR first so its finalizers run cleanly
before the operator Deployment is torn down.

<Warning>
  **The CRD survives `helm uninstall`** — same convention cert-manager and
  kube-prometheus-stack use. To remove it (and cascade-delete any remaining CRs):

  ```bash theme={null}
  kubectl delete crd lmcacheengines.lmcache.lmcache.ai
  ```
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Install on OpenShift" icon="circle-nodes" href="/operator/installation/openshift">
    Add the privileged SCC binding required for `hostIPC`.
  </Card>

  <Card title="Modify Existing vLLM Deployment" icon="wrench" href="/operator/installation/existing-deployment">
    Patch the workload you already run to consume the engine connection ConfigMap.
  </Card>

  <Card title="Troubleshooting" icon="stethoscope" href="/operator/installation/troubleshooting">
    Engine `Pending`, `ImagePullBackOff`, hung uninstalls, and more.
  </Card>
</CardGroup>
