Post

Home-Assistant in Kubernetes [First Part - HA Deployment]

You will learn how to install and Deploy Home-Assistant in Kubernetes

Home-Assistant in Kubernetes [First Part - HA Deployment]

Deploy Home-Assistant in Kubernetes [First Part - Deployment]

One of the most comfortable ways to run Home Assistant in a homelab is plain Docker — but what if we go one step further and deploy it on a Kubernetes cluster to make it more resilient? This is the first part of a three-part series on exactly that:

  1. Home-Assistant Deployment in Kubernetes (this post)
  2. Build your own Bastion-SSH for Deployments from GitHub (Second Part)
  3. CI Workflow to Update Home-Assistant configuration (Third Part)

🧱 What We’re Building

Specs used in this setup:

  • Kubernetes: 1.30.0 (k0s)
  • StorageClass: Longhorn (Chart 1.7.2)
  • MetalLB (Chart 0.14.5)
  • Nginx Ingress Controller (Chart 4.12.0) — optional

We’ll declare each piece of the deployment in order:

  • Namespace
  • PersistentVolumeClaim
  • StatefulSet
  • Service / LoadBalancer
  • Ingress (optional, to publish externally)

🛠️ The Manifests

1. Namespace

1
2
3
4
apiVersion: v1
kind: Namespace
metadata:
  name: home-assistant

2. PersistentVolumeClaim

The access mode is ReadWriteMany — we’ll need that later when a second container (the Bastion-SSH from Part 2) mounts the same volume for CI-driven config updates.

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: home-assistant-pvc
  namespace: home-assistant
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi # Adjust the size to your needs
  storageClassName: longhorn

3. StatefulSet

You can deploy Home Assistant as a Deployment or a StatefulSet — I’ve found the latter more convenient around container restarts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: home-assistant
  namespace: home-assistant
spec:
  replicas: 1
  serviceName: "home-assistant"  
  selector:
    matchLabels:
      app: home-assistant
  template:
    metadata:
      labels:
        app: home-assistant    
    spec:
      imagePullSecrets:
      - name: dockerhub-registry    
      containers:
        - name: ha-core
          image: ghcr.io/home-assistant/home-assistant:2025.3.3
          imagePullPolicy: Always
          resources:
            requests:
              memory: "512Mi"
            limits: 
              memory: "2512Mi"       
          ports:
            - containerPort: 8123
          volumeMounts:
            - name: pv-home-assistant
              mountPath: /config      
      volumes:
        - name: pv-home-assistant
          persistentVolumeClaim:
            claimName: home-assistant-pvc

4. LoadBalancer

Here I’m using a LoadBalancer service backed by MetalLB, just to get a stable internal IP.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
  name: home-assistant
  namespace: home-assistant
spec:
  type: LoadBalancer
  loadBalancerIP: 192.168.2.3
  selector:
    app: home-assistant
  ports:
    - name: http-initial
      protocol: TCP
      port: 8123
      targetPort: 8123
  externalTrafficPolicy: Local    

5. Ingress (optional)

If you want to publish Home Assistant externally, add an Ingress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: home-assistant-ingress
  namespace: home-assistant
  annotations:
    # This example is for nginx; if you're using something else, adjust accordingly
    kubernetes.io/ingress.class: nginx
spec:
  tls:
    - hosts:
        - home-assistant.mydomain.com
      secretName: my-certificate
  rules:
    # Edit this to a domain you control
    - host: home-assistant.mydomain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: home-assistant
                port:
                  number: 8123   

Home Assistant checks the Host header against trusted_proxies / use_x_forwarded_for in its own configuration.yaml once it’s behind an Ingress — if you see “400 Bad Request” after publishing it externally, that’s the first place to look.


✅ Voila!

Everything should be deployed and running. With MetalLB assigning the LoadBalancer IP, you can reach Home Assistant directly at <loadBalancerIP>:8123.

👉 Next Part: Bastion SSH

This post is licensed under CC BY 4.0 by the author.