La stack, en détail
Tout est infrastructure-as-code dans un seul dépôt :
cluster/ pour les briques transverses, apps/ pour les
applications. Un git clone + un make reconstruisent l'ensemble.
Le chemin d'une requête
Visiteur (Internet)
│ HTTPS
▼
┌─────────────────┐ TLS terminé chez Cloudflare
│ Cloudflare │ (aucun port ouvert sur la box)
└────────┬────────┘
│ tunnel chiffré
▼
┌─────────────────┐ cloudflared → traefik:80
│ Docker Swarm │
│ │ 1. error-pages + crowdsec (4xx/5xx soignés, IPS + WAF)
│ ┌────────────┐ │ 2. forwarded-https (schéma réel = https)
│ │ Traefik │──┼─▶ 3. authelia (SSO/2FA si app privée)
│ └─────┬──────┘ │ 4. sablier (réveille l'app si endormie)
│ ▼ │
│ ┌────────────┐ │ nginx · php-fpm · postgres
│ │ App │ │ …ou nginx-s3-gateway ← bucket R2 (cette vitrine)
│ └────────────┘ │
└─────────────────┘
│
▼ restic chiffré (planifié)
Cloudflare R2 (backups hors-site) Les choix qui comptent
Zéro port ouvert
Aucune redirection de port sur la box : tout le trafic entrant passe par un tunnel sortant Cloudflare. La surface d'attaque réseau côté maison est nulle.
Scale-to-zero
Les apps peu sollicitées descendent à 0 réplica et se réveillent à la première requête autorisée. RAM économisée sur du matériel modeste, avec une page d'attente soignée.
SSO d'abord, réveil ensuite
Authelia s'exécute avant Sablier : une requête non authentifiée est renvoyée au portail sans même réveiller l'application. On ne dépense des ressources que pour de vrais utilisateurs.
Reconstruisible
Tout est versionné et déployable en ssh + docker stack deploy, sans dépendre d'une UI. Un plan de reprise existe pour repartir d'un dépôt git et des sauvegardes.
Déploiement continu, secrets cloisonnés
Cette vitrine se reconstruit sur git push : GitHub Actions build, puis n8n déploie vers R2 et vide le cache — sans coupure (rolling). Les clés d'écriture R2 restent côté homelab (Docker secrets), jamais sur GitHub.
On ne s'alerte pas de sa propre panne
Tunnel, bases, services : tout part en notification push si ça défaille. Mais pas d'alerte « le canal de notif est mort » — elle transiterait par ce même canal. Un système ne surveille pas sa propre alerte ; c'est le rôle d'un veilleur externe. On préfère connaître ses angles morts plutôt que s'offrir une fausse assurance.
Le résolveur ne vit pas dans le cluster qu'il sert
Le DNS du LAN (AdGuard) est géré par le cluster — config versionnée, métriques, alertes, accès SSO — mais il tourne dehors, sur sa propre machine. Le mettre dans le Swarm créerait une dépendance circulaire : un déplacement de nœud couperait le DNS de toute la maison, et donc les outils pour le réparer. On intègre la gestion, pas le moteur.
Pas de point unique de défaillance
Les 3 Pi sont managers (quorum raft 2/3), l'ingress tourne en actif/actif, les bases sont en cluster Patroni (failover automatique) et chaque app flotte entre les nœuds — jusqu'à la couche d'authentification (Authelia + sessions) répliquée. Perdre un nœud — à chaud ou par crash — ne coupe ni le site, ni le pilotage, ni les données : validé par une matrice de 6 drills (sortie propre + crash sur chacun des 3 nœuds). Les Pi restent nomades, déplaçables un par un.
La source de vérité des secrets ne vit pas dans le cluster
Bitwarden Secrets Manager est le master : chaque stack reçoit ses secrets sous forme de fichier rendu, le contrat (les noms, jamais les valeurs) est versionné en git. Le runtime ne dépend jamais de Bitwarden — seuls la rotation et le disaster recovery y touchent. Perdre le disque du manager, c'est un make secrets-render ; faire tourner une clé, trois commandes.
Le matériel
- rpi4 — Raspberry Pi 4, manager (le nœud léger).
- rpi51 — Raspberry Pi 5 + NVMe (boot NVMe), manager & nœud de données : il porte un membre de chaque cluster Postgres (Patroni).
- rpi52 — second Raspberry Pi 5 + NVMe, manager : second membre de chaque base (Patroni) + apps qui flottent + jobs de build.
- Les 3 sont managers — quorum raft 2/3 : le cluster survit à la perte d'un nœud, et chaque Pi se déplace à chaud, un par un (ils sont nomades).
- Consommation totale : quelques watts. Pas de cloud, pas d'abonnement serveur.
Le détail des briques
Orchestration
| Docker Swarm | 3 nœuds Pi (NVMe), tous managers — quorum raft 2/3, tolère la perte d'un nœud |
| Portainer | UI + API (pilotée par le déploiement) |
Ingress / réseau
| Traefik | v3 — actif/actif sur les 3 managers ; reverse-proxy, métriques + traces OTLP |
| Cloudflare Tunnel | exposition publique, 0 port ouvert — cloudflared en HA |
| Sablier | scale-to-zero + page d'attente |
| error-pages | pages 4xx/5xx soignées, globales |
| AdGuard Home | DNS du LAN (hors-cluster, par principe) — filtrage pub/malware, DoH chiffré ; piloté en config-as-code, observé, en SSO |
Sécurité / identité
| CrowdSec | IPS + WAF (CRS) — réputation d'IP + AppSec |
| Authelia | SSO + 2FA (forwardAuth & OIDC) — entièrement HA : 2 répliques + sessions Valkey Sentinel |
| Vaultwarden | gestionnaire de mots de passe (Postgres HA, clé en secret) |
Applications
| Stickers Manager | l'app métier (nginx + php-fpm) |
| nginx-s3-gateway | sert cette vitrine depuis un bucket R2 |
| n8n | automatisation — pilote le déploiement continu |
Données / sauvegarde
| PostgreSQL + Patroni | une base dédiée par app, chacune en cluster 2 nœuds — failover automatique (etcd + HAProxy) |
| restic → Cloudflare R2 | backups chiffrés hors-site, 2 agents |
| Backrest | orchestration des sauvegardes |
Secrets
| Bitwarden Secrets Manager | la source de vérité (~70 secrets) — plus aucun mot de passe en clair dans les .env |
| bws + secrets-sync.sh | rendu par stack en .env.secrets (0600) ; rotation = 3 commandes ; comparaisons md5, jamais une valeur affichée |
| Docker secrets | les credentials runtime (clés RSA, tokens de jobs) — mirrorés dans Bitwarden pour la reprise |
Observabilité
| Prometheus | métriques cluster, apps & bases — un exporter par cible |
| Loki + Alloy | agrégation des logs (global shipper) |
| Tempo + OTel Collector | traces distribuées (service map) |
| Grafana | dashboards par service (n8n, Postgres, tunnel…) + suivi des releases + alertes + pont métriques↔logs↔traces |
| ntfy | notifications push — alertes Grafana, déploiements & nouvelles releases |
CI/CD & IaC
| GitHub Actions | build du site → artefact (jamais de clé R2) |
| n8n + Docker secrets | déploiement vers R2 + flush, secrets côté homelab |
| Makefile + scripts | ssh + docker stack deploy ; secrets rendus depuis Bitwarden (make secrets-render) |
| Git | tout versionné — github.com/zebby76/zebbox.net |
Le code de cette infrastructure est public : github.com/zebby76/zebbox.net