Stack
Ce qui fait tourner mon infrastructure personnelle, avec le raisonnement derrière chaque choix. Et, plus utile encore, ce que j'ai écarté : un outil retenu ne dit rien tant qu'on ignore contre quoi il a gagné.
Cette page décrit mon infrastructure personnelle, celle dont je maîtrise chaque décision. Les outils que j'exploite en entreprise — Active Directory, CrowdStrike, Fortinet, Proxmox, Prometheus/Grafana — relèvent de contraintes qui ne sont pas les miennes ; ils sont décrits dans l'étude de cas dédiée.
Langage
-
Go
Les trois applications sont des binaires Go uniques, compilés avec
CGO_ENABLED=0. Un binaire statique, pas d'interpréteur, pas de gestionnaire de paquets au runtime : l'image finale contient un fichier et rien d'autre. Sur un cluster à 1,8 Go, ce n'est pas de l'esthétique, c'est du budget mémoire.Écarté : Python et Node. Excellents pour itérer, mais l'un comme l'autre imposent un runtime, un arbre de dépendances à maintenir et une surface de vulnérabilités qui vit sa propre vie. Pour du logiciel que je dois pouvoir laisser tourner des mois sans y toucher, le compromis penche nettement.
-
La bibliothèque standard, presque seule
Pas de framework web, pas d'ORM :
net/httpetdatabase/sqlsuffisent. Les seules dépendances directes du projet Frigo sont un pilote SQLite et une bibliothèque Web Push — le reste est de la stdlib.Écarté : Gin, Echo, GORM. Ils font gagner du temps au démarrage et en coûtent à chaque montée de version. Sur un projet dont je suis le seul mainteneur, moins de dépendances = moins de choses qui cassent pendant que je regarde ailleurs.
Données
-
SQLite via
modernc.org/sqliteUne base par foyer, un fichier sur un volume persistant. Le pilote est une transpilation de SQLite en Go pur : pas de CGO, donc la compilation statique reste possible et l'image finale n'embarque aucune bibliothèque C.
Écarté :
mattn/go-sqlite3, plus rapide, mais qui impose CGO — donc une chaîne de compilation croisée, une image de base non vide, et l'abandon du binaire statique. Le gain de performance était sans objet à cette échelle.Conséquence assumée : un seul writer. D'où
SetMaxOpenConns(1), une réplique unique et une stratégie de déploiementRecreate— donc quelques secondes de coupure à chaque mise à jour. Pourquoi je n'ai pas « corrigé » ça : la note qui raconte le diagnostic. -
MariaDB pour Coach Adaptatif
Instance dédiée, isolée par NetworkPolicy, sauvegardée par CronJob quotidien avec checksums. Le modèle de données y est franchement relationnel et concurrent — le cas où SQLite n'est plus le bon outil.
Écarté : mutualiser une base pour les deux applications. Ça aurait économisé de la RAM, au prix d'un couplage entre deux projets sans rapport et d'une panne partagée.
Plateforme
-
k3s
Kubernetes single-node sur un VPS Debian. Distribution allégée : etcd remplacé par SQLite, Traefik et le contrôleur Helm intégrés au binaire — donc 0 Mo résident pour ce dernier.
Écarté :
kubeadm, qui ne tenait pas dans l'enveloppe mémoire. Et docker-compose, que j'utilisais avant : parfaitement suffisant pour faire tourner des conteneurs, mais sans réconciliation d'état, sans rollback déclaratif, sans NetworkPolicies. -
Flux CD
GitOps en pull : le cluster tire son état depuis un dépôt Git et le réconcilie en continu. Le pipeline n'a aucun accès SSH ni kubectl sur la machine ; un rollback est un
git revert.Écarté : Argo CD, plus complet mais nettement plus gourmand, et son interface web serait une surface exposée de plus. Écarté aussi : le déploiement en push depuis la CI — ce serait donner à un tiers un chemin d'accès permanent à la production. C'est la même raison qui fait que les chiffres du cluster affichés sur l'étude de cas sont mesurés à la main.
-
SOPS + age
Les secrets sont versionnés dans Git, chiffrés. Déchiffrés uniquement dans le cluster, par une clé qui n'existe nulle part ailleurs. Un diff de secret reste lisible en structure : on voit quelle clé a changé, jamais sa valeur.
Écarté : HashiCorp Vault — dimensionné pour des organisations, pas pour un nœud. Sealed Secrets, qui ne permet pas de relire ce qu'on a chiffré. Et surtout : les secrets hors de Git, qui reviennent à ne plus savoir ce qui est réellement déployé.
-
Traefik + cert-manager
Ingress fourni par k3s, certificats Let's Encrypt renouvelés automatiquement. Le port 6443 de l'API Kubernetes n'est pas exposé : l'administration passe par tunnel SSH.
Durcissement
-
Conteneurs contraints par défaut
Chaque pod tourne en
runAsNonRoot(UID 65532), système de fichiers racine en lecture seule,seccompProfile: RuntimeDefault, capabilities retirées, jeton de compte de service non monté. Les images sont épinglées par digest et un scan Trivy bloque la CI.Le principe : ces réglages ne sont pas ajoutés après un incident, ils sont le point de départ. Kubernetes, lui, laisse tout passer par défaut — y compris le trafic entre pods, d'où des NetworkPolicies sur chaque base.
-
Ce site
nginx non privilégié servant des fichiers statiques.
default-src 'none', le seul script inline autorisé par son empreinte SHA-256, HSTS avec préchargement,security.txtconforme à la RFC 9116. Zéro cookie, zéro tracker, zéro requête tierce — y compris pour les polices, servies depuis ce domaine (deux fichiers WOFF2 sous licence OFL, réduits aux caractères latins, environ 60 Ko).Écarté : tout générateur de site statique. Des pages HTML écrites à la main, une feuille de style, deux petits fichiers JavaScript facultatifs. Ajouter Hugo ou Astro serait introduire une chaîne de build pour un problème que je n'ai pas. Les blocs répétés sur chaque page (navigation, pied de page) sont synchronisés entre marqueurs par un script Python sans dépendance, que la CI exécute en mode vérification : une page qui diverge fait échouer le build. Écartés aussi : les analytics et un CDN.
La bascule de thème manuelle, longtemps écartée parce qu'elle semblait exiger un script inline dans le
<head>, est désormais en place sans rien céder : un fichier externe de quelques lignes, chargé avant le rendu, applique le choix mémorisé dans le navigateur. Pas de scintillement, pas d''unsafe-inline', pas d'empreinte CSP supplémentaire, et rien n'est transmis au serveur.
Ce que je n'utilise pas
-
Pas de supervision sur ce cluster
Pas de Prometheus ni de Grafana ici : la pile complète coûterait plus de RAM que les trois applications réunies. Je m'appuie sur les probes Kubernetes, les logs et une vérification manuelle. C'est un compromis, pas une bonne pratique — sur l'infrastructure d'entreprise, où la contrainte mémoire n'existe pas, la supervision est en place.
-
Pas de haute disponibilité
Un nœud, une réplique par application. Une panne matérielle, c'est une interruption. Le choix est budgétaire et assumé : mieux vaut un single-node dont je connais chaque composant qu'un semblant de HA dont je ne pourrais pas payer le vrai coût.
Les décisions racontées en détail — avec les erreurs de diagnostic — sont dans les notes techniques.