Le problème : deux dashboards, un seul qui marche
L’article Dashboard du cluster avec Homepage + Glances documentait Homepage + Glances comme point d’entrée « auto-découvert » du homelab. En pratique, ce dashboard cohabitait depuis des mois avec un second hub personnel — celui qui sert cet article — et Homepage était devenu peu fiable et de moins en moins consulté : services mal découverts, widgets qui timeoutent, une YAML de configuration qu’il fallait rouvrir à chaque nouvelle app pour retrouver la bonne syntaxe.
Plutôt que de le réparer une énième fois, la décision a été plus radicale : décommission complète. Aucun remplacement « meilleur outil d’auto-découverte » — juste sa suppression pure et simple du cluster.
index.html statique dans une ConfigMap : zéro paquet,
sauf sur l'unique route /admin/api qui proxie vers homelab-admin.
Homepage kept polling continuously and eventually timed out, duplicating the personal hub — decommissioned rather than fixed one more time.
The hub that remains is a static index.html in a ConfigMap: zero packets,
except on the single /admin/api route that proxies to homelab-admin.
Le choix : statique plutôt que dynamique
Le hub qui reste tourne sur un principe volontairement pauvre : un unique index.html stocké dans une ConfigMap Kubernetes (hub-static-html, namespace yb-personal), servi par un simple nginx:1.27-alpine monté en volume :
# yb-personal/deploy-hub-static.yaml (extrait)
containers:
- image: nginx:1.27-alpine
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: html-volume
volumes:
- configMap:
name: hub-static-html
name: html-volume
Aucune dépendance runtime, aucun pod qui peut planter faute de connexion à une API tierce, aucun DSL YAML à apprendre pour ajouter un service. Éditer le hub, c’est modifier un fichier HTML dans le repo GitOps, commit, et laisser ArgoCD synchroniser — le même geste que pour n’importe quel autre composant du cluster. Pour un dashboard à usage strictement personnel, la « découverte automatique » de Homepage résolvait un problème que je n’avais pas vraiment : la liste des services change une fois toutes les deux semaines, pas toutes les heures.
Design system « Pro Ops »
La palette part d’un fond quasi-noir plutôt que du classique thème sombre à dégradé : #0a0e14 en canvas, #10151d pour les surfaces, bordures #1f2733. Un seul accent — un teal #2dd4bf — et les trois couleurs sémantiques classiques réservées exclusivement aux statuts : vert #3fb950 (ok), ambre #d29922 (warn), rouge #f85149 (err). Deux polices : Inter pour l’interface, JetBrains Mono pour tout ce qui est valeur, IP ou métrique — la distinction typographique aide à repérer d’un coup d’œil ce qui est donnée brute versus ce qui est libellé.
Le layout est pensé pour un mur d’écran, pas pour un laptop. Les breakpoints Tailwind par défaut s’arrêtent à 2xl (1536px) ; j’en ai ajouté deux au-delà :
screens: {
'3xl': '2200px',
'4xl': '2800px',
},
Le catalogue de services est un layout masonry en colonnes CSS, qui s’élargit avec l’écran : une colonne en mobile, jusqu’à six en 4K.
<main class="columns-1 md:columns-2 xl:columns-3 2xl:columns-4 3xl:columns-5 4xl:columns-6 gap-5 2xl:gap-6">
Le conteneur principal suit la même logique (max-w-[2560px], 4xl:max-w-[3360px]) : sur un écran 4K accroché au mur, le hub utilise vraiment la largeur disponible au lieu de rester figé dans une colonne centrale étroite.
Le seul bout dynamique : un proxy API pour le vivant
Tout le HTML est statique — sauf les quelques widgets qui ont besoin de données en temps réel (état des nœuds, à terme cpu/mem live). Plutôt que de dupliquer une API dans le hub lui-même, ces appels sont routés vers le service homelab-admin déjà existant, via une IngressRoute Traefik dédiée :
# homelab-admin/ingressroute-hub-proxy.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: hub-admin-proxy
namespace: homelab-admin
spec:
routes:
- match: Host(`hub.olig-homelab-2025.win`) && PathPrefix(`/admin/api`)
kind: Rule
priority: 1000
services:
- name: homelab-admin
namespace: homelab-admin
port: 8080
Le détail qui compte ici, c’est la priority: 1000. L’Ingress Kubernetes standard du hub route déjà / vers hub-static-svc. Sans priorité explicite plus élevée sur la règle /admin/api, Traefik pourrait laisser la règle la plus large (le préfixe racine) intercepter des requêtes qui devraient partir vers homelab-admin. Une priorité forcée à 1000 lève toute ambiguïté : le préfixe API gagne toujours, peu importe l’ordre de déclaration des deux ressources dans le cluster.
Sections ajoutées au fil de l’eau
Le hub n’a pas été livré figé un jour puis oublié — il reflète l’infra au fur et à mesure que celle-ci change. Le déploiement du pipeline de scan documentaire (Paperless-ngx, Paperless-AI, scanservjs) a immédiatement donné lieu à l’ajout d’une section « Scan & OCR » dans le catalogue. Le lendemain, une fois la décommission de Homepage actée, son lien dans la section « Sites & Apps » a été retiré à son tour — deux commits distincts, chacun réagissant à un changement réel de l’infra plutôt qu’anticipant un plan figé à l’avance.
C’est la différence pratique entre un dashboard « intelligent » qui devrait en théorie suivre tout seul, et un fichier statique versionné : ce dernier ne suit rien automatiquement, mais chaque changement est un diff Git lisible, daté, et volontaire.
Leçon transférable
Pour un dashboard personnel à faible fréquence de changement, un fichier HTML statique versionné dans Git bat presque toujours un outil « intelligent » de plus à opérer. Moins de surface de panne — pas de pod supplémentaire qui peut planter, pas de découverte automatique qui se trompe de service. Un historique Git lisible plutôt qu’un état caché dans une base de config. Et le vrai coût, le temps passé à éditer du HTML à la main, reste marginal comparé au temps qu’aurait coûté un nouveau débogage de Homepage.
Le hub tourne aujourd’hui derrière hub.olig-homelab-2025.win, GitOps via ArgoCD comme le reste du cluster, avec homelab-admin comme unique dépendance dynamique. Pas de dashboard de plus — un dashboard de moins, et celui qui reste est plus simple que les deux qu’il remplace.