KubeMQ
Deploy

Install KubeMQ with Helm

Deploy KubeMQ to Kubernetes for production with the Helm charts and operator, with a production readiness checklist.

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
  • KubeMQ license key — required for the cluster to start

Running KubeMQ for local development? Consider using Docker instead for a simpler setup.

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

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

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.

Terminal
helm repo add kubemq-charts https://kubemq-io.github.io/charts
helm repo update

Verify the repository was added successfully:

Terminal
helm search repo kubemq-charts

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

Install KubeMQ

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

Install KubeMQ CRDs

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

Terminal
helm install --create-namespace -n kubemq kubemq-crds kubemq-charts/kubemq-crds

Install KubeMQ controller

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

Terminal
helm install --wait -n kubemq kubemq-controller kubemq-charts/kubemq-controller

Install KubeMQ cluster

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

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.

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.

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:

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.

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.

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, 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.

Verify installation

Confirm that all KubeMQ pods are running and ready.

Terminal
kubectl get pods -n kubemq

Expected output:

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:

Terminal
kubectl get svc -n kubemq

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

Terminal
kubectl port-forward -n kubemq svc/kubemq-cluster 8080:8080

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

Charts & images

ChartPurpose
kubemq-crdsRegisters the KubemqCluster / KubemqConnector CRD schemas — no workloads
kubemq-controllerThe operator Deployment
kubemq-clusterRenders 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

The kubemq-controller chart's operator and server images default to the mutable image-tag :nextoperatorImage and kubemqImage in the 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.

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:

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

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:

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:

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.

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

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

Only key (your license, or keySecretRef — see 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:

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 and Storage Engines.

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:

values.yaml (kubemq)
key: "YOUR_LICENSE_KEY"

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.

Using a values file

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

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:

Terminal
helm install --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster -f values.yaml

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.

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.

Create a KubeMQ cluster

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

Basic cluster

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:

Terminal
kubectl apply -f kubemq-cluster.yaml

Standalone node (development)

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

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

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

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

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 for the full engine-selection model.

To enable Kafka on Kubernetes, see the zero-config recipe in Configure → Zero-config Kafka.

Check the status of your cluster after applying a manifest:

Terminal
kubectl get kubemqclusters -n kubemq

Manage the cluster

Scale

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

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

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.

Update configuration

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

Terminal
kubectl edit kubemqcluster kubemq-cluster -n kubemq

Or apply an updated manifest:

Terminal
kubectl apply -f kubemq-cluster-updated.yaml

Delete

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

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-by-field settings for the KubemqCluster spec — types, defaults, and valid values — live in the Configuration Reference, not here: Deployment & High Availability, Storage Engines, and Connectors.

Upgrade

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

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 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:

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

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 for how to find affected clusters before you upgrade.

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), 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).

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.

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.

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)

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

Remove KubeMQ from your cluster. If you installed the three charts separately, uninstall in reverse order — this is the recommended, default path:

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:

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.

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:

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:

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.

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.

To also remove the namespace:

Terminal
kubectl delete namespace kubemq

Production checklist

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

CheckWhat to do
Odd replica count, 3 or moreUse 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.
Shutdown grace periodSet 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.
Session affinity on AWS / GCPIf 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.
volume.size setSet 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.
Resource requests/limitsSet 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 exposureDecide LoadBalancer vs ClusterIP (vs NodePort) per interface (grpc.expose, rest.expose, api.expose) based on whether clients connect from outside the cluster.
Health probesSet 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 for the full liveness/readiness model.
Secure itTurn 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.
Image-tag policyThe server rides the mutable :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). For change control without digest-pinning, mirror :next to your own registry and promote on your own schedule.
PVC retentionDeleting 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

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

Eval / devProduction
Replicas1 (standalone: true)3, 5, or 7
CPU request / limit250m / 500m2 / 4
Memory request / limit512Mi / 1Gi4Gi / 8Gi
volume.size5Gi (or unset for legacy)50Gi+, fast-ssd storage class
Service exposureClusterIP / port-forwardLoadBalancer

Was this page helpful?

On this page