# Docker (single-node) (/configure/docker)



Docker single-node **is** the standalone KubeMQ server, containerized. One container
runs the full server: every interface, every connector, the persistent store, and the
embedded messaging engine. There is no separate "bare-binary" target — the container
*is* the binary. This guide hosts the complete, runnable Docker configs; the
[reference](/configure/reference) pages show per-setting snippets and link back
here.

**Install KubeMQ first** → [Docker install](/deploy/docker). This
page covers how to *configure* a single node, not how to install one.

| Port    | Interface        | Purpose                                                                    |
| ------- | ---------------- | -------------------------------------------------------------------------- |
| `50000` | gRPC             | Primary SDK transport.                                                     |
| `9090`  | REST / WebSocket | REST API, WebSocket, and the shared HTTP server (MCP · A2A · CloudEvents). |
| `8080`  | API / Dashboard  | Web dashboard, management API, health probes, and metrics.                 |

The store is bind-mounted to &#x2A;*`/kubemq/store`** inside the container. Persistence is
required for the **Events Store** and **Queues** patterns.

<Callout type="warn">
  **Two things a persistent store needs, and both are easy to miss.**

  **Mount at `/kubemq/store`, not `/store`.** The image's working directory is `/kubemq` and
  the default `store.storepath` is the *relative* `./store`, so the server writes to
  `/kubemq/store`. A volume mounted at `/store` receives nothing and the data dies with the
  container — and every health check on this page still passes, because the server is
  running fine, just onto container-local disk.

  **Pin the hostname with `--hostname`.** The store layout is
  `<storepath>/<hostname>/…`, and Docker assigns a fresh random hostname to every
  container. Recreate the container without `--hostname` and the server boots clean and
  healthy onto a *new empty directory*, with the old one sitting intact beside it. On the
  `next` engine the node's raft identity moves with it too. Setting KubeMQ's own `HOST` does
  **not** substitute — the directory name comes from the OS hostname.
</Callout>

## Three ways to supply config [#three-ways-to-supply-config]

Every setting has a documented default, so a server runs with no overrides at all. To change
a setting you have three delivery methods, in order of how much you are configuring:

### Per-field environment variables [#per-field-environment-variables]

Pass each setting as `-e GROUP_FIELD=value`. The env var is the `config.yaml` key
snake-cased, dotless, and uppercased — `store.maxretention` becomes `STORE_MAX_RETENTION`.
This is the lightest method, best for a handful of overrides:

<RunKubeMQ env="{ LOG_LEVEL: '1', STORE_MAX_RETENTION: '2880', CONNECTORSCE_ENABLE: 'false' }" />

<Callout type="warn">
  **The connector acronym variables drop the underscore.*&#x2A; The CloudEvents enable variable
  is &#x2A;*`CONNECTORSCE_ENABLE`** (no underscore between `CONNECTORS` and `CE`). The same
  collapse applies to `CONNECTORSMCP_*` and `CONNECTORSMQTT_*`, while the Title-case
  connectors keep the underscore (`CONNECTORS_AMQP_*`, `CONNECTORS_STOMP_*`,
  `CONNECTORS_AWS_*`), and A2A splits to `CONNECTORSA2_A_*`. The full rule is in
  [the reference legend](/configure/reference).

  **What happens when you get it wrong depends on which form you used, and the warning is
  not reliable in either direction:**

  | You set                                                     | Does it work?                                           | Does the server say anything?                                                  |
  | ----------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------ |
  | `CONNECTORS_MQTT_ENABLE` (wrong twin of a collapsed name)   | **No**                                                  | Yes — an `IGNORED` warning, because the name is in the `CONNECTORS_` namespace |
  | `CONNECTORS_CE_ENABLE` (CloudEvents only)                   | **Yes** — CE is the one connector that binds both forms | Yes — an `IGNORED` warning **that is wrong**; the value is applied anyway      |
  | A typo in a correct collapsed name (`CONNECTORSMQT_ENABLE`) | No                                                      | **Nothing at all** — collapsed names sit outside the warner's namespace list   |

  So: an `IGNORED` warning for a `CONNECTORS_CE_*` variable is a false alarm — do not
  "fix" a working setting because of it. And the absence of a warning proves nothing about a
  collapsed-form name. Check the effective value in the dashboard rather than trusting the
  log either way.
</Callout>

### A mounted config.yaml [#a-mounted-configyaml]

Once you are setting more than a few fields, keep them in a `config.yaml` and mount it into
the container. The server auto-detects YAML or TOML; point it at the file with the
`--config` flag.

```yaml title="config.yaml"
log:
  level: 1
store:
  storepath: ./store
  maxretention: 2880
connectors:
  grpc:
    enable: true
    port: "50000"
  rest:
    enable: true
    port: "9090"
  ce:
    enable: false
api:
  port: "8080"
```

<Callout type="warn">
  **The license key is not a `config.yaml` field.*&#x2A; It is read straight from the environment
  as &#x2A;*`KUBEMQ_TOKEN`** (or passed as the `--key` flag) — there is no viper binding and no
  config-file key for it. A `key:` entry in `config.yaml` is the *Helm* spelling; put it here
  and the server ignores it and refuses to start unlicensed. Keep the token in the
  environment, as every `docker run` on this page does.
</Callout>

Mount the file and select it with `--config`. &#x2A;*The flag goes after the image name *and*
after the binary path:**

```bash title="Terminal"
docker run -d \
  --name kubemq \
  --hostname kubemq \
  -p 50000:50000 \
  -p 9090:9090 \
  -p 8080:8080 \
  -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY \
  -v "$(pwd)/kubemq-store:/kubemq/store" \
  -v "$(pwd)/config.yaml:/kubemq/config.yaml:ro" \
  europe-docker.pkg.dev/kubemq/images/kubemq:next \
  /kubemq/kubemq-run --config /kubemq/config.yaml
```

<Callout type="warn">
  **You must repeat `/kubemq/kubemq-run`.** The image declares no `ENTRYPOINT` — only
  `CMD ["/kubemq/kubemq-run"]` — so anything you put after the image name **replaces** the
  command rather than appending to it. Passing just `--config /kubemq/config.yaml` makes
  Docker try to execute `--config` as the binary, and the container never starts.
</Callout>

<Callout type="warn">
  **`enable` is the Docker toggle — but the default differs by family.** The interfaces and
  the HTTP-family connectors (gRPC, REST, API, MCP, A2A, CloudEvents) ship **on by default**;
  the wire-protocol connectors (MQTT, AMQP, STOMP, Kafka, AWS, GCP) are **opt-in** and ship
  **off**. On Docker you flip either with `enable: true | false` (env `..._ENABLE`) — turn an
  always-on interface **off** with `enable: false`, or turn a wire connector **on** with
  `enable: true`. The Helm/CRD surface splits the two: the always-on interfaces use an opt-out
  `disabled: true`, while the wire connectors use an opt-in `enabled: true` (see
  [Kubernetes](/configure/kubernetes)). Same toggles, inverted boolean.
</Callout>

### The CONFIG environment variable [#the-config-environment-variable]

When mounting a file is awkward — orchestrators that inject env vars, CI runners, secret
managers — supply the **whole** config inline through the `CONFIG` environment variable.
The server writes the value to a file and loads it, exactly as if you had passed
`--config`:

```bash title="Terminal"
docker run -d \
  --name kubemq \
  --hostname kubemq \
  -p 50000:50000 \
  -p 9090:9090 \
  -p 8080:8080 \
  -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY \
  -v "$(pwd)/kubemq-store:/kubemq/store" \
  -e CONFIG="$(cat config.yaml)" \
  europe-docker.pkg.dev/kubemq/images/kubemq:next
```

`CONFIG` also accepts a **base64-encoded** payload, which avoids newline and quoting
issues when the value passes through a secret store or a templating layer:

```bash title="Terminal"
docker run -d \
  --name kubemq \
  --hostname kubemq \
  -p 50000:50000 \
  -p 9090:9090 \
  -p 8080:8080 \
  -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY \
  -v "$(pwd)/kubemq-store:/kubemq/store" \
  -e CONFIG="$(base64 < config.yaml)" \
  europe-docker.pkg.dev/kubemq/images/kubemq:next
```

## docker-compose [#docker-compose]

The same single node as a `docker-compose.yaml`, combining a mounted `config.yaml` with a
couple of per-field environment overrides:

```yaml title="docker-compose.yaml"
services:
  kubemq:
    image: europe-docker.pkg.dev/kubemq/images/kubemq:next
    container_name: kubemq
    hostname: kubemq
    command: ["/kubemq/kubemq-run", "--config", "/kubemq/config.yaml"]
    ports:
      - "50000:50000"  # gRPC
      - "9090:9090"    # REST / WebSocket
      - "8080:8080"    # API / Dashboard
    environment:
      - KUBEMQ_TOKEN=${KUBEMQ_TOKEN:-}
      - LOG_LEVEL=1
    volumes:
      - kubemq-store:/kubemq/store
      - ./config.yaml:/kubemq/config.yaml:ro
    restart: unless-stopped

volumes:
  kubemq-store:
```

Start it:

```bash title="Terminal"
docker compose up -d
```

To supply the config inline instead of mounting a file, drop the `command` and
`config.yaml` volume and set `CONFIG` (or `CONFIG` as base64) in the `environment` block.

## Configure by domain [#configure-by-domain]

Every setting in the configs above is documented in the reference, grouped by domain. Each
page lists the `config.yaml` key, the environment variable, the type, the default, and the
valid values.

<Cards>
  <Card title="Core & Licensing" href="/configure/reference/core" description="License key, log level, and host/server identity." />

  <Card title="Interfaces" href="/configure/reference/interfaces" description="gRPC, REST/WebSocket, the management API, and the shared HTTP server with CORS." />

  <Card title="Connectors" href="/configure/reference/connectors" description="MCP, A2A (agents), CloudEvents, MQTT, AMQP 0.9.1, AMQP 1.0, STOMP, Kafka, AWS, and GCP Pub/Sub." />

  <Card title="Storage & Queues" href="/configure/reference/storage-queues" description="Persistent store limits and retention plus queue delivery defaults and ceilings." />

  <Card title="Storage Engines" href="/configure/reference/storage-engines" description="The next and legacy persistence engines, engine selection, durability, and retention scope." />

  <Card title="Security" href="/configure/reference/security" description="JWT and OIDC authentication, policy-based authorization, and TLS/mTLS." />

  <Card title="Observability" href="/configure/reference/observability" description="OpenTelemetry traces and metrics, audit logging, and notifications." />

  <Card title="Deployment & High Availability" href="/configure/reference/deployment" description="Kubernetes packaging — image, volume, resources, health, scheduling, Service exposure — plus replicas and standalone mode." />

  <Card title="Advanced" href="/configure/reference/advanced" description="Message-broker engine, runtime tuning, and routing — config.yaml-only advanced knobs." />
</Cards>

## Verify [#verify]

Confirm the node is up. Open the dashboard at
`http://localhost:8080`, then check the container logs:

```bash title="Terminal"
docker logs kubemq
```

A healthy start logs each interface binding to its port. The health and readiness probes
on the API port confirm the server is accepting traffic:

```bash title="Terminal"
curl http://localhost:8080/health
curl http://localhost:8080/ready
```

## Related [#related]

* [Kubernetes (Helm)](/configure/kubernetes) — the production target with replicas and `Service` exposure.
* [Configuration overview](/configure) — the two targets and the config model.
* [Configuration reference](/configure/reference) — every setting, grouped by domain.
