Home Assistant on Kubernetes: A Questionable Decision I'd Make Again

June 2026 · 8 min read

Home Assistant's documentation gently steers you toward running it on a dedicated box. A Raspberry Pi, their own appliance OS, maybe a container if you insist. What it does not suggest is running it as a workload on a Kubernetes cluster, because that is a strange thing to want.

I run mine on the k3s cluster in my living room anyway, deployed by Argo CD from a Git repo like everything else in the house. This post is the honest accounting. Which Kubernetes concepts survive contact with a smart home hub, which ones bend, and where the workarounds live.

A stateful singleton in a world built for replicas

Start with what Home Assistant is, architecturally: a singleton with a SQLite database and strong feelings about being the only one of itself. Two instances writing one config directory is corruption with extra steps. So the deployment renounces most of what Kubernetes is famous for:

spec:
  replicas: 1
  strategy:
    type: Recreate

Recreate instead of the default rolling update matters more than it looks. A rolling update briefly runs old and new pods side by side. For a stateless API that's zero-downtime deployment. For Home Assistant it's two processes fighting over one SQLite file. Recreate kills the old pod fully before starting the new one. I traded thirty seconds of "the lights are thinking" during upgrades for not corrupting my smart home's memory.

State lives on two PersistentVolumeClaims. 10 GiB for the config volume, which looks generous until you remember the recorder database grows with every sensor reading your house ever emits. And a separate 1 GiB volume for the Matter server, kept apart so the two processes never share a write path. Both are ReadWriteOnce, which on a single-node cluster is less a restriction than a statement of fact. Backups ride the same GitOps philosophy as everything else. The manifests in Git rebuild the shape of the deployment in minutes. The PVC contents are what actually needs backing up, and they're small enough that snapshotting them is a cron job, not a project.

Everything about this pod, drawn in one picture:

flowchart TB
    subgraph NODE["NUC (master node — pod pinned here)"]
        subgraph POD["Home Assistant pod (hostNetwork: true)"]
            INIT["init: busybox<br/>seed configuration.yaml<br/>append http: proxy block"] --> HA["home-assistant:stable"]
            INIT --> MS["matter-server:stable<br/>(sidecar)"]
        end
        CFG[("PVC 10Gi<br/>/config")] --- HA
        MDATA[("PVC 1Gi<br/>matter data")] --- MS
        CM["ConfigMap<br/>config template"] -.seed only.-> INIT
    end
    LAN["smart devices<br/>mDNS · SSDP · Matter"] <-->|multicast heard via host NIC| POD
    WEB["browser"] --> TR["Traefik ingress"] --> HA
  

The hostNetwork confession

The least Kubernetes-native line in the manifest, and the most necessary:

hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet

Smart home devices announce themselves with multicast. mDNS, SSDP. Cast devices, ESPHome nodes, half the discoverable world shouts into the local network and expects the hub to hear it. A pod on the cluster's overlay network is deaf to all of it, because the multicast never crosses the bridge. hostNetwork: true puts Home Assistant directly on the NUC's network interface, where it can hear the house.

The subtle tax is that second line. With host networking, a pod defaults to the host's DNS, and suddenly it can't resolve cluster-internal service names. ClusterFirstWithHostNet restores cluster DNS while keeping host networking. I now check that combination by reflex on any hostNetwork pod, having learned it the way this is always learned: forty-five minutes of staring at a resolver error that made no sense.

Since host networking nails the pod to a specific machine's network identity anyway, the deployment leans in. Node affinity requires the master node, with a matching toleration for its taint. Portability was already spent, so determinism was worth buying with it. If the cluster ever grows a second node, Home Assistant will not be invited to wander.

A busybox init container as the bootstrapper

My favorite piece of this manifest is the tiny shell script that runs before Home Assistant starts. The problem: Home Assistant owns and rewrites its configuration.yaml at runtime, so you can't mount the whole config from a ConfigMap read-only. But a fresh install still needs sane initial config, and running behind Traefik requires a proxy block Home Assistant won't add itself.

The compromise is a busybox init container with two idempotent moves. If configuration.yaml doesn't exist, seed it from the ConfigMap template. If it exists but lacks an http: section, append the reverse-proxy settings: use_x_forwarded_for plus the cluster's pod CIDR (10.42.0.0/16, the k3s default) as a trusted proxy, so Home Assistant believes the client IPs Traefik forwards instead of treating every login as an attack from inside the house.

The template itself stays deliberately minimal. The default_config integrations, text-to-speech wired to a translate service, and that proxy block. Everything else, the dashboards and automations and device configs, belongs to the app's own runtime mutations. It's ConfigMap-as-seed rather than ConfigMap-as-truth. Git owns the starting state and the invariants, the app owns what it learns afterward, and the init container is the boundary negotiator between GitOps and software that was never told about GitOps. Ten lines of shell doing the diplomacy.

The idempotency is what makes it live comfortably under Argo CD. Every pod restart re-runs the init container, and on a healthy volume both checks no-op in milliseconds. There's no "first install" flag anywhere, no state about whether bootstrapping happened. The script just converges the volume toward its invariants every single boot.

Don't record whether you did the thing. Check whether the thing is true.

The Matter sidecar

Matter device support needs a separate controller process, and this is where Kubernetes stops fighting me and starts helping. The Matter server rides in the same pod as a second container. Sidecar semantics are exactly right for once. Commissioning a Matter device involves the controller and the hub coordinating over the local network, and sharing the pod's host network means they see the same network reality by construction. They share a lifecycle so they restart together, the Matter server keeps its own small volume, and there is no scenario where one is up, the other is down, and I'm debugging which. The pattern that felt like ceremony for web apps is genuinely the correct shape here.

Probes for software that boots like a house

Health checking stays humble. TCP probes on the web port, liveness with a 60-second initial delay and 30-second period, readiness at 30 and 10. Home Assistant boots like it's stretching first. Integrations initialize serially, some wait on network devices that may themselves be waking up, and the whole process takes longer after a power cut, which is precisely when you least want Kubernetes making things worse.

That's why the probes answer "is the process up" and nothing deeper. Every increment of probe cleverness, checking the API, checking an integration, is another way for a slow Zigbee stick to get the pod killed mid-boot, restarted, killed again, forever. I've watched that loop. On restarts-after-power-cuts, patience beats precision, and a liveness probe should test for "alive", not "fully dressed".

Verdict

Would I recommend this to someone who just wants working lights? No. Buy the appliance, enjoy your evening. The honest benefit case is narrower and better. If a k3s cluster already runs your house, folding Home Assistant into it means the smart home gets the same superpowers as everything else. Config in Git, upgrades by bumping an image tag in a commit, rollbacks by revert, rebuild-from-scratch as a twenty-minute plan rather than a lost weekend.

The pod pins to one node, claims its network, refuses replicas, and mutates its own config. Kubernetes purists would wince. But it's been quietly running the house for months, surviving upgrades and power cuts, and every workaround it needed is documented in the one place I'll actually find it again: the manifest.

References