# Install on Kubernetes (/deploy/install/kubernetes)



This page shows how to install the KubeMQ operator and a cluster of three servers on Kubernetes, with kmq or with Helm. When you finish, `kmq license` reports the license on every server, a test message round-trips, and you know the address your applications use. Time: about 30 minutes.

Hardening for production traffic is on the [Production checklist](/deploy/production-checklist).

These commands are for bash on macOS and Linux. On Windows, run them in WSL (Windows Subsystem for Linux), or use the kmq tab of [Try KubeMQ](/deploy/quickstart), which runs natively on Windows.

The cluster `messaging`, as the operator runs it:

<Mermaid
  chart="graph TB
  Apps[&#x22;Your applications&#x22;]

  subgraph Namespace[&#x22;Namespace kubemq&#x22;]
    subgraph OperatorRelease[&#x22;Release kubemq-operator&#x22;]
      Operator[&#x22;Operator&#x22;]
    end
    subgraph ClusterRelease[&#x22;Release messaging&#x22;]
      Cluster[&#x22;KubemqCluster messaging&#x22;]
    end
    License[&#x22;License Secret&#x22;]
    Services[&#x22;Services<br/>gRPC · REST · Kafka · AMQP&#x22;]
    subgraph Servers[&#x22;Servers&#x22;]
      Server0[&#x22;messaging-0&#x22;]
      Server1[&#x22;messaging-1&#x22;]
      Server2[&#x22;messaging-2&#x22;]
    end
    Volume0[&#x22;Volume&#x22;]
    Volume1[&#x22;Volume&#x22;]
    Volume2[&#x22;Volume&#x22;]
  end

  License --> Cluster
  Operator -. &#x22;reads&#x22; .-> Cluster
  Operator -. &#x22;runs&#x22; .-> Servers
  Apps --> Services
  Services --> Servers
  Server0 --- Volume0
  Server1 --- Volume1
  Server2 --- Volume2

  class Operator,Cluster,Services,Server0,Server1,Server2 broker
  class Apps client
  class Volume0,Volume1,Volume2,License data"
/>

## Before you start [#before-you-start]

Create a private folder, readable only by you, and work inside it:

```bash
mkdir -p -m 700 kubemq-private
```

```bash
cd kubemq-private
```

* kubectl and Helm, with a context for the target cluster. The kmq tab runs them for you.
* kmq, installed as in [Try KubeMQ](/deploy/quickstart#install-kmq). Both tabs use it.
* Permission to create custom resource definitions and cluster roles.
* A storage class that creates volumes on demand. Note its name:

```bash
kubectl --context YOUR_KUBE_CONTEXT get storageclass
```

Replace: `YOUR_KUBE_CONTEXT` — your cluster's kubectl context, from `kubectl config get-contexts`.

You should see:

```text title="Output"
HARNESS_OUTPUT_PENDING
```

Node and network needs: [Requirements and supported setups](/deploy/install/requirements#kubernetes).

## Add your license [#add-your-license]

<Tabs groupId="license" items="[&#x22;Trial key&#x22;, &#x22;License key&#x22;]">
  <Tab value="Trial key">
    `--accept-terms` records that you accept the trial terms  and the [privacy notice](https://kubemq.io/privacy-policy-2/); an AI agent must ask you first.

    ```bash
    kmq trial request --email YOUR_EMAIL --name "YOUR_NAME" --company "YOUR_COMPANY" --platform kubernetes --accept-terms
    ```

    Replace: `YOUR_EMAIL`, `YOUR_NAME`, `YOUR_COMPANY` — your work email, full name and company.

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```

    Type the emailed code yourself, never in chat.

    ```bash
    kmq trial verify --request YOUR_REQUEST_ID
    ```

    Replace: `YOUR_REQUEST_ID` — the `request_id` printed above.

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```

    ```bash
    kmq trial claim --request YOUR_REQUEST_ID
    ```

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```

    The key covers one cluster of 3 servers and binds to the first cluster it starts on; to move it, contact KubeMQ through [Plans compared](/licensing#buy).
  </Tab>

  <Tab value="License key">
    Use the key from your KubeMQ order email. It must cover at least 3 servers.

    Save the license key as `license.key` in this folder. The file holds only the key, on one line, with no quotes and no `KUBEMQ_LICENSE_KEY=` prefix. Then make it readable by you alone:

    ```bash
    chmod 600 license.key
    ```

    This file is a secret. Do not commit it, paste it into chat, or attach it to a support ticket.

    ```bash
    kmq license import --file license.key
    ```

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```
  </Tab>
</Tabs>

kmq printed a license reference, `credential` or `reference`. The kmq tab uses it and creates its own license Secret; Helm step 2 stores `license.key` as the Secret `messaging-license`, exporting a trial key first.

## Steps [#steps]



<Tabs groupId="install-tool" items="[&#x22;kmq&#x22;, &#x22;Helm&#x22;]">
  <Tab value="kmq">
    <Steps>
      <Step>
        ### Write the install file [#write-the-install-file]

        In `kubemq-private`:

        ```json title="kubernetes.json"
        {
          "schema_version": 1,
          "goal": "production",
          "target": "kubernetes",
          "name": "messaging",
          "credential": "YOUR_LICENSE_REF",
          "kubernetes": {
            "context": "YOUR_KUBE_CONTEXT",
            "namespace": "kubemq",
            "operator_release": "kubemq-operator",
            "cluster_release": "messaging",
            "server_count": 3,
            "storage_class": "YOUR_STORAGE_CLASS",
            "volume_size": "20Gi"
          }
        }
        ```

        Replace: `YOUR_LICENSE_REF` — your license reference; `YOUR_KUBE_CONTEXT` — your cluster's kubectl context; `YOUR_STORAGE_CLASS` — the storage class you noted.
      </Step>

      <Step>
        ### Check the cluster and save a plan [#check-the-cluster-and-save-a-plan]

        ```bash
        kmq deploy prepare --input kubernetes.json --out prepared.json --deadline 10m
        ```

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```

        It creates the namespace, a license Secret and a management certificate Secret that kmq trusts.

        ```bash
        kmq deploy plan --input prepared.json --out plan.json
        ```

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```
      </Step>

      <Step>
        ### Install the operator and the cluster [#install-the-operator-and-the-cluster]

        ```bash
        kmq deploy apply --plan plan.json --deadline 15m
        ```

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```

        After an interruption, run it again; it resumes.
      </Step>
    </Steps>
  </Tab>

  <Tab value="Helm">
    <Steps>
      <Step>
        ### Create the namespace [#create-the-namespace]

        ```bash
        kubectl --context YOUR_KUBE_CONTEXT create namespace kubemq --dry-run=client -o yaml | kubectl --context YOUR_KUBE_CONTEXT apply -f -
        ```

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```
      </Step>

      <Step>
        ### Store the license as a Secret [#store-the-license-as-a-secret]

        With a trial key, export it first (delete an old `license.key`; kmq never overwrites):

        ```bash
        kmq license export --credential YOUR_LICENSE_REF --out license.key
        ```

        Replace: `YOUR_LICENSE_REF` — your license reference.

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```

        Create the Secret; the same command replaces it:

        ```bash
        kubectl --context YOUR_KUBE_CONTEXT -n kubemq create secret generic messaging-license --from-file=licenseKey=license.key --dry-run=client -o yaml | kubectl --context YOUR_KUBE_CONTEXT apply -f -
        ```

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```

        <Callout type="warn" title="Security: keep the key out of Helm values">
          Never use `--set licenseKey=`, `--from-literal` or a key in `cluster-values.yaml`: Helm stores values in the cluster.
        </Callout>
      </Step>

      <Step>
        ### Install the operator [#install-the-operator]

        ```bash
        helm repo add kubemq-next https://kubemq-io.github.io/charts-next
        ```

        ```bash
        helm repo update kubemq-next
        ```

        ```bash
        helm upgrade --install kubemq-operator kubemq-next/kubemq-next \
          --kube-context YOUR_KUBE_CONTEXT \
          -n kubemq \
          --set operator.enabled=true \
          --set cluster.enabled=false \
          --wait
        ```

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```
      </Step>

      <Step>
        ### Install the cluster [#install-the-cluster]

        In `kubemq-private`:

        ```yaml title="cluster-values.yaml"
        operator:
          enabled: false
        cluster:
          enabled: true
        fullnameOverride: messaging
        replicas: 3
        licenseKeySecretRef:
          name: messaging-license
          key: licenseKey
        volume:
          size: 20Gi
          storageClass: YOUR_STORAGE_CLASS
        api:
          expose: ClusterIP
          auth:
            enable: true
            adminUsername: admin
        env:
          STORE_NEXT_ACK_POLICY: "strict"
        ```

        Replace: `YOUR_STORAGE_CLASS` — the storage class you noted.

        License fields, resources and node spread: [Deployment & High Availability](/configure/reference/deployment#license).

        ```bash
        helm upgrade --install messaging kubemq-next/kubemq-next \
          --kube-context YOUR_KUBE_CONTEXT \
          -n kubemq \
          -f cluster-values.yaml
        ```

        You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```
      </Step>
    </Steps>
  </Tab>
</Tabs>

Then wait until every server is ready:

```bash
kubectl --context YOUR_KUBE_CONTEXT -n kubemq wait --for=condition=Ready kubemqclusters.next.kubemq.io/messaging --timeout=10m
```

You should see:

```text title="Output"
HARNESS_OUTPUT_PENDING
```

Check the license:

```bash
kmq license --kube-context YOUR_KUBE_CONTEXT
```

You should see:

```text title="Output"
HARNESS_OUTPUT_PENDING
```

Check for one row per server, `messaging-0` to `messaging-2`, each `active` with the same `PLAN` and `EXPIRES`. Any other state needs attention: [How licensing works](/licensing/how-it-works#check-license-status).

<Accordions>
  <Accordion title="Pin a version">


    Open the [Release notes](/release-notes) and copy the release number exactly as shown.

    If kmq is installed, switch it to that release:

    ```bash
    kmq update --version YOUR_VERSION
    ```

    Replace: `YOUR_VERSION` — the release number you copied, with a leading `v`.

    On a machine without kmq, download the installer as [Try KubeMQ](/deploy/quickstart#install-kmq) shows, then run it with that release:

    ```bash
    sh install-kmq.sh --version YOUR_VERSION
    ```

    On Windows, in PowerShell:

    ```powershell
    & .\install-kmq.ps1 -Version YOUR_VERSION
    ```

    That kmq installs its matching server release. A pinned install does not update itself: read the release notes and move to the latest release regularly. `kmq update` with no `--version` returns kmq to the latest release.



    Open the [Release notes](/release-notes) and copy the release number exactly as shown. Add `--version YOUR_VERSION` to every `helm upgrade --install` command and, when you upgrade, to `helm show crds`. Use the same value for the operator release and the cluster release, so the custom resource definitions match the pinned operator. `helm search repo kubemq-next/kubemq-next --versions` lists every published chart version.

    Replace: `YOUR_VERSION` — the release number you copied, with no leading `v`.

    Pinned installs do not update themselves. Check the release notes and move to the latest release regularly.



    To install with Argo CD or Flux, pin the chart in your deployment repository. Open the [Release notes](/release-notes) and copy the release number exactly as shown.

    * Deploy the chart `kubemq-next` from `https://kubemq-io.github.io/charts-next` twice, as the operator release and the cluster release, with the same values as the Helm commands on this page.
    * Pin both releases to the same version: Argo CD `spec.source.targetRevision: YOUR_VERSION`, or Flux HelmRelease `spec.chart.spec.version: YOUR_VERSION`.
    * On Flux, set `spec.install.crds: CreateReplace` and `spec.upgrade.crds: CreateReplace`. The defaults never upgrade the custom resource definitions.
    * Sync the cluster release after the operator release: Flux `spec.dependsOn`, or Argo CD sync waves.
    * Create the Secret `messaging-license`, key `licenseKey`, from your secret manager, for example External Secrets or Sealed Secrets. Never put the key in the values.
    * Point update tools such as Renovate or Flux image automation at the `charts-next` chart index, never at an image's `latest` tag.

    Replace: `YOUR_VERSION` — the release number you copied, with no leading `v`.

    Pinned installs do not update themselves. Check the release notes and move to the latest release regularly.
  </Accordion>
</Accordions>

## Send a test message [#send-a-test-message]

<Steps>
  <Step>
    ### Open the management port [#open-the-management-port]

    ```bash title="Terminal 2"
    kubectl --context YOUR_KUBE_CONTEXT -n kubemq port-forward pod/messaging-0 18080:8080
    ```

    Replace: `YOUR_KUBE_CONTEXT` — your cluster's kubectl context.

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```

    Leave it running.
  </Step>

  <Step>
    ### Sign in [#sign-in]

    <Tabs groupId="install-tool" items="[&#x22;kmq&#x22;, &#x22;Helm&#x22;]">
      <Tab value="kmq">


        ```bash
        kmq auth login --bootstrap --installation messaging --username admin --api-address https://127.0.0.1:18080
        ```

        kmq reads the first password from the cluster and asks for a new one. You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```
      </Tab>

      <Tab value="Helm">
        Save the first password in `kubemq-private`:

        ```bash
        kubectl --context YOUR_KUBE_CONTEXT -n kubemq get secret messaging-api-admin -o jsonpath='{.data.admin-password}' | base64 -d > admin-password
        ```

        ```bash
        kmq auth login --username admin --api-address http://127.0.0.1:18080 --context-name messaging
        ```

        Paste the contents of `admin-password` when kmq asks, then set a new password. You should see:

        ```text title="Output"
        HARNESS_OUTPUT_PENDING
        ```



        <Callout type="warn" title="Security: use the admin password only">
          The Secret's `operator-token` key belongs to the operator. Never use it.
        </Callout>
      </Tab>
    </Tabs>

    If kmq reports `Management connection failed`, check that Terminal 2 is still running, then run the command again.
  </Step>

  <Step>
    ### Point kmq at the cluster [#point-kmq-at-the-cluster]

    ```bash
    kmq context use messaging
    ```

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```
  </Step>

  <Step>
    ### Send and receive [#send-and-receive]

    ```bash
    kmq queue send onboarding-check '{"id":1}'
    ```

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```

    ```bash
    kmq queue receive onboarding-check
    ```

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```
  </Step>
</Steps>

## Connect your applications [#connect-your-applications]

Applications inside the cluster use these Services, all `ClusterIP`:

| Interface                        | In-cluster address                         | Port    |
| -------------------------------- | ------------------------------------------ | ------- |
| gRPC                             | `messaging-grpc.kubemq.svc.cluster.local`  | `50000` |
| REST                             | `messaging-rest.kubemq.svc.cluster.local`  | `9090`  |
| Kafka-compatible                 | `messaging-kafka.kubemq.svc.cluster.local` | `9092`  |
| RabbitMQ-compatible (AMQP 0-9-1) | `messaging-amqp.kubemq.svc.cluster.local`  | `5672`  |

From outside Kubernetes: [Interfaces (gRPC · REST · API · HTTP)](/configure/reference/interfaces). Client code: [Client SDKs](/sdks).

## Replace the license later [#replace-the-license-later]

Get the new license as in [Add your license](#add-your-license).

<Tabs groupId="install-tool" items="[&#x22;kmq&#x22;, &#x22;Helm&#x22;]">
  <Tab value="kmq">


    ```bash
    kmq deploy update --installation messaging --credential YOUR_LICENSE_REF --deadline 15m
    ```

    Replace: `YOUR_LICENSE_REF` — the new reference.

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```
  </Tab>

  <Tab value="Helm">
    Put the new key in `license.key` and run the Secret command from Helm step 2 again.
  </Tab>
</Tabs>

The operator restarts the servers one at a time, keeping their data; an online key needs internet access meanwhile. Then run the wait and `kmq license` commands again. If a server does not become ready, put the previous license back and see [Troubleshooting](/licensing/troubleshooting#license-not-accepted).

## Remove the cluster [#remove-the-cluster]

<Tabs groupId="install-tool" items="[&#x22;kmq&#x22;, &#x22;Helm&#x22;]">
  <Tab value="kmq">


    ```bash
    kmq deploy remove --installation messaging --deadline 15m
    ```

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```
  </Tab>

  <Tab value="Helm">
    Helm keeps the cluster resource, so delete it first:

    ```bash
    kubectl --context YOUR_KUBE_CONTEXT -n kubemq delete kubemqclusters.next.kubemq.io messaging --wait
    ```

    ```bash
    helm uninstall messaging --kube-context YOUR_KUBE_CONTEXT -n kubemq
    ```
  </Tab>
</Tabs>

Both keep the volumes and the operator. Remove the operator once no cluster uses it:

```bash
helm uninstall kubemq-operator --kube-context YOUR_KUBE_CONTEXT -n kubemq
```

Replace: `YOUR_KUBE_CONTEXT` — your cluster's kubectl context.

<Callout type="warn" title="Data loss: volumes and namespace">
  Deleting the volume claims or the namespace deletes every message and the cluster's identity. Keep your administrator password; a cluster on these volumes needs it.
</Callout>

## Use your own management certificate [#management-certificate]

For production, a management certificate your organization issues. On a first kmq install, create the namespace first (Helm step 1). With cert-manager:

```yaml title="management-certificate.yaml"
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: messaging-api
  namespace: kubemq
spec:
  secretName: messaging-api-tls
  dnsNames:
    - messaging-api.kubemq.svc
    - messaging-api.kubemq.svc.cluster.local
  ipAddresses:
    - 127.0.0.1
  issuerRef:
    name: YOUR_ISSUER
```

```bash
kubectl --context YOUR_KUBE_CONTEXT -n kubemq apply -f management-certificate.yaml
```

Replace: `YOUR_ISSUER` — a cert-manager issuer in `kubemq` (for a cluster issuer, add `kind: ClusterIssuer`); `YOUR_KUBE_CONTEXT` — your cluster's kubectl context.

Without cert-manager, use files covering the same names and address:

```bash
kubectl --context YOUR_KUBE_CONTEXT -n kubemq create secret tls messaging-api-tls --cert=YOUR_TLS_CERT_FILE --key=YOUR_TLS_KEY_FILE
```

Replace: `YOUR_TLS_CERT_FILE` — the certificate; `YOUR_TLS_KEY_FILE` — its key.

<Tabs groupId="install-tool" items="[&#x22;kmq&#x22;, &#x22;Helm&#x22;]">
  <Tab value="kmq">
    At first install only, before kmq step 2, add two fields to `kubernetes.json`:

    ```json title="kubernetes.json"
    {
      "schema_version": 1,
      "goal": "production",
      "target": "kubernetes",
      "name": "messaging",
      "credential": "YOUR_LICENSE_REF",
      "kubernetes": {
        "context": "YOUR_KUBE_CONTEXT",
        "namespace": "kubemq",
        "operator_release": "kubemq-operator",
        "cluster_release": "messaging",
        "server_count": 3,
        "storage_class": "YOUR_STORAGE_CLASS",
        "volume_size": "20Gi",
        "tls_secret": "messaging-api-tls",
        "ca_file": "YOUR_CA_FILE"
      }
    }
    ```

    Replace: `YOUR_CA_FILE` — your issuing authority's certificate; `YOUR_LICENSE_REF`, `YOUR_KUBE_CONTEXT`, `YOUR_STORAGE_CLASS` — as in kmq step 1.



    On a cluster kmq already installed, [contact support](mailto:support@kubemq.io).
  </Tab>

  <Tab value="Helm">
    Add `tlsSecret: messaging-api-tls` under `api` in `cluster-values.yaml` and apply it as the settings-change section of [Kubernetes (Helm)](/configure/kubernetes#apply-a-settings-change) shows. The servers restart: run the wait command again, restart the Terminal 2 port-forward, then sign in over HTTPS with your new password:

    ```bash
    kmq auth login --username admin --api-address https://127.0.0.1:18080 --ca-file YOUR_CA_FILE --context-name messaging --service-name messaging-kmq-tls
    ```

    Replace: `YOUR_CA_FILE` — your issuing authority's certificate.

    You should see:

    ```text title="Output"
    HARNESS_OUTPUT_PENDING
    ```
  </Tab>
</Tabs>

## If something goes wrong [#if-something-goes-wrong]

Replace: `YOUR_KUBE_CONTEXT` — your cluster's kubectl context.

* **Pods stay `Pending`.** `kubectl --context YOUR_KUBE_CONTEXT -n kubemq describe pod messaging-0` says why: usually storage or too few nodes; see [Requirements and supported setups](/deploy/install/requirements#kubernetes).
* **`ImagePullBackOff`.** The nodes cannot reach the registry; see [Requirements and supported setups](/deploy/install/requirements#network) or [Install air-gapped](/deploy/install/air-gapped).
* **`credential_storage_unavailable`.** On Linux with no keyring, add `--credential-backend file` to every `kmq trial`, `kmq license`, `kmq deploy` and `kmq auth` command.
* **A server stops with a licensing message.** `kubectl --context YOUR_KUBE_CONTEXT -n kubemq logs messaging-0` prints a [Troubleshooting](/licensing/troubleshooting) link, such as `#cluster-over-cap`.

## Next steps [#next-steps]

<Cards>
  <Card title="Production checklist" href="/deploy/production-checklist" description="Harden the cluster before it carries production traffic." />

  <Card title="Upgrade KubeMQ" href="/deploy/upgrade" description="Move the operator and the cluster to the latest release." />

  <Card title="Deployment & High Availability" href="/configure/reference/deployment" description="Every setting of the cluster resource." />

  <Card title="Operate KubeMQ" href="/operate" description="Monitor, back up and run the cluster day to day." />
</Cards>
