# Install KubeMQ with Helm (/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](/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="info">
  The charts are published as stable GA releases, each versioned independently — `kubemq-crds` **3.2.0**, `kubemq-cluster` **3.2.0**, the umbrella `kubemq` **3.2.0**, and `kubemq-controller` **2.0.0**. Install with plain `helm` commands — no `--devel` flag is needed.

  The chart **version** (each chart's own semver, e.g. `kubemq-cluster` at `3.2.0`) is separate from the chart's **appVersion** — the product line it tracks. appVersion is **3.0.0** (the server) for `kubemq-crds`, `kubemq-cluster`, and the umbrella `kubemq`; it's **2.0.0** (the operator) for `kubemq-controller` — appVersion is per-chart, not one shared number, and it names the GA line rather than the exact build (the current operator build on `:next` is **v2.3.0**). Both are separate again from the operator/server container **image tag** `:next` you'll see later on this page — the server rides the mutable `:next` image tag rather than a pinned version. Don't confuse the three.
</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
```

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
    ```
  </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
    ```
  </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
    ```

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

## Supplying the license from a Secret [#supplying-the-license-from-a-secret]

Passing `--set key=YOUR_LICENSE_KEY` stores the raw license as a Helm value (recoverable via `helm get values`) and on the `KubemqCluster` object. To keep the token out of cluster state — the recommended path for **GitOps**, where manifests live in git — put the license in a Kubernetes Secret and reference it instead.

```bash title="Terminal"
kubectl create secret generic kmq-license -n kubemq --from-literal=key=YOUR_LICENSE_KEY
```

Then install (or apply a CR) with `keySecretRef` in place of `key`:

```bash title="Terminal"
helm install --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster \
  --set keySecretRef=kmq-license
```

The operator resolves the key from the Secret at reconcile time; the raw token never appears in the `KubemqCluster` object or the Helm release values.

<Callout type="info">
  `keySecretRef` is **opt-in** — the literal `key` / `--set key=` path is unchanged and remains the default. Set only one: providing both a literal `key` and `keySecretRef` is rejected. The optional `keySecretKey` overrides the Secret's data key (defaults to `key`); `licenseSecretRef` / `licenseSecretKey` behave the same for `spec.license`.
</Callout>

<Callout type="warn">
  **Operator logs from before v2.3.0 contain the activation key in plaintext.** Earlier operator builds printed the full key at INFO level on every activation. As of **v2.3.0** the operator logs only a last-4 fingerprint, everywhere — including inside licence-store error strings.

  If you have archived operator logs, or a log aggregator that ingested them, &#x2A;*treat those archives as containing the activation key and rotate it.** Anyone with `kubectl logs` access on the operator over that window had the token.
</Callout>

## 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 published as stable GA releases — `kubemq-crds` `3.2.0`, `kubemq-cluster` `3.2.0`, the umbrella `kubemq` `3.2.0`, and `kubemq-controller` `2.0.0` — a plain `helm install` resolves them, no `--devel` needed (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.

<Callout type="info">
  **Tracking `:next` vs. pinning a digest.** Tracking `:next` — letting each pod roll re-pull the tag — is the default and recommended path; it always lands the current build. If a deployment needs byte-for-byte reproducibility instead, you may pin the same image to an immutable digest, `europe-docker.pkg.dev/kubemq/images/kubemq:next@sha256:<digest>`. The tag is still `:next`, so this stays compatible with the `:next`-only policy — it isn't a semver pin. The tradeoff: once pinned, a pod roll no longer adopts a new `:next` build on its own. You own the upgrade — re-pull `:next`, read its current digest, and re-pin:

  ```bash title="Terminal"
  docker pull europe-docker.pkg.dev/kubemq/images/kubemq:next && \
    docker inspect --format='{{index .RepoDigests 0}}' europe-docker.pkg.dev/kubemq/images/kubemq:next
  ```
</Callout>

<Callout type="warn">
  **There is no `image.tag` — set the whole reference in `image.image`.** The `kubemq-cluster` chart passes **every** value straight through into the `KubemqCluster` object, so `--set image.tag=…` reaches the API server as an undeclared field and the install fails outright:

  ```text
  server-side apply failed … .spec.image.tag: field not declared in schema
  ```

  `spec.image.image` is **one full reference** — repository, and tag or digest, together:

  ```yaml title="values.yaml (kubemq-cluster)"
  image:
    image: europe-docker.pkg.dev/kubemq/images/kubemq:next@sha256:<digest>
    pullPolicy: IfNotPresent
  ```

  Keep the tag `:next` — that is the mandatory release channel, and a digest appended to it is the supported way to freeze a build. **Don't substitute a semver tag** such as `:v3.1.3`: the server does not publish one, and a reference that names it will not resolve.
</Callout>

## 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, or `keySecretRef` — see [Supplying the license from a Secret](#supplying-the-license-from-a-secret)) 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](/configure/reference/deployment) and [Storage Engines](/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
```

## 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/v1beta1
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/v1beta1
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/v1beta1
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` — and a new cluster on a clean store comes up on `next`.** So this applies to a default install, not just to clusters that asked for `next`. 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; to run `legacy` you name it explicitly at creation. See [Storage Engines](/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](/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](/configure/reference/deployment), [Storage Engines](/configure/reference/storage-engines), and [Connectors](/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
```

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. That is deliberate: it is what stops a `helm uninstall` from cascade-deleting every `KubemqCluster` and its data. The CRD-upgrade path is applying the canonical manifest directly, not `helm upgrade`:

```bash title="Terminal"
kubectl apply -f https://raw.githubusercontent.com/kubemq-io/charts/master/kubemq-crds/crds/kubemqclusters.core.k8s.kubemq.io.crd.yaml
```

<Callout type="warn">
  **Charts 3.2.0 carries a CRD schema change — apply it.** The new rule rejects a `KubemqCluster` that sets **both** an `oidc` block and the JWT `key` / `signatureType` fields, a combination that previously produced a broker with **no authentication at all**. Skipping the apply doesn't break anything: it only means the API server won't enforce the rule at admission, and the operator's own check catches the same case at reconcile instead. See [the authentication callout](/configure/reference/security#authentication) for how to find affected clusters before you upgrade.
</Callout>

### 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. If you've pinned the image to a digest for reproducibility (see [Images and the `:next` channel](#images-and-the-next-channel)), a plain pod roll won't pick up a new build on its own — re-pull `:next`, read its current digest, and re-pin it to upgrade.
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. An **established** cluster keeps the engine it already has — the operator names it explicitly and nothing rolls. A **fresh** cluster with no engine on record is created with `STORE_ENGINE=auto`, and the server resolves it at boot. Once the engine is established as `next`, the **replicas-freeze** arms: `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. If you installed the three charts separately, uninstall in reverse order — this is the recommended, default path:

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

If you installed the umbrella `kubemq` chart, you can remove the whole release in one step instead:

```bash title="Terminal"
helm uninstall kubemq -n kubemq
```

The umbrella chart ships a `pre-delete` hook designed to delete the `KubemqCluster` resource and let the operator drain its finalizer *before* the operator itself is removed — that's what makes the one-step path possible.

{/* TODO(CH-PREDEL): drop this caveat when the chart default preDelete.image is fixed */}

<Callout type="warn">
  **This isn't safe out-of-the-box today.** The hook's default `preDelete.image` (`europe-docker.pkg.dev/kubemq/images/kubectl:latest`) doesn't resolve, so a plain `helm uninstall kubemq -n kubemq` will **hang** waiting on the hook. `helm uninstall` doesn't take `--set` itself, so override the value with an upgrade first, then uninstall — either point the hook at a `kubectl` image you know resolves:

  ```bash title="Terminal"
  helm upgrade kubemq kubemq-charts/kubemq -n kubemq --reuse-values \
    --set preDelete.image=<a-reachable-kubectl-image>
  helm uninstall kubemq -n kubemq
  ```

  or disable the hook and delete the cluster yourself first:

  ```bash title="Terminal"
  helm upgrade kubemq kubemq-charts/kubemq -n kubemq --reuse-values \
    --set preDelete.enabled=false
  kubectl delete kubemqcluster <name> -n kubemq
  helm uninstall kubemq -n kubemq
  ```

  Until this is fixed, prefer the 3-step reverse uninstall above.
</Callout>

<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, 3 or more**  | Use **3, 5, or 7** replicas for clustered mode. The operator creates a `PodDisruptionBudget` only from 3 up — below that it creates none and emits an `UnsupportedReplicaCount` warning event, because the only correct budget at 2 replicas would block every node drain. An even count gets an `EvenReplicaCount` warning: 4 tolerates the same single failure as 3, at the cost of a node and a copy of the data. See [Disruption budget & pod spread](/configure/reference/deployment#disruption-budget--pod-spread).                                                                                                                                                                                                                                                                                            |
| **Shutdown grace period**         | Set `terminationGracePeriodSeconds` to at least **45**. The server splits the grace between connector teardown — which **requeues in-flight messages that would otherwise be lost** — and store shutdown. At the Kubernetes default of 30s the requeue backstop is only **1 second**; 45s funds the full **12-second** backstop. See [Shutdown grace period](/configure/reference/deployment#shutdown-grace-period).                                                                                                                                                                                                                                                                                                                                                                                                 |
| **Session affinity on AWS / GCP** | If you enable the **AWS** or **GCP Pub/Sub** connector on more than one replica, set `sessionAffinity: ClientIP`. Their delete/ack tokens are bound to the replica that issued them — without stickiness an SQS queue never drains and GCP acks are refused. Prefer ingress cookie affinity where clients share a NAT or egress IP. See [Service exposure & session affinity](/configure/reference/connectors#service-exposure--session-affinity).                                                                                                                                                                                                                                                                                                                                                                   |
| **`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](/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. The API port also serves a `/ready` **readiness** endpoint that gates when the pod can actually serve — for a clustered `next`-engine cold start, it holds the pod not-Ready until cluster quorum forms. See [Health probe](/configure/reference/deployment#health-probe) for the full liveness/readiness model.                                                                                                                                                                                                                                                                                                                      |
| **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](/configure/reference/security).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| **Image-tag policy**              | The server rides the mutable &#x2A;*`:next`** tag — that's the mandatory release channel, not a placeholder. Tracking `:next` (re-pulling on every pod roll) is the default and recommended path — own the rolling model: "upgrading" means re-pulling `:next` and rolling the pods, there's no version number to bump to. For byte-for-byte reproducibility you may instead pin `…/kubemq:next@sha256:<digest>` — the tag stays `:next`, so it's compatible with this policy — but then you own upgrades: a pod roll won't adopt a new `:next` build, so you upgrade by re-pulling `:next` and re-pinning the new digest (see [Images and the `:next` channel](#images-and-the-next-channel)). For change control without digest-pinning, **mirror `:next` to your own registry** and promote on your own schedule. |
| **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="/configure/kubernetes">
    Task guide: values.yaml, the KubemqCluster CR, single-node vs HA, and interface exposure.
  </Card>

  <Card title="Storage Engines" href="/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="/configure/reference/deployment">
    Reference: Kubernetes packaging, replicas/standalone, resources, health probes, and Service exposure — types, defaults, and valid values.
  </Card>
</Cards>
