Kubernetes manifests for deploying Zigbee2MQTT using Kustomize, with first-class support for FluxCD GitOps workflows.
Zigbee2MQTT is a bridge that exposes your Zigbee network to MQTT, enabling integration with home automation systems. It connects a USB or serial Zigbee coordinator (such as the Texas Instruments CC2652 series or Sonoff Zigbee 3.0 USB Dongle Plus) to an MQTT broker, and publishes device state and events as MQTT messages.
When homeassistant.enabled: true is set in the Zigbee2MQTT configuration, Zigbee2MQTT publishes MQTT Discovery messages to your Home Assistant MQTT broker. Home Assistant automatically creates entities for all paired Zigbee devices β no manual entity configuration required.
Prerequisites on the Home Assistant side:
- The MQTT integration must be configured in Home Assistant.
- Home Assistant and Zigbee2MQTT must point to the same MQTT broker.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Kubernetes Cluster β
β β
β βββββββββββββββββββββββββββ β
β β zigbee2mqtt StatefulSetβ β
β β β β
β β /dev/ttyACM0 (USB)ββββββΌβββΊ Zigbee Coordinator β
β β /app/data (hostPath/ β β
β β PVC) β β
β β Port 8080 (web UI) β β
β ββββββββββββββ¬βββββββββββββ β
β β β
β β MQTT (port 1883) β
β βΌ β
β ββββββββββββββββββββββββββ ββββββββββββββββββββββββ β
β β MQTT Broker βββββΊβ Home Assistant β β
β β (Mosquitto etc.) β β (MQTT Integration) β β
β ββββββββββββββββββββββββββ ββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- StatefulSet deployment β ensures stable network identity and reliable restarts.
- Kustomize-native β apply directly or use as a Kustomize remote base.
- FluxCD ready β works as a GitRepository source for FluxCD Kustomization resources.
- Renovate enabled β automatic image version PRs via Renovate.
- Configurable β optional ConfigMap for GitOps-managed configuration, or bring your own data directory.
| Requirement | Notes |
|---|---|
| Kubernetes cluster | k3s, k0s, RKE2, EKS, GKE, etc. |
| Zigbee USB coordinator | Connected to a cluster node (e.g. /dev/ttyACM0) |
| MQTT broker | Mosquitto or similar, reachable from the cluster |
kubectl |
For direct install |
kustomize |
v5+ (or kubectl apply -k) |
| FluxCD (optional) | For GitOps install |
Node affinity: Because the Zigbee USB device is physically attached to a specific node, you will likely need to pin the pod to that node using a
nodeSelectorornodeName. See Configuration for details.
Step 1: Clone or download this repository
git clone https://github.com/dawg-io/zigbee2mqtt-kustomize.git
cd zigbee2mqtt-kustomizeStep 2: Edit the StatefulSet to match your environment
Open base/statefulset.yaml and update the following values:
| Field | Default | Description |
|---|---|---|
spec.template.spec.volumes[zoneinfo].hostPath.path |
/usr/share/zoneinfo |
Path to zoneinfo on the node |
spec.template.spec.volumes[zigbee2mqtt-data].hostPath.path |
/var/lib/zigbee2mqtt/data |
Required: path on the node where Zigbee2MQTT data is stored |
spec.template.spec.containers[0].env[TZ] |
UTC |
Timezone (e.g. America/New_York) |
spec.template.spec.containers[0].volumeMounts[zoneinfo].subPath |
America/New_York |
Must match the TZ value |
Step 3: Edit the ConfigMap (optional)
Uncomment the configmap.yaml entry in base/kustomization.yaml to manage the Zigbee2MQTT configuration.yaml via a ConfigMap. Then update the MQTT server address in base/configmap.yaml:
mqtt:
server: mqtt://<your-mqtt-broker-ip>:1883Also update the serial port if your coordinator is not on /dev/ttyACM0.
Step 4: Create the namespace and deploy
kubectl create namespace zigbee2mqtt
kubectl apply -k . -n zigbee2mqttStep 5: Verify the deployment
kubectl get pods -n zigbee2mqtt
kubectl logs -n zigbee2mqtt -l app=zigbee2mqttThe Zigbee2MQTT web UI is available at http://<node-ip>:8080 (or via a port-forward):
kubectl port-forward -n zigbee2mqtt svc/zigbee2mqtt 8080:8080FluxCD users can reference this repository directly as a GitRepository source and apply it as a Kustomization.
Step 1: Create the namespace and any required Secrets
kubectl create namespace zigbee2mqttStep 2: Apply the FluxCD resources
Copy the example files from examples/flux/ and update them to match your cluster. Then apply:
kubectl apply -f examples/flux/gitrepository.yaml
kubectl apply -f examples/flux/kustomization.yamlOr place these files in your main Flux repository so Flux manages them automatically.
Example GitRepository:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: zigbee2mqtt-kustomize
namespace: flux-system
spec:
interval: 1h
url: https://github.com/dawg-io/zigbee2mqtt-kustomize
ref:
branch: mainExample Kustomization:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: zigbee2mqtt
namespace: flux-system
spec:
interval: 10m
path: "./"
prune: true
sourceRef:
kind: GitRepository
name: zigbee2mqtt-kustomize
targetNamespace: zigbee2mqtt
postBuild:
substitute:
DATA_PATH: "/your/data/path"
TZ: "America/New_York"See examples/flux/ for complete, ready-to-use example files.
The timezone is set in two places in statefulset.yaml:
-
The
TZenvironment variable (used by the application):env: - name: TZ value: America/New_York
-
The
zoneinfovolume mountsubPath(provides the correct localtime file):volumeMounts: - name: zoneinfo mountPath: /etc/localtime subPath: America/New_York
Both values must match.
Edit base/configmap.yaml to set your coordinator's serial port:
serial:
port: /dev/ttyACM0 # Change to match your device
baudrate: 115200
rtscts: falseCommon port values:
/dev/ttyACM0β CP210x or CH340 based coordinators/dev/ttyUSB0β Some coordinators enumerate here/dev/serial/by-id/<id>β Recommended for stability (survives reboots)
The pod runs with
privileged: trueandSYS_ADMINcapability to access the USB serial device. This is required for direct device access.
Edit the mqtt.server value in base/configmap.yaml:
mqtt:
server: mqtt://mosquitto.home-assistant.svc.cluster.local:1883
# Optional: add authentication
# user: zigbee2mqtt
# password: yourpasswordIf your MQTT broker requires authentication, add user and password fields. Consider using a Kubernetes Secret and injecting the values via environment variables or a Kustomize secret generator.
Because the Zigbee USB dongle is physically attached to one node, pin the pod to that node. Add a nodeSelector to the StatefulSet spec:
spec:
template:
spec:
nodeSelector:
kubernetes.io/hostname: my-node-nameOr use nodeName for a hard pin:
spec:
template:
spec:
nodeName: my-node-nameBy default, the StatefulSet mounts a hostPath volume for Zigbee2MQTT's data directory (/app/data). This stores the device database, coordinator backup, and configuration state.
Using hostPath (default):
Update the path in base/statefulset.yaml to point to an existing directory on your node:
volumes:
- name: zigbee2mqtt-data
hostPath:
path: /your/data/path # Must exist on the nodeUsing a PersistentVolumeClaim:
See examples/pvc/ for a Kustomize overlay that replaces the hostPath with a PVC. This is recommended when using a distributed storage provider (Longhorn, NFS, etc.).
The configmap.yaml contains a full configuration.yaml for Zigbee2MQTT. It is commented out of kustomization.yaml by default to allow the configuration to be managed by Zigbee2MQTT itself (stored in the data directory).
To manage the configuration via a ConfigMap (GitOps style):
-
Uncomment the ConfigMap in
base/kustomization.yaml:resources: - statefulset.yaml - service.yaml - configmap.yaml # Uncomment this line
-
Also uncomment the
config-volumeentries inbase/statefulset.yamlto mount the ConfigMap. -
Edit
configmap.yamlwith your settings.
Note: Some Zigbee2MQTT settings (like the coordinator backup and device pairing state) are always stored in the data directory and cannot be fully managed via ConfigMap.
This repository includes a renovate.json configuration. When used with Renovate, Renovate automatically opens pull requests to update the koenkk/zigbee2mqtt image version in statefulset.yaml.
To enable this:
- Install the Renovate GitHub App on your fork.
- Renovate will open PRs when new versions are available.
- Merge the PR β FluxCD (or your next
kubectl apply) will roll out the update.
Update the image tag in statefulset.yaml:
image: koenkk/zigbee2mqtt:2.9.1 # Change to the desired versionCheck the Zigbee2MQTT releases page for available versions.
Then apply:
kubectl apply -k . -n zigbee2mqtt
# or with FluxCD: git commit and push the changekubectl describe pod -n zigbee2mqtt -l app=zigbee2mqtt
kubectl logs -n zigbee2mqtt -l app=zigbee2mqtt --previousCommon causes:
| Symptom | Cause | Fix |
|---|---|---|
Error: failed to open serial port |
Wrong serial port or USB not attached to this node | Check port in configmap.yaml; pin pod to the correct node |
ENOENT /app/data |
Data directory does not exist | Create the hostPath directory on the node before deploying |
Connection refused to MQTT |
Wrong MQTT server address | Update mqtt.server in configmap.yaml |
| Pod keeps restarting | Liveness probe fails | Check pod logs; ensure port 8080 is reachable |
# SSH to the node and list serial devices
ls -la /dev/tty*
ls -la /dev/serial/by-id/kubectl port-forward -n zigbee2mqtt svc/zigbee2mqtt 8080:8080Then open http://localhost:8080 in your browser.
To factory-reset the coordinator (removes all paired devices), use the Zigbee2MQTT web UI: Settings β Tools β Reset coordinator
.
βββ README.md # This file
βββ kustomization.yaml # Root Kustomize entry point (references base/)
βββ renovate.json # Renovate image auto-update config
βββ base/ # Base Kubernetes manifests
β βββ kustomization.yaml # Base Kustomize configuration
β βββ statefulset.yaml # Zigbee2MQTT StatefulSet
β βββ service.yaml # ClusterIP Service (port 8080)
β βββ configmap.yaml # Optional: Zigbee2MQTT configuration.yaml
βββ examples/
βββ flux/ # FluxCD GitRepository + Kustomization examples
β βββ README.md
β βββ gitrepository.yaml
β βββ kustomization.yaml
βββ pvc/ # Kustomize overlay: PVC-based storage
βββ kustomization.yaml
βββ pvc.yaml
Issues and pull requests are welcome. Please ensure any manifest changes are tested against a real cluster before submitting.
See LICENSE if present, or check the repository settings.