Home-Assistant in Kubernetes [First Part - HA Deployment]
You will learn how to install and Deploy Home-Assistant in Kubernetes
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:
- Home-Assistant Deployment in Kubernetes (this post)
- Build your own Bastion-SSH for Deployments from GitHub (Second Part)
- 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
Hostheader againsttrusted_proxies/use_x_forwarded_forin its ownconfiguration.yamlonce 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.
