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:stable runs as one privileged replica.
  • Host networking supports discovery protocols used by home-automation devices.
  • default/home-assistant-pvc provides 10 Gi from rook-ceph-block at /config.
  • MetalLB advertises 192.168.0.15; Home Assistant listens on TCP port 8123.
  • Traefik publishes homeass.brunner.ninja over its websecure entrypoint.
  • Traefik's existing default *.brunner.ninja certificate supplies TLS.
  • The /auth route uses default-authentik@kubernetescrd so Home Assistant's authentication callbacks are not intercepted by Authentik.
  • The Recreate strategy 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 of main.

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 default containing the reserved address 192.168.0.15.
  • A cluster policy that permits privileged containers and host networking.
  • Traefik ingress class traefik, with *.brunner.ninja configured as its default TLS certificate.
  • Authentik middleware default-authentik in namespace default.

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.ninja
  • http://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-assistant and home-assistant-auth.

It has no Kubernetes Secrets API access. First installation and RBAC bootstrap remain administrator operations.

One-time CI setup

  1. Run ./install.sh with an administrator identity so the workload resources exist.

  2. Run:

    ./create-ci-kubeconfig.sh
    
  3. Copy only the final base64 line into the Gitea repository Actions secret named KUBE_CONFIG_BASE64.

  4. Protect main, require the validation job before merging, and review manifest and workflow changes.

  5. 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.

S
Description
My kubernetes configuration files to run home-assistant in my kubernetes cluster
Readme GPL-3.0
56 KiB
Languages
Shell 100%