Home Assistant on Kubernetes
Production deployment for the brunner.ninja Home Assistant instance. It runs the official Home Assistant Container image with persistent Ceph storage, host networking, a MetalLB address, and Traefik ingress.
This repository installs Home Assistant Container, not Home Assistant OS or Home Assistant Supervised. It therefore does not include the Supervisor, add-ons, or the Home Assistant OS backup interface. See the official Home Assistant installation documentation for the differences between installation types.
Architecture
ghcr.io/home-assistant/home-assistant:stableruns as one privileged replica.- Host networking supports discovery protocols used by home-automation devices.
default/home-assistant-pvcprovides 10 Gi fromrook-ceph-blockat/config.- MetalLB advertises
192.168.0.15; Home Assistant listens on TCP port 8123. - Traefik publishes
homeass.brunner.ninjaover itswebsecureentrypoint. - Traefik's existing default
*.brunner.ninjacertificate supplies TLS. - The
/authroute usesdefault-authentik@kubernetescrdso Home Assistant's authentication callbacks are not intercepted by Authentik. - The
Recreatestrategy prevents two Home Assistant processes from writing the same config volume during an update.
The configuration follows Home Assistant's official
container installation,
including /config persistence, host networking, and privileged access.
Repository contents
home-assistant.yaml: Deployment, PVC, LoadBalancer Service, and ingresses.install.sh: server-side dry-run, apply, and rollout verification.test.sh: local script and offline Kubernetes schema validation.ci-deployer.yaml: restricted Gitea Actions ServiceAccount, Role, binding, and revocable token.create-ci-kubeconfig.sh: one-time CI identity bootstrap..gitea/workflows/deploy.yml: validation on every push and pull request; deployment after successful validation ofmain.
No Home Assistant configuration, database, backup, Kubernetes Secret, or kubeconfig belongs in this repository.
Prerequisites
For the manifest as committed:
- Kubernetes and kubectl.
- StorageClass
rook-ceph-block. - MetalLB pool
defaultcontaining the reserved address192.168.0.15. - A cluster policy that permits privileged containers and host networking.
- Traefik ingress class
traefik, with*.brunner.ninjaconfigured as its default TLS certificate. - Authentik middleware
default-authentikin namespacedefault.
Another cluster operator can reuse the file by changing the namespace,
StorageClass, timezone, load-balancer address, hostname, ingress class, and
optional Authentik middleware directly in home-assistant.yaml.
Host networking means port 8123 must be free on the selected node. A cluster
that does not need local-network discovery can remove hostNetwork and the
matching ClusterFirstWithHostNet DNS policy.
Install or restore manually
Run with an administrator kubeconfig:
./install.sh
The script first asks the Kubernetes API for a server-side dry-run, applies the
same manifest, and waits up to ten minutes for the Deployment. All resource
names match the long-running installation, so this updates it in place and
continues using the bound default/home-assistant-pvc. Applying the manifest
does not recreate or empty that claim.
Home Assistant is then available at:
https://homeass.brunner.ninjahttp://192.168.0.15:8123
Reverse proxy configuration
Home Assistant must explicitly trust a reverse proxy before accepting its
forwarded client addresses. Configure the actual Traefik source addresses or
pod network in /config/configuration.yaml, choosing the narrowest correct
range:
http:
use_x_forwarded_for: true
trusted_proxies:
- 10.42.0.0/16 # example only; replace with the actual Traefik source range
Do not copy the example network blindly. The details are documented in Home Assistant's HTTP integration proxy settings.
Back up and restore
The Git repository reconstructs the Kubernetes objects, but not Home
Assistant's state. /config contains configuration, users, automations,
integrations, and the default SQLite database. Back up the PVC with Ceph
snapshots or a separate volume-backup system.
The rook-ceph-block StorageClass has a Delete reclaim policy on this cluster,
so deleting the claim can also delete its backing volume. Never delete
home-assistant-pvc during a normal reinstall.
Before a consistent filesystem-level restore, stop Home Assistant without deleting its PVC:
kubectl scale deployment/home-assistant --replicas=0 -n default
Restore /config, then run ./install.sh to recreate or update the Deployment.
Validate locally
./test.sh
The test checks all shell scripts and validates home-assistant.yaml and
ci-deployer.yaml with kubeconform without contacting Kubernetes. It uses a
local kubeconform binary when available, otherwise the pinned
ghcr.io/yannh/kubeconform:v0.7.0 container through Docker.
Automatic deployment with Gitea Actions
The workflow validates every push and pull request. A successful push to
main performs a server-side dry-run, applies home-assistant.yaml, and polls
the Deployment for up to ten minutes.
CI cannot create or delete resources. Its namespace Role can only get, patch, and update these existing named objects:
- Deployment
home-assistant; - Service
home-assistant; - PVC
home-assistant-pvc; - Ingresses
home-assistantandhome-assistant-auth.
It has no Kubernetes Secrets API access. First installation and RBAC bootstrap remain administrator operations.
One-time CI setup
-
Run
./install.shwith an administrator identity so the workload resources exist. -
Run:
./create-ci-kubeconfig.sh -
Copy only the final base64 line into the Gitea repository Actions secret named
KUBE_CONFIG_BASE64. -
Protect
main, require the validation job before merging, and review manifest and workflow changes. -
Push a harmless README change and confirm the deploy job succeeds.
The workflow matches the existing brunner.ninja runner: Kubernetes API URL
https://host.containers.internal:6443, with TLS server name 192.168.0.2.
Other clusters must change those workflow variables.
The generated credential is a random Kubernetes service-account token, not a
human-chosen password. Treat the printed kubeconfig as a password. Revoke it by
deleting default/home-assistant-deployer-token and the Gitea Actions secret.
Rerunning the wrapper creates a replacement.
Kubernetes RBAC cannot constrain individual fields inside an allowed
Deployment patch. A malicious change merged into main could alter the pod
specification. Branch protection and review therefore remain part of the
security boundary; admission policy is needed for field-level restrictions.