Home Assistant is the household automation hub. It is installed only by Argo CD Application homeassistant, which pins community Helm chart home-assistant at 0.3.71 (image tag 2026.7.3) into namespace homeassistant. There is no official chart; the pin is pajikos/home-assistant from https://pajikos.github.io/home-assistant-helm-chart/. The GitOps file shape matches Jellyfin: public companion manifests, public values, a private overlay, and the upstream chart. Home Assistant keeps a ClusterIP on port 8123. Remote phone access is the in-cluster Tailscale Kubernetes Operator, not a Cloudflare Access challenge. Jellyfin and Navidrome still use the Cloudflare Tunnel; this app does not need a Public Hostname for v1.
This is the Container image sitting in the cluster, not a supervised OS install. LAN multicast discovery and USB radios are out of v1; cloud-linked or statically addressed devices still work. This page sits with the other application guides. Site hostnames stay in homelabs-private/clusters/<cluster>/.
GitOps#
Application homeassistant is multi-source, cloned from the Jellyfin five-source order. Public homelabs/homeassistant/manifests apply the Longhorn config claim. Private clusters/<cluster>/manifests/homeassistant is an empty kustomize (resources: []); there is no NFS volume. Helm merges $homelabs/homeassistant/values.yaml with $private/clusters/<cluster>/overlays/homeassistant/values.yaml onto chart home-assistant 0.3.71. Sync-wave is 1; CreateNamespace is on.
| Piece | Where | Role |
|---|---|---|
| Application CR | clusters/<cluster>/argocd/applications/homeassistant.yaml | Multi-source app |
| Public values | homelabs/homeassistant/values.yaml | ClusterIP :8123, claim name, Recreate, ServiceMonitor off |
| Private overlay | clusters/<cluster>/overlays/homeassistant/values.yaml | Repeats ClusterIP :8123. Expose annotation is a later commit |
| Config PVC | homelabs/homeassistant/manifests/pvc-config.yaml | homeassistant-config-pvc-lh |
| Private kustomize | clusters/<cluster>/manifests/homeassistant/ | Empty resources: [] |
The chart uses pajikos keys: service.type and service.port. Do not copy Navidrome service.main into these values; that misses the schema and leaves the chart default port 8080. The overlay must repeat service.port: 8123 for the same reason. Public values also set fullnameOverride: homeassistant, one replica, controller.type: Deployment (this chart’s existingClaim path is Deployment-only), deploymentStrategy: Recreate as a string, ingress and HTTPRoute off, hostNetwork: false, code-server off, and configuration.enabled: false so GitOps does not write configuration.yaml over first-run. They leave serviceMonitor.enabled false: core has no Prometheus /metrics until you add the integration in config, so a ServiceMonitor would scrape-fail. The same file requests 250m CPU / 512Mi memory and limits 2 CPU / 2Gi, and drops all capabilities with allowPrivilegeEscalation: false.
AppProject homelab must allow the chart repo https://pajikos.github.io/home-assistant-helm-chart/ and destination namespace homeassistant. Apply the live project before the Application is committed:
kubectl apply -f homelabs-private/argocd/projects/homelab.yamlDay-to-day changes go through Git and an Argo sync. Do not helm install Home Assistant from a workstation. Pin stays 0.3.71 / 2026.7.3; treat a chart or image bump as a new change.
Remote access#
The Service stays ClusterIP. Phones and the companion app reach it on the household tailnet through the in-cluster Tailscale operator. That overlay is not a public origin, so you do not add a Zero Trust Public Hostname for the app, and you do not turn Cloudflare Access off anywhere to make the companion work.
Operator GitOps for Tailscale already exists: Application tailscale, official chart 1.102.3, OAuth from Vault. Home Assistant’s private overlay still repeats ClusterIP only. The pajikos annotation tailscale.com/expose: "true" is a later overlay commit, after an owner exists. Until that annotation is synced, MagicDNS does not answer for this Service.
When expose is live, a phone on the tailnet opens http://homeassistant.<tailnet>.ts.net:8123. That hostname is a placeholder; the operator assigns the real MagicDNS name. Do not advertise the cluster service CIDR, and do not dial the ClusterIP from the phone.
A Cloudflare hostname is optional and only for a browser. If you later want homeassistant.<your-domain>, add it on the existing cloudflared Deployment in namespace cloudflare-tunnel, not a second tunnel, and not an edit to homelabs/cloudflare/:
| Field | Value |
|---|---|
| Subdomain | homeassistant (published as homeassistant.<your-domain>) |
| Type | HTTP |
| URL | http://homeassistant.homeassistant.svc.cluster.local:8123 |
| Cloudflare Access | On |
Put Access in front of that hostname before it answers. Use an email allow-list or an existing household identity group, not Everyone, and not an empty policy. Dex and Grafana stay Access-off because they are an identity issuer and its callback; Home Assistant is neither. Companion apps stay on Tailscale; they are not a reason to turn Access off here.
Dashboard route changes apply without restarting cloudflared. The connector itself is documented under Cloudflare Tunnel.
First-run owner#
Anyone who can hit :8123 before an owner exists becomes the owner. Create the owner on a workstation port-forward before the Tailscale expose annotation is synced. Do not finish onboarding from a phone on the tailnet, and do not publish a Cloudflare hostname before Access exists.
- Sync Application
homeassistantand confirm Servicehomeassistantis ClusterIP with Ready endpoints and no EXTERNAL-IP. Leave the overlay withouttailscale.com/exposeuntil the owner exists. - Forward the Service and open the wizard on your workstation:
kubectl -n homeassistant port-forward svc/homeassistant 8123:8123- Create the first owner.
- Then sync the private overlay expose annotation. Open the companion from the phone at
http://homeassistant.<tailnet>.ts.net:8123(placeholder). Create a Public Hostname only if you want a browser URL, with Access already on.
Do not run onboarding on a world-open origin.
There is no ExternalSecret, Vault path, or Dex client in v1. The owner password lives in config on the Longhorn claim after the wizard.
If you put Cloudflare in front later, tell Home Assistant that the tunnel pod is a trusted reverse proxy. Git leaves configuration.enabled false, so you set this on the config PVC yourself, typically http.use_x_forwarded_for: true and http.trusted_proxies equal to the cluster pod CIDR from clusters/<cluster>/config/cluster.yaml. Trust that CIDR only. Do not use 0.0.0.0/0. Tailscale L3 expose does not need those keys.
Parental UniFi toggles#
After UniFi holds internet-block rules for kid device groups, Home Assistant can expose each rule as a switch and put two toggles on a dashboard.
Prerequisites#
- Add the UniFi Network integration pointed at your UniFi console (local admin user with permission to change rules).
- In the integration options, enable control of Traffic Rules and/or Policy Engine rules (wording varies by HA version). Reload the integration if the switches do not appear.
- Under Developer Tools → States, find the two
switch.*entities that match your UniFi block rules. Note the entity IDs.
Polarity: switch on means the UniFi rule is enabled (internet blocked for that group). Switch off disables the rule.
Dashboard#
A reference Lovelace view lives in the public repo:
homelabs/homeassistant/lovelace/parental-toggles.yaml
- Open that file and replace
switch.REPLACE_GROUP_A_RULEandswitch.REPLACE_GROUP_B_RULEwith your two entity IDs. Rename the card labels to your group names. - In Home Assistant: Settings → Dashboards → Add dashboard (or edit an existing one) → raw YAML / add an Entities card with those two switches.
- Flip each toggle once and confirm the matching rule enable/disable in the UniFi Network UI.
Home Assistant config stays on the Longhorn claim (configuration.enabled remains false in GitOps). This dashboard is applied in the UI; Git only keeps the reference YAML.
Do not commit UniFi passwords or real household device names into the public repo.
Storage#
Config state is claim homeassistant-config-pvc-lh: longhorn-ssd, ReadWriteOnce, 2Gi, GitOps from homelabs/homeassistant/manifests/pvc-config.yaml and Helm persistence.existingClaim. The Deployment strategy is Recreate so that single RWO volume can remount after a replace.
There is no NFS claim and no USB or hostPath volume. You do not remount Jellyfin or Navidrome media, and you do not run Home Assistant in those namespaces.
Confirm the claim after sync:
kubectl -n homeassistant get pvchomeassistant-config-pvc-lh should be Bound on longhorn-ssd.
v1 does not see LAN mDNS, SSDP, or a USB radio. That is a later change, not something this page treats as a pod-network setting to flip.
Deploy#
Commit order is homelabs (feature branch + PR so homeassistant/ exists on main) → homelabs-private on main (AppProject apply first, then overlay and Application) → this docs site. Parent app-of-apps already watches argocd/applications/. First-sync this Application only after Navidrome’s 2Gi Longhorn claim is Bound and Healthy at replica count 3, so two new longhorn-ssd volumes do not compete.
After sync, the Application should be Synced/Healthy, the Service ClusterIP only, and the pod Ready:
kubectl -n argocd get application homeassistant
kubectl -n homeassistant get svc homeassistant
kubectl -n homeassistant get pods
kubectl -n homeassistant get servicemonitorService homeassistant must show type ClusterIP on port 8123 and must not have an EXTERNAL-IP. The ServiceMonitor list in the namespace should be empty.
Smoke#
v1 health is pod Ready. After an owner exists and the expose annotation is synced, sign in from a phone on the tailnet at http://homeassistant.<tailnet>.ts.net:8123. Until expose lands, use the port-forward. If you later add a Cloudflare hostname, an Access challenge before the UI is expected. A Cloudflare 502 then means the origin Service or pod (the same signal as Immich and Jellyfin), so check svc/homeassistant and the Deployment, not a VIP.
There is no ServiceMonitor, PrometheusRule, Grafana dashboard, or Blackbox Probe for Home Assistant in v1. kube-prometheus-stack already watches pod Ready and the Longhorn config volume.
Rollback#
Disable or delete Application homeassistant, or scale the Deployment to 0. Config remains on homeassistant-config-pvc-lh until you delete that claim. If you created a Zero Trust Public Hostname, remove it and its Access application. Leave Jellyfin, Navidrome, cloudflared, and Application tailscale alone unless you are tearing the operator down too.