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:
kubemq-crds— CRD schema only. Registers theKubemqCluster(andKubemqConnector) resource types with Kubernetes. No workloads, no operator.kubemq-controller— The operator Deployment. Watches forKubemqClusterresources and reconciles StatefulSets, Services, and configuration.kubemq-cluster— Renders oneKubemqClustercustom resource. This chart is a thin passthrough: nearly every Helm value you set becomes the identically-named field on the CR'sspec, 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.
helm repo add kubemq-charts https://kubemq-io.github.io/charts
helm repo updateVerify the repository was added successfully:
helm search repo kubemq-chartsYou 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.
helm install --create-namespace -n kubemq kubemq-crds kubemq-charts/kubemq-crdsInstall KubeMQ controller
The KubeMQ controller (operator) watches for KubemqCluster resources and manages the lifecycle of KubeMQ nodes.
helm install --wait -n kubemq kubemq-controller kubemq-charts/kubemq-controllerInstall KubeMQ cluster
Deploy the KubeMQ cluster. Replace YOUR_LICENSE_KEY with your actual license key.
helm install --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster \
--set key=YOUR_LICENSE_KEYBy 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.
kubectl create secret generic kmq-license -n kubemq --from-literal=key=YOUR_LICENSE_KEYThen install (or apply a CR) with keySecretRef in place of key:
helm install --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster \
--set keySecretRef=kmq-licenseThe 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.
kubectl get pods -n kubemqExpected 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 1mCheck the services exposed by KubeMQ:
kubectl get svc -n kubemqTo access the KubeMQ dashboard, port-forward the API service:
kubectl port-forward -n kubemq svc/kubemq-cluster 8080:8080Then open http://localhost:8080 in your browser.
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
The kubemq-controller chart's operator and server images default to the mutable image-tag :next — operatorImage 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:
docker pull europe-docker.pkg.dev/kubemq/images/kubemq:next && \
docker inspect --format='{{index .RepoDigests 0}}' europe-docker.pkg.dev/kubemq/images/kubemq:nextThere 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 schemaspec.image.image is one full reference — repository, and tag or digest, together:
image:
image: europe-docker.pkg.dev/kubemq/images/kubemq:next@sha256:<digest>
pullPolicy: IfNotPresentKeep 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
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:nextconnectorTargetsImage, 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:
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: 50GiFor 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:
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:
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: 8GiThen install with:
helm install --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster -f values.yamlDeclarative 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
apiVersion: core.k8s.kubemq.io/v1beta1
kind: KubemqCluster
metadata:
name: kubemq-cluster
namespace: kubemq
spec:
replicas: 3
key: "YOUR_LICENSE_KEY"Apply the manifest:
kubectl apply -f kubemq-cluster.yamlStandalone node (development)
For local development or testing, deploy a single standalone node:
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: 32080Production cluster
A production-ready configuration with resource limits, persistent storage, and LoadBalancer services:
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: 5A 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:
kubectl get kubemqclusters -n kubemqManage the cluster
Scale
Update the replicas field in your manifest and re-apply, or patch the resource directly:
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.
kubectl edit kubemqcluster kubemq-cluster -n kubemqOr apply an updated manifest:
kubectl apply -f kubemq-cluster-updated.yamlDelete
Remove a KubeMQ cluster while keeping the controller running (so it can still manage other clusters):
kubectl delete kubemqcluster kubemq-cluster -n kubemqThis 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:
helm upgrade --wait -n kubemq kubemq-cluster kubemq-charts/kubemq-cluster \
--set key=YOUR_LICENSE_KEY \
--reuse-valuesThe --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:
kubectl apply -f https://raw.githubusercontent.com/kubemq-io/charts/master/kubemq-crds/crds/kubemqclusters.core.k8s.kubemq.io.crd.yamlCharts 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:
- 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.
- Server images. Because the server rides the mutable
:nexttag rather than a pinned semantic version, "upgrading" the server means rolling the pods so they re-pull the current:nextimage — there's no version number to bump to. If you've pinned the image to a digest for reproducibility (see Images and the:nextchannel), 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. - Charts — the Helm release metadata itself (
--reuse-values/--setas 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:
helm uninstall -n kubemq kubemq-cluster
helm uninstall -n kubemq kubemq-controller
helm uninstall -n kubemq kubemq-crdsIf you installed the umbrella kubemq chart, you can remove the whole release in one step instead:
helm uninstall kubemq -n kubemqThe 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:
helm upgrade kubemq kubemq-charts/kubemq -n kubemq --reuse-values \
--set preDelete.image=<a-reachable-kubectl-image>
helm uninstall kubemq -n kubemqor disable the hook and delete the cluster yourself first:
helm upgrade kubemq kubemq-charts/kubemq -n kubemq --reuse-values \
--set preDelete.enabled=false
kubectl delete kubemqcluster <name> -n kubemq
helm uninstall kubemq -n kubemqUntil 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:
kubectl delete namespace kubemqProduction 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. |
| 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. |
| 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. |
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. |
| 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 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. |
| Image-tag policy | The 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 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
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
Configure KubeMQ
Task guide: values.yaml, the KubemqCluster CR, single-node vs HA, and interface exposure.
Storage Engines
Reference: the legacy and next persistence engines, zero-config engine selection, and durability trade-offs — types, defaults, and valid values.
Deployment & HA
Reference: Kubernetes packaging, replicas/standalone, resources, health probes, and Service exposure — types, defaults, and valid values.
Was this page helpful?