🏠 Objectif#
Déployer Home Assistant dans votre cluster K3s 100 % GitOps :
- chart Helm local ultra‑léger (4 ressources)
- arborescence
helm/+overlays/prod/ - secrets chiffrés avec Bitnami Sealed Secrets
- synchro continue via Argo CD App‑of‑Apps
📂 1. Arborescence GitOps spécifique#

📝 2. Chart Helm minimal (helm/home-assistant/charts.yaml)#
apiVersion: v2
name: home-assistant # Réutilisé par helpers.tpl
description: Minimal chart for Home Assistant (GitOps)
type: application
version: 0.1.0 # Version du *chart* (pas de l'App)
appVersion: "2025.7.2" # Version HA par défaut
values.yaml#
# ---------------------------------------------------------------------------
# Valeurs partagées par tous les environnements. Les surcharges se font
# dans overlays/prod/values-prod.yaml
# ---------------------------------------------------------------------------
image:
repository: ghcr.io/home-assistant/home-assistant
tag: "2025.7.2" # 🔄 bump ici pour upgrade HA
replicaCount: 1 # HA n’est pas (encore) stateless → 1 seul pod
service:
type: ClusterIP
port: 8123
ingress:
enabled: true
className: nginx
host: votre-domaine.com
tlsSecret: homeassistant-tls # géré par cert‑manager
nodeSelector: {} # overridé en prod
resources: {} # overridé en prod
storage: # PVC Longhorn
size: 10Gi
storageClass: longhorn
accessMode: ReadWriteOnce
# ---------------------------------------------------------------------------
# Fichier configuration.yaml versionné → monté en lecture seule
# ---------------------------------------------------------------------------
configuration: |-
homeassistant:
external_url: "https://votre-domaine.com"
internal_url: "http://home-assistant.home-assistant.svc.cluster.local:8123"
http:
use_x_forwarded_for: true
trusted_proxies:
- 10.42.0.0/16 # CIDR pods/ingress
- 192.168.1.211 # IP MetalLB
- 192.168.1.0/24 # LAN
# Ajoute ici l’intégration de base souhaitée
default_config:
Vous pouvez ajouter vos intégrations YAML directement dans ce bloc, l’image étant read‑only.
📑 3 Templates#
_helper.tpl (utile pour ne pas répété les infos)#
{{- /* 📌 Fonctions utilitaires pour ne pas dupliquer les noms, labels, etc. */ -}}
{{- define "ha.name" -}} home-assistant {{- end }}
{{- define "ha.fullname" -}} {{ include "ha.name" . }} {{- end }}
{{- define "ha.labels" -}}
app.kubernetes.io/name: {{ include "ha.name" . }}
app.kubernetes.io/managed-by: Helm
{{- end }}
{{- define "ha.selectorLabels" -}}
app.kubernetes.io/name: {{ include "ha.name" . }}
{{- end }}
service.yaml#
# ---------------------------------------------------------------------------
# 📡 Expose Home Assistant en interne ou en externe selon le type de service
# (ClusterIP, NodePort, etc.)
# ---------------------------------------------------------------------------
apiVersion: v1
kind: Service
metadata:
name: {{ include "ha.fullname" . }}
labels: {{- include "ha.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
selector: {{- include "ha.selectorLabels" . | nindent 4 }}
pvc.yaml#
# ---------------------------------------------------------------------------
# 💾 Provisionne un volume persistant pour stocker les données de Home Assistant
# ---------------------------------------------------------------------------
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "ha.fullname" . }}-data
labels: {{- include "ha.labels" . | nindent 4 }}
spec:
accessModes: [{{ .Values.storage.accessMode }}]
storageClassName: {{ .Values.storage.storageClass }}
resources:
requests:
storage: {{ .Values.storage.size }}
configmap.yaml#
# ---------------------------------------------------------------------------
# ⚙️ Injecte la configuration YAML de Home Assistant depuis les valeurs Helm
# ---------------------------------------------------------------------------
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "ha.fullname" . }}-config
labels: {{- include "ha.labels" . | nindent 4 }}
data:
# Le fichier bootstrapé ; toute modif = PR → Argo CD → diff appliqué
configuration.yaml: |
{{ .Values.configuration | indent 4 }}
ingress.yaml#
# ---------------------------------------------------------------------------
# 🌐 Déclare un Ingress HTTPs avec redirection SSL et certificats Let's Encrypt
# (si activé)
# ---------------------------------------------------------------------------
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "ha.fullname" . }}
annotations:
cert-manager.io/cluster-issuer: letsencrypt-http01
nginx.ingress.kubernetes.io/limit-rpm: "300"
nginx.ingress.kubernetes.io/limit-burst-multiplier: "5"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
ingressClassName: {{ .Values.ingress.className }}
tls:
- secretName: {{ .Values.ingress.tlsSecret }}
hosts: [{{ .Values.ingress.host }}]
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "ha.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- end }}
deployment.yaml#
# ---------------------------------------------------------------------------
# 🚀 Déploie Home Assistant avec montée en réplica, volume persistant, ConfigMap # et variables d’environnement via Secret
# ---------------------------------------------------------------------------
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "ha.fullname" . }}
labels: {{- include "ha.labels" . | nindent 4 }}
annotations:
# Force le rollout si le ConfigMap change
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels: {{- include "ha.selectorLabels" . | nindent 6 }}
template:
metadata:
labels: {{- include "ha.selectorLabels" . | nindent 8 }}
spec:
nodeSelector: {{- toYaml .Values.nodeSelector | nindent 8 }}
containers:
- name: home-assistant
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8123
env: # ex: TZ
- name: TZ
value: Europe/Paris
# --- clé API Govee depuis le Secret scellé ---
- name: GOVEE_TOKEN
valueFrom:
secretKeyRef:
name: home-assistant-env # ⬅️ doit correspondre au SealedSecret
key: GOVEE_TOKEN
volumeMounts:
- name: data
mountPath: /config
- name: bootstrap-config
mountPath: /config/configuration.yaml
subPath: configuration.yaml
readOnly: true # ⬅️ verrou GitOps
resources: {{- toYaml .Values.resources | nindent 12 }}
volumes:
- name: data # PVC RW (Longhorn)
persistentVolumeClaim:
claimName: {{ include "ha.fullname" . }}-data
- name: bootstrap-config # ConfigMap RO
configMap:
name: {{ include "ha.fullname" . }}-config
📑 4. Overlays prod/#
overlays/prod/kustomization.yaml#
# ---------------------------------------------------------------------------
# 🧩 Point d’entrée Kustomize : applique le chart Helm local avec des valeurs # "prod" et un SealedSecret
# ---------------------------------------------------------------------------
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# On commence par tout ce qui se trouve dans ../../base
resources:
- home-assistant-env-sealed.yaml
# Chemin où Kustomize ira chercher les charts Helm locaux
helmGlobals:
chartHome: ../../helm
# Déclaration du chart à rendre pour cet overlay
helmCharts:
- name: home-assistant # dossier sous charts/
releaseName: home-assistant
namespace: home-assistant
valuesFile: values-prod.yaml # surcharge « prod »
includeCRDs: false
values-prod.yaml#
# ---------------------------------------------------------------------------
# 🔧 Surcharges propres à l’environnement "prod" : affinage CPU, mémoire et node # ciblé
# ---------------------------------------------------------------------------
nodeSelector:
kubernetes.io/hostname: cube00
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "2" # ← 2 vCPU complets
memory: 1Gi # ← gardez 1 Gi ; montez à 1.5 Gi si ça reste juste
home-assistant-env-sealed.yaml#
kubectl -n home-assistant create secret generic home-assistant-env \
--from-literal=GOVEE_TOKEN="<votre‑clé>" -o yaml --dry-run=client | \
kubeseal -n home-assistant \
--controller-namespace sealed-secrets \
--controller-name sealed-secrets \
--format yaml > overlays/prod/home-assistant-env-sealed.yaml
Argo CD le déchiffre à l’application et crée le Secret « clair » dans le namespace.
home-assistant-env.sealed.yaml#
# ---------------------------------------------------------------------------
# 🔐 Secret chiffré via SealedSecrets : injecte des variables d’environnement # sensibles (ex: GOVEE_TOKEN)
# ---------------------------------------------------------------------------
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: home-assistant-env
namespace: home-assistant
spec:
encryptedData:
GOVEE_TOKEN: Votre_Token_IcI_en_base64
template:
metadata:
creationTimestamp: null
name: home-assistant-env
namespace: home-assistant
📄 5. Déclaration Argo CD - app-of-apps (app.yaml)#
# ---------------------------------------------------------------------------
# 🚀 Manifest de déclaration de l'application dans ArgoCD
# ---------------------------------------------------------------------------
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: home-assistant
namespace: argocd
spec:
project: default
source:
repoURL: https://gitlab.com/<votre-repo>/homelab-gitops.git
targetRevision: main
path: apps/home-automation/home-assistant/overlays/prod
kustomize:
enableHelm: true # ← suffisant avec Argo v3
destination:
server: https://kubernetes.default.svc
namespace: home-assistant
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Tip : Ce fichier est découvert automatiquement par l’App‑of‑Apps parent, nul besoin de l’appliquer manuellement.
🕹️ 6. Étape unique de bootstrap#
# Argo CD est déjà installé
kubectl apply -f apps/home-automation/home-assistant/app.yaml
Si vous avez suivi les précédents articles sur app-of-apps, alors le déploiement se fera à la prochaine réconciliation.
En quelques secondes :
- le namespace
home-assistantest créé ; - PVC, ConfigMap, Ingress, Secret sont provisionnés ;
- votre instance Home Assistant est accessible sur https://votre-domaine.com avec certificat Let’s Encrypt.

🔌 Tester sans DNS : port forwarding local#
Si vous n’avez pas encore de nom de domaine configuré, accédez à Home Assistant localement :
kubectl -n home-assistant port-forward svc/home-assistant 8123:8123
Puis ouvrez : http://localhost:8123 🧪
Interface HA accessible en HTTPS ou via un port forwarding en local :

📌 7. Aller plus loin#
- Automatiser les sauvegardes (addon
Backup) vers votre NAS ; - Superviser Home Assistant via Prometheus/Loki (ServiceMonitor, règles PromQL) ;
- Ajouter des NetworkPolicies ou une
PodDisruptionBudget(voirbase/kustomization.yaml).
💡 La prochaine page détaillera l’intégration Govee LAN et la découverte mDNS depuis un Pod.
TL;DR#
| Élément | Localisation Git |
|---|---|
| Chart Helm minimal | helm/home-assistant/ |
| Overlay prod + Secret scellé | overlays/prod/ |
| Application Argo CD | app.yaml |
| Point d’entrée App‑of‑Apps | bootstrap/homelab.yaml → apps/ |
| URL d’accès | https://votre-domaine.com |
Accrochez‑vous : tout est piloté par Git ; un commit = un déploiement.✨
