# Install KubeMQ with Helm (/docs/deploy/kubernetes-helm)



## Prerequisites [#prerequisites]

Before you begin, ensure you have the following installed and configured:

* **Kubernetes cluster** (v1.20 or later) — any distribution (EKS, GKE, AKS, k3s, minikube)
* **kubectl** — configured to access your cluster
* **Helm v3** — download from [helm.sh](https://helm.sh/docs/intro/install/)
* **KubeMQ license key** — required for the cluster to start

<Callout type="info">
  Running KubeMQ for local development? Consider using [Docker](/docs/deploy/docker) instead for a simpler setup.
</Callout>

## How it works [#how-it-works]

KubeMQ ships **four** Helm charts. The standard install path is a 3-step sequence:

1. **`kubemq-crds`** — CRD schema only. Registers the `KubemqCluster` (and `KubemqConnector`) resource types with Kubernetes. No workloads, no operator.
2. **`kubemq-controller`** — The operator Deployment. Watches for `KubemqCluster` resources and reconciles StatefulSets, Services, and configuration.
3. **`kubemq-cluster`** — Renders one `KubemqCluster` custom resource. This chart is a thin passthrough: nearly every Helm value you set becomes the identically-named field on the CR's `spec`, and the operator installed in step 2 does the actual reconciling.

A fourth chart, the &#x2A;*umbrella `kubemq`** chart, bundles CRDs + operator + one `KubemqCluster` CR into a single release — a one-shot alternative to running the three charts above in sequence. It requires only `key` (your license) to render.

All components are installed into the `kubemq` namespace.

## Add the KubeMQ Helm repository [#add-the-kubemq-helm-repository]

Register the KubeMQ Helm chart repository and update your local chart index.

<Callout type="warn">
  **The published chart channel is prerelease-only today.** Every published chart version carries a `-next.N` suffix (for example `3.0.0-next.3`), and Helm ignores prerelease versions unless you pass `--devel`. Every `helm search`, `helm install`, and `helm upgrade` command on this page needs `--devel` or it will fail to resolve a version.

  This chart-version prerelease channel (`-next.N`, gated by `--devel`) is separate from the image-tag `:next` you'll see later on this page (the mutable operator/server container image tag) — don't confuse the two "next" channels.
</Callout>

```bash title="Terminal"
helm repo add kubemq-charts https://kubemq-io.github.io/charts
helm repo update
```

Verify the repository was added successfully:

```bash title="Terminal"
helm search repo kubemq-charts --devel
```

You should see charts for `kubemq-crds`, `kubemq-controller`, `kubemq-cluster`, and the umbrella `kubemq` chart.

## Install KubeMQ [#install-kubemq]

KubeMQ installation on Kubernetes requires three Helm charts installed in order: CRDs, the controller (operator), and the cluster itself.

<Steps>
  <Step>
    ### Install KubeMQ CRDs [#install-kubemq-crds]

    The Custom Resource Definitions must be installed first. They define the `KubemqCluster` resource type that the operator manages.

    ```bash title="Terminal"
    helm install --create-namespace -n kubemq kubemq-crds kubemq-charts/kubemq-crds --devel
    ```
  </Step>

  <Step>
    ### Install KubeMQ controller [#install-kubemq-controller]

    The KubeMQ controller (operator) watches for `KubemqCluster` resources and manages the lifecycle of KubeMQ nodes.

    ```bash title="Terminal"
    helm install --wait -n kubemq kubemq-controller kubemq-charts/kubemq-controller --devel
    ```
  </Step>

  <Step>
    ### Install KubeMQ cluster [#install-kubemq-cluster]

    Deploy the KubeMQ cluster. Replace `YOUR_LICENSE_KEY` with your actual license key.

    ```bash title="Terminal"
    helm install --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster \
      --set key=YOUR_LICENSE_KEY \
      --devel
    ```

    By default, this creates a 3-node cluster. To deploy a single standalone node for development, add `--set standalone=true`.
  </Step>
</Steps>

## Verify installation [#verify-installation]

Confirm that all KubeMQ pods are running and ready.

```bash title="Terminal"
kubectl get pods -n kubemq
```

Expected output:

```text
NAME                                  READY   STATUS    RESTARTS   AGE
kubemq-controller-xxxxxxxxx-xxxxx     1/1     Running   0          2m
kubemq-cluster-0                      1/1     Running   0          1m
kubemq-cluster-1                      1/1     Running   0          1m
kubemq-cluster-2                      1/1     Running   0          1m
```

Check the services exposed by KubeMQ:

```bash title="Terminal"
kubectl get svc -n kubemq
```

To access the KubeMQ dashboard, port-forward the API service:

```bash title="Terminal"
kubectl port-forward -n kubemq svc/kubemq-cluster 8080:8080
```

Then open `http://localhost:8080` in your browser.

## Charts & images [#charts--images]

| Chart               | Purpose                                                                      |
| ------------------- | ---------------------------------------------------------------------------- |
| `kubemq-crds`       | Registers the `KubemqCluster` / `KubemqConnector` CRD schemas — no workloads |
| `kubemq-controller` | The operator Deployment                                                      |
| `kubemq-cluster`    | Renders one `KubemqCluster` CR (thin `spec.*` passthrough)                   |
| `kubemq` (umbrella) | CRDs + operator + one CR in a single release (`key` required)                |

All four charts are on the prerelease channel today — pass `--devel` on every `helm` command (see the callout above).

### Images and the `:next` channel [#images-and-the-next-channel]

The `kubemq-controller` chart's operator and server images default to the mutable image-tag `:next` — `operatorImage` and `kubemqImage` in the [Controller chart values](#controller-chart-values) below. `kubemqImage` isn't consumed by the operator's own container — it feeds the operator's `RELATED_IMAGE_KUBEMQ_CLUSTER` environment variable, which is what the operator uses as the container image when it renders a `KubemqCluster` StatefulSet.

## Configurable values [#configurable-values]

The snippets below are illustrative, one per chart — not exhaustive field tables. Field-by-field defaults and valid values for the `KubemqCluster` spec live in the Configuration Reference (linked throughout this section).

### Controller chart values [#controller-chart-values]

```yaml title="values.yaml (kubemq-controller)"
operatorImage: europe-docker.pkg.dev/kubemq/images/kubemq-operator:next
kubemqImage: europe-docker.pkg.dev/kubemq/images/kubemq:next
imagePullSecrets:
  - name: my-registry-secret

# Images for the connector workloads started by the operator
connectorTargetsImage: europe-docker.pkg.dev/kubemq/images/kubemq-targets:next
connectorSourcesImage: europe-docker.pkg.dev/kubemq/images/kubemq-sources:next
connectorBridgesImage: europe-docker.pkg.dev/kubemq/images/kubemq-bridges:next
```

`connectorTargetsImage`, `connectorSourcesImage`, and `connectorBridgesImage` set the images the operator uses for the Targets, Sources, and Bridges connector workloads. Pin these to your own registry mirror in production — don't rely on the chart's built-in default.

### Cluster chart values [#cluster-chart-values]

Only `key` (your license) and `imagePullSecrets` are native to this chart. Every other value is a **verbatim passthrough** to the `KubemqCluster` CR — a Helm value maps 1:1 to the `spec.*` field of the same name:

```yaml title="values.yaml (kubemq-cluster)"
key: "YOUR_LICENSE_KEY"

# Everything below passes through as spec.* on the KubemqCluster CR —
# see the Configuration Reference for the full field list.
replicas: 3
volume:
  size: 50Gi
```

For the complete field list, see [Deployment & High Availability](/docs/configure/reference/deployment) and [Storage Engines](/docs/configure/reference/storage-engines).

### Umbrella chart values [#umbrella-chart-values]

Only `key` is chart-native, and it's **required** — the release fails to render without it. The operator image is hardcoded in the chart's template, not driven by `operatorImage`/`kubemqImage`:

```yaml title="values.yaml (kubemq)"
key: "YOUR_LICENSE_KEY"
```

<Callout type="warn">
  Any value you set in a `values.yaml` **pins** that field permanently — once active, the server's built-in default no longer applies for that field, even across upgrades. Keep `values.yaml` minimal and only set what you intend to override long-term.
</Callout>

### Using a values file [#using-a-values-file]

For complex configurations, create a `values.yaml` file for the `kubemq-cluster` chart:

```yaml title="values.yaml"
key: "YOUR_LICENSE_KEY"
replicas: 3
standalone: false

volume:
  size: 20Gi
  storageClass: fast-ssd

grpc:
  expose: LoadBalancer
  port: 50000

api:
  expose: LoadBalancer
  port: 8080

resources:
  requestsCpu: "2"
  requestsMemory: 4Gi
  limitsCpu: "4"
  limitsMemory: 8Gi
```

Then install with:

```bash title="Terminal"
helm install --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster -f values.yaml --devel
```

## Declarative install (KubemqCluster CR) [#declarative-install-kubemqcluster-cr]

The `kubemq-controller` you installed above is the KubeMQ operator: it watches for `KubemqCluster` custom resources and reconciles the actual cluster state to match. Instead of passing cluster settings as Helm flags, you can describe the desired cluster declaratively in a `KubemqCluster` manifest and let the controller create and manage it.

<Callout type="info">
  This reuses the controller already running from the steps above — there is nothing extra to install. Apply a `KubemqCluster` resource and the operator does the rest.
</Callout>

### Create a KubeMQ cluster [#create-a-kubemq-cluster]

Define the desired cluster state in a `KubemqCluster` custom resource, then apply it with `kubectl`.

#### Basic cluster [#basic-cluster]

```yaml title="kubemq-cluster.yaml"
apiVersion: core.k8s.kubemq.io/v1alpha1
kind: KubemqCluster
metadata:
  name: kubemq-cluster
  namespace: kubemq
spec:
  replicas: 3
  key: "YOUR_LICENSE_KEY"
```

Apply the manifest:

```bash title="Terminal"
kubectl apply -f kubemq-cluster.yaml
```

#### Standalone node (development) [#standalone-node-development]

For local development or testing, deploy a single standalone node:

```yaml title="kubemq-standalone.yaml"
apiVersion: core.k8s.kubemq.io/v1alpha1
kind: KubemqCluster
metadata:
  name: kubemq-dev
  namespace: kubemq
spec:
  replicas: 1
  standalone: true
  key: "YOUR_LICENSE_KEY"
  grpc:
    expose: NodePort
    nodePort: 32000
  api:
    expose: NodePort
    nodePort: 32080
```

#### Production cluster [#production-cluster]

A production-ready configuration with resource limits, persistent storage, and LoadBalancer services:

```yaml title="kubemq-production.yaml"
apiVersion: core.k8s.kubemq.io/v1alpha1
kind: KubemqCluster
metadata:
  name: kubemq-production
  namespace: kubemq
spec:
  replicas: 3
  key: "YOUR_LICENSE_KEY"
  volume:
    size: 50Gi
    storageClass: fast-ssd
  grpc:
    expose: LoadBalancer
    port: 50000
    bodyLimit: 100000000
  rest:
    disabled: false
    expose: ClusterIP
    port: 9090
  api:
    expose: LoadBalancer
    port: 8080
  resources:
    requestsCpu: "2"
    requestsMemory: 4Gi
    limitsCpu: "4"
    limitsMemory: 8Gi
  health:
    initialDelaySeconds: 5
    periodSeconds: 10
    timeoutSeconds: 5
```

<Callout type="warn">
  **A `next`-engine cluster requires `spec.volume.size`.** A volumeless `next`-engine cluster raises the `EphemeralNextStore` warning — its durable data would otherwise live on ephemeral container storage. The storage engine itself (`legacy` vs `next`) is **established once, at cluster creation**, and is immutable thereafter — there is no in-place migration between engines. See [Storage Engines](/docs/configure/reference/storage-engines) for the full engine-selection model.
</Callout>

To enable Kafka on Kubernetes, see the zero-config recipe in [Configure → Zero-config Kafka](/docs/configure/kubernetes#zero-config-kafka).

Check the status of your cluster after applying a manifest:

```bash title="Terminal"
kubectl get kubemqclusters -n kubemq
```

### Manage the cluster [#manage-the-cluster]

#### Scale [#scale]

Update the `replicas` field in your manifest and re-apply, or patch the resource directly:

```bash title="Terminal"
kubectl patch kubemqcluster kubemq-cluster -n kubemq \
  --type merge -p '{"spec":{"replicas":5}}'
```

<Callout type="warn">
  For clustered mode (non-standalone), use an **odd** number of replicas (3, 5, 7) to maintain consensus quorum. With even numbers, the cluster cannot tolerate as many failures.
</Callout>

#### Update configuration [#update-configuration]

Edit the `KubemqCluster` resource to change configuration. The operator reconciles changes automatically.

```bash title="Terminal"
kubectl edit kubemqcluster kubemq-cluster -n kubemq
```

Or apply an updated manifest:

```bash title="Terminal"
kubectl apply -f kubemq-cluster-updated.yaml
```

#### Delete [#delete]

Remove a KubeMQ cluster while keeping the controller running (so it can still manage other clusters):

```bash title="Terminal"
kubectl delete kubemqcluster kubemq-cluster -n kubemq
```

This removes all pods and services for that cluster. Persistent volume claims are retained by default.

### Field reference [#field-reference]

Field-by-field settings for the `KubemqCluster` spec — types, defaults, and valid values — live in the Configuration Reference, not here: [Deployment & High Availability](/docs/configure/reference/deployment), [Storage Engines](/docs/configure/reference/storage-engines), and [Connectors](/docs/configure/reference/connectors).

## Upgrade [#upgrade]

Upgrade an existing KubeMQ installation to a new version or change configuration:

```bash title="Terminal"
helm upgrade --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster \
  --set key=YOUR_LICENSE_KEY \
  --reuse-values \
  --devel
```

The `--reuse-values` flag preserves your existing configuration and only applies the changes you specify.

### Helm does not upgrade CRDs [#helm-does-not-upgrade-crds]

Helm **never** upgrades CRDs shipped in a chart's `crds/` directory — running `helm upgrade` against `kubemq-crds` will not update an already-installed `KubemqCluster` CRD. The primary CRD-upgrade path is applying the canonical CRD manifest directly with `kubectl apply -f <path to the current KubemqCluster CRD manifest>`, not `helm upgrade`.

### Upgrade order [#upgrade-order]

Upgrade in this order:

1. **CRDs and the operator, together, in one step.** The window where CRDs are upgraded but the operator is still old is transient — it is not a resting state you should pause in.
2. **Server images.** Because the server rides the mutable `:next` tag rather than a pinned semantic version, "upgrading" the server means **rolling the pods so they re-pull the current `:next` image** — there's no version number to bump to.
3. **Charts** — the Helm release metadata itself (`--reuse-values`/`--set` as needed).

<Callout type="warn">
  **An old operator strips fields it doesn't know about.** Don't add `spec.env`, `spec.envFromSecrets`, `expose`, or `nodePort` — or rely on the engine writing itself back onto the CR — until the operator itself has been upgraded. An old operator **permanently** strips unknown fields the first time it reconciles a resource, it doesn't just ignore them once.
</Callout>

<Callout type="warn">
  **Operator rollback is not engine-safe.** If you roll the operator back, pin `spec.store.engine: next` explicitly first — a clustered `next` CR left to auto-detect will crash-loop under an old operator. The engine-establishment annotation on the CR survives rollback (it's untyped metadata an old operator doesn't touch), so re-upgrading the operator later re-derives the engine correctly.
</Callout>

An explicit `legacy` CR gets **one** checksum-triggered rolling restart on upgrade. On any CR where the engine was previously unset, engine write-back also **arms the replicas-freeze**: once the operator establishes the engine as `next`, `spec.replicas` becomes immutable on that CR.

### v2.8.x → v2.9.0 (breaking) [#v28x--v290-breaking]

The six wire-protocol connectors — MQTT, AMQP 0.9.1, AMQP 1.0, STOMP, AWS, and GCP — changed their CRD field from `disabled: bool` to `enabled: *bool`. This requires all three components upgraded in lockstep: `kubemq-crds` ≥ 2.13.0, `kubemq-operator` ≥ 1.19.0, and the server ≥ v3.0.0-b7. Kafka's `enabled: *bool` opt-in field is unaffected — it already used this shape, consistent with the post-v2.9 pattern.

## Uninstall [#uninstall]

Remove KubeMQ from your cluster. Uninstall in reverse order:

```bash title="Terminal"
helm uninstall -n kubemq kubemq-cluster
helm uninstall -n kubemq kubemq-controller
helm uninstall -n kubemq kubemq-crds
```

<Callout type="warn">
  Uninstalling deletes all KubeMQ pods and services. Persistent volume claims (PVCs) are retained by default. Delete them manually if you want to remove all data: `kubectl delete pvc -n kubemq -l app=kubemq-cluster`.
</Callout>

To also remove the namespace:

```bash title="Terminal"
kubectl delete namespace kubemq
```

## Production checklist [#production-checklist]

Before you point real traffic at a cluster, run down this list.

| Check                        | What to do                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Odd replica count**        | Use **3, 5, or 7** replicas for clustered mode — an odd number keeps consensus quorum well-defined. Even counts don't buy you extra fault tolerance and can complicate quorum math.                                                                                                                                                                                                                                                             |
| **`volume.size` set**        | Set `spec.volume.size` (or the `volume.size` Helm value) explicitly. It's **required for the `next` storage engine** — a volumeless `next` cluster raises the `EphemeralNextStore` warning because its durable data would otherwise live on ephemeral container storage. See [Storage Engines](/docs/configure/reference/storage-engines).                                                                                                      |
| **Resource requests/limits** | Set `resources.requestsCpu` / `requestsMemory` / `limitsCpu` / `limitsMemory` (or the matching `spec.resources.*` fields). There are no built-in defaults — an unset field is simply omitted from the pod spec. See the sizing table below.                                                                                                                                                                                                     |
| **Service exposure**         | Decide `LoadBalancer` vs `ClusterIP` (vs `NodePort`) per interface (`grpc.expose`, `rest.expose`, `api.expose`) based on whether clients connect from outside the cluster.                                                                                                                                                                                                                                                                      |
| **Health probes**            | Set `spec.health.enabled: true` so the operator wires a liveness probe (`/health` on the API port) and `API_BIND_ADDRESS=0.0.0.0` so the kubelet can reach it. Off by default.                                                                                                                                                                                                                                                                  |
| **Secure it**                | Turn on **authentication** (JWT or OIDC), **authorization** (policy-based roles), and **TLS/mTLS** before exposing the cluster beyond a trusted network. All off by default. Full field-by-field reference: [Security](/docs/configure/reference/security).                                                                                                                                                                                     |
| **Image-tag policy**         | The server rides the mutable &#x2A;*`:next`** tag — that's the mandatory release channel, not a placeholder. Own the rolling model: "upgrading" means re-pulling `:next` and rolling the pods, there's no version number to bump to. For change control, **mirror `:next` to your own registry** and promote on your own schedule. **Do not** pin the image by digest or semver — that channel doesn't exist today and breaks the upgrade path. |
| **PVC retention**            | Deleting a `KubemqCluster` (or uninstalling the chart) retains PVCs by default — data survives. Delete them explicitly (`kubectl delete pvc -n kubemq -l app=kubemq-cluster`) if you actually want to wipe storage.                                                                                                                                                                                                                             |

### Sizing: eval vs. production [#sizing-eval-vs-production]

Illustrative starting points, not hard requirements — tune to your message volume, payload size, and retention needs.

|                        | Eval / dev                    | Production                        |
| ---------------------- | ----------------------------- | --------------------------------- |
| Replicas               | 1 (`standalone: true`)        | 3, 5, or 7                        |
| CPU request / limit    | `250m` / `500m`               | `2` / `4`                         |
| Memory request / limit | `512Mi` / `1Gi`               | `4Gi` / `8Gi`                     |
| `volume.size`          | `5Gi` (or unset for `legacy`) | `50Gi`+, `fast-ssd` storage class |
| Service exposure       | `ClusterIP` / port-forward    | `LoadBalancer`                    |

## Related [#related]

<Cards>
  <Card title="Configure KubeMQ" href="/docs/configure/kubernetes">
    Task guide: values.yaml, the KubemqCluster CR, single-node vs HA, and interface exposure.
  </Card>

  <Card title="Storage Engines" href="/docs/configure/reference/storage-engines">
    Reference: the legacy and next persistence engines, zero-config engine selection, and durability trade-offs — types, defaults, and valid values.
  </Card>

  <Card title="Deployment & HA" href="/docs/configure/reference/deployment">
    Reference: Kubernetes packaging, replicas/standalone, resources, health probes, and Service exposure — types, defaults, and valid values.
  </Card>
</Cards>
