Case study - Infrastructure & DevOps
Homelab Proxmox : une plateforme DevOps auto-hébergée, entièrement en code
Un PC Proxmox, devenu une plateforme complète d'Infrastructure as Code - registre d'images, supervision multi-machines et déploiement continu.


Contexte
Mon homelab repose sur deux machines.
Le Raspberry Pi, la machine de production : il héberge 24 heures sur 24, une pile de services personnels que j'utilise réellement au quotidien (reverse proxy, DNS, gestionnaire de mots de passe, cloud personnel, synchronisation de notes).
Le PC sous Proxmox, à côté, ne faisait tourner qu'un seul conteneur : Uptime Kuma, un simple outil de vérification de disponibilité.
Ce projet est né de ce constat : plutôt que de laisser ce Proxmox inutilisé, en faire une vraie plateforme d'infrastructure - et surtout, saisir l'occasion pour sortir de ma zone de confort sur des sujets que je maîtrisais peu jusque-là : l'Infrastructure as Code, l'observabilité multi-machines, les pipelines CI/CD de bout en bout.
Le PC a peu de RAM, donc conteneurs LXC plutôt que VM classiques pour rester léger.
Architecture globale
Les deux machines physiques communiquent via un réseau mesh Tailscale, le Proxmox jouant le rôle de routeur de sous-réseau (subnet router) : il expose tout le réseau local du homelab au mesh, de sorte que les deux machines et leurs services se rejoignent comme s'ils étaient sur le même LAN - sans jamais ouvrir le moindre port en frontal sur Internet.
Les quatre conteneurs LXC
| Conteneur | Rôle | Fonction |
|---|---|---|
| Uptime Kuma | Disponibilité | Surveille que les services web répondent encore. |
| Prometheus + Grafana | Métriques | Collecte et visualise les données système des deux machines. |
| Docker Registry | Distribution | Registre privé d'images Docker, interne au réseau Tailscale. |
| CI/CD Runner | Automatisation | Exécute les pipelines GitHub Actions auto-hébergés. |
Schéma d'architecture
flowchart TB
Dev(("Moi - git push")) --> GHA["GitHub Actions"]
GHA --> Runner
subgraph Proxmox["PC Proxmox - ~2,7 Go RAM - routeur Tailscale"]
direction TB
Runner["CI/CD Runner (LXC)"]
Registry["Docker Registry (LXC)"]
Kuma["Uptime Kuma (LXC)"]
Prom["Prometheus + Grafana (LXC)"]
ExpProxmox["node_exporter"]
end
subgraph Pi["Raspberry Pi - production 24/7"]
direction TB
Services["Services personnels"]
API["Démo API FastAPI"]
Daemon["Daemon télécommande (Python)"]
ExpPi["node_exporter"]
end
Runner -->|"build croisé + push"| Registry
Runner -->|"SSH : pull + redeploy"| API
Registry -.-> API
Kuma -.-> Services
Kuma -.-> API
Prom --> ExpProxmox
Prom --> ExpPi
Proxmox <-.->|"mesh Tailscale"| Pi
Remote["Télécommande C++/Qt 6"] -.->|"socket TCP"| Daemon
Daemon -.-> API
Flux complets - CI/CD, supervision, et le pont vers la télécommande (section 7)

Pilier Infrastructure as Code : tout en Ansible
Le fil conducteur du projet est l'Infrastructure as Code. Plutôt que de cliquer dans des interfaces et de taper des commandes que j'oublierais six mois plus tard, j'ai décrit l'intégralité de l'infrastructure dans du code Ansible versionné sur Git.
Concrètement : le dépôt Git est l'infrastructure. Chaque conteneur, chaque service, chaque réglage réseau y est déclaré noir sur blanc, sous forme de rôles Ansible modulaires - un rôle par service, jamais un script monolithique.
- name: Créer le conteneur LXC du registre Docker
community.general.proxmox_lxc:
api_host: "{{ proxmox_api_host }}"
api_token_secret: "{{ proxmox_api_token_secret | vault }}"
hostname: docker-registry
ostemplate: "local:vztmpl/debian-12-standard_amd64.tar.zst"
memory: 256
cores: 1
state: present
- name: Déployer le conteneur registry (idempotent)
community.docker.docker_container:
name: registry
image: "registry:2"
state: started
restart_policy: unless-stopped
ports:
- "127.0.0.1:5000:5000"
volumes:
- registry_data:/var/lib/registryExtrait illustratif, représentatif de la structure réelle des rôles du dépôt homelab-proxmox-iac.
Ce qui rend tout ça fiable : l'idempotence
Chaque rôle Ansible décrit un état cible, pas une suite d'actions à rejouer. Un script peut être relancé sans effet de bord : il n'agit que si l'état réel diffère de l'état déclaré.
Concrètement : relancer le playbook après une modification manuelle malencontreuse corrige l'infrastructure plutôt que de la casser davantage, et qu'un rôle tourne sur une machine neuve ou déjà configurée, le résultat final est identique. Aucune peur de « relancer le script pour voir » - c'est justement le principe.
Reproductibilité, historique des changements dans Git, et une doc qui ne se périme jamais - c'est le code lui-même.
Pilier Observabilité : Prometheus & Grafana
« Voir l'état de tout mon homelab depuis un seul écran. »
Prometheus collecte les métriques système - CPU, RAM, disque, réseau - du Raspberry Pi et de l'hôte Proxmox, via un agent node_exporter déployé sur chaque machine. Il interroge (scrape) chaque agent à intervalle régulier et conserve l'historique : c'est la seule source de vérité chronologique de l'infrastructure, indépendante de l'état actuel des machines.
Grafana agrège le tout dans un tableau de bord unique, avec un sélecteur pour basculer d'une machine à l'autre en un coup d'œil. Chaque tableau de bord est lui-même défini en JSON versionné dans le dépôt - la même logique d'Infrastructure as Code appliquée à la supervision elle-même.
Les deux machines dialoguent au-delà de la simple remontée de métriques : l'hôte Proxmox expose l'état de ses conteneurs, que ce tableau de bord central peut exploiter, tandis que les outils du Raspberry Pi mesurent le trafic généré par l'ensemble de l'infrastructure.

Pilier CI/CD : du build croisé au déploiement SSH
« Je pousse mon code, et il se déploie tout seul. »
C'est le cœur du projet : une boucle CI/CD complète où, à chaque git push sur une application, une chaîne entièrement automatisée se déclenche. Pour la démontrer bout en bout, j'ai développé une petite API en FastAPI dédiée à cette démonstration (demo-api).
| # | Étape | Détail |
|---|---|---|
| 1 | Récupération | Le runner auto-hébergé (Proxmox) récupère le code à chaque push. |
| 2 | Build croisé | Image Docker compilée x86 → ARM64 (buildx + QEMU). |
| 3 | Publication | Image poussée sur le registre Docker privé (Tailscale). |
| 4 | Connexion SSH | Clé dédiée au déploiement, isolée de toute clé personnelle. |
| 5 | Redéploiement | Le Pi récupère la nouvelle image et redéploie le conteneur. |
| 6 | Health check | Validation automatique que le déploiement a réussi. |
name: Build & Deploy demo-api
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: self-hosted # runner auto-hébergé, sur le Proxmox
steps:
- uses: actions/checkout@v4
- name: Set up QEMU (émulation ARM64)
uses: docker/setup-qemu-action@v3
- name: Build croisé + push
run: |
docker buildx build \
--platform linux/arm64 \
--tag registry.internal:5000/demo-api:{{ github.sha }} \
--push .
- name: Déploiement SSH sur le Raspberry Pi
run: |
ssh -i {{ secrets.PI_DEPLOY_KEY }} deploy@pi.tailnet \
"docker pull registry.internal:5000/demo-api:{{ github.sha }} && \
docker compose up -d demo-api"
- name: Health check
run: curl --fail https://demo-api.tailnet/healthExtrait illustratif du pipeline réel - la forme exacte du workflow.
Détail technique - pourquoi un runner auto-hébergé
La charge de build (compilation croisée, plus lourde qu'un build natif) s'exécute sur du matériel déjà possédé, et le registre Docker privé n'a jamais besoin d'être exposé sur Internet - tout reste dans le réseau Tailscale, du premier au dernier octet.

homelab-proxmox-iac - même principe d'automatisation que le pipeline de déploiementLes 4 défis d'ingénierie résolus
Quatre problèmes ont demandé un vrai travail de diagnostic technique (un cinquième, l'idempotence des rôles Ansible, est traité en section 3, car il relève davantage de la méthode que d'un incident ponctuel).
Build multi-architecture (x86_64 → ARM64)
Problème
Le runner CI/CD tourne sur un processeur x86_64 (le PC Proxmox), mais le Raspberry Pi cible est en ARM64. Une image construite nativement sur l'un ne s'exécute tout simplement pas sur l'autre - architecture de jeu d'instructions incompatible.
Solution
Build croisé avec docker buildx, combiné à une émulation QEMU enregistrée sur le runner : produire des images ARM64 valides depuis un hôte x86, sans second runner physique.
BuildKit & registre HTTP isolé
Problème
Le registre privé tourne en HTTP simple sur le réseau local. BuildKit, isolé dans son propre conteneur, ignorait la configuration réseau de l'hôte et refusait de communiquer avec un registre non-HTTPS - un comportement de sécurité par défaut.
Solution
Fournir à BuildKit sa propre configuration explicite, listant le registre interne comme insecure registry de confiance.
[registry."registry.internal:5000"]
http = true
insecure = trueRéseau & DNS des conteneurs LXC
Problème
Les conteneurs n'avaient initialement aucun accès à Internet. Deux causes distinctes : d'abord une mauvaise passerelle héritée, puis - une fois corrigée - un DNS injecté par Tailscale qui ne résolvait pas les adresses publiques.
Solution
Diagnostic couche par couche, dans l'ordre du modèle réseau - routage, puis passerelle, puis résolution de noms - jusqu'à isoler précisément où la chaîne se rompait.
Sécurité - gestion des secrets
Problème
Aucun secret ne doit jamais apparaître en clair dans un dépôt Git versionné, y compris pour un projet personnel - une hygiène qui se pratique, pas une option qu'on active seulement en entreprise.
Solution
Le token d'API Proxmox est chiffré avec Ansible Vault, déchiffré uniquement à l'exécution. La clé SSH de déploiement est isolée dans un secret GitHub, injectée uniquement au moment de l'exécution, avec des droits limités au strict nécessaire.
Raspberry Pi
La moitié « production » du homelab : en service 24 heures sur 24 depuis des années, bien avant ce projet. Huit services personnels y tournent au quotidien, chacun dans son propre conteneur Docker, administrés via Portainer - une pile indépendante du Proxmox.
Les services hébergés
| Service | Rôle | Description |
|---|---|---|
| AdGuard Home | DNS | Bloqueur de publicités et de traqueurs au niveau du réseau, pour tous les appareils connectés à la maison. |
| Vaultwarden | Secrets | Implémentation légère en Rust compatible Bitwarden, pour le stockage chiffré des identifiants et clés d'accès. |
| Nextcloud + MariaDB | Stockage | Plateforme cloud auto-hébergée avec base MariaDB dédiée, sauvegarde automatique de documents et médias. |
| CouchDB | Synchronisation | Synchronisation chiffrée de bout en bout des notes de prise de connaissance (Obsidian), entre tous mes appareils. |
| Zoraxy | Réseau | Reverse proxy moderne écrit en Go : certificat wildcard unique via challenge DNS-01 (DuckDNS) pour tous les sous-domaines, interface de supervision intégrée. |
| Portainer | Administration | Vue en temps réel sur les conteneurs, images et stacks Docker Compose du Pi. |
| Homepage | Tableau de bord | Raccourcis et statut de tous les services du réseau local, regroupés en un point d'entrée unique. |
| IA Portfolio API | Backend | Micro-service hébergeant le moteur d'IA qui alimente les réponses de ce portfolio, aux côtés de tout le reste. |
La télécommande C++/Qt 6
SSH fonctionne très bien pour administrer le homelab. Mais je voulais un outil lançable depuis mon téléphone entre deux cours, sans client VPN lourd ni terminal à sortir. La solution : une petite application client/serveur maison.
Un daemon Python tourne en tâche de fond sur le Raspberry Pi, géré comme un service système persistant via systemd, démarrant automatiquement au boot. Il attend une poignée de commandes texte sur un simple socket TCP - rien de plus. Le client, écrit en C++ avec Qt 6 côté Windows, n'est que l'interface graphique qui envoie ces commandes et affiche le flux de réponse en temps réel - via le même mesh Tailscale que le reste du projet.
[Unit]
Description=Daemon de télécommande (socket TCP)
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/piserver/daemon.py
Restart=on-failure
User=piserver
[Install]
WantedBy=multi-user.targetCommandes exposées
| Catégorie | Commandes |
|---|---|
| Surveillance | CPU & charge, température SoC (vcgencmd), disque (df -h), RAM & uptime |
| Conteneurs | Liste (docker ps), logs temps réel, start / stop / restart |
| Énergie | Reboot propre, extinction sécurisée |


Interconnexion : comment les deux projets dialoguent
Les deux projets ne sont pas juste voisins sur la même machine - ils se croisent concrètement :
sequenceDiagram
participant Dev as Moi (git push)
participant Runner as Runner CI/CD (Proxmox)
participant Pi as Raspberry Pi
participant Remote as Télécommande (Qt/C++)
Dev->>Runner: git push (demo-api)
Runner->>Runner: build croisé x86 → ARM64
Runner->>Pi: SSH - pull image + redeploy
Pi-->>Runner: health check OK
Note over Pi: Le conteneur demo-api tourne<br/>désormais sur le Pi
Remote->>Pi: socket TCP via Tailscale - "docker ps"
Pi-->>Remote: liste des conteneurs (dont demo-api)
Remote->>Pi: "docker restart demo-api"
Pi-->>Remote: confirmation
Le conteneur que le pipeline CI/CD vient de déployer automatiquement sur le Pi est le même conteneur que je peux surveiller et redémarrer depuis mon téléphone via la télécommande - sans jamais ouvrir de terminal SSH.
Ce que ce projet démontre pour mon profil
| Domaine | Démontré par |
|---|---|
| Infrastructure as Code | Rôles Ansible modulaires, idempotents et versionnés - zéro configuration manuelle. |
| DevOps / CI-CD | Chaîne d'intégration et déploiement continus de bout en bout, runner auto-hébergé, build croisé multi-architecture. |
| Observabilité | Supervision multi-machines avec Prometheus, Grafana, exporteurs de métriques déployés en parallèle. |
| Administration système & réseau | Conteneurs LXC, Docker, réseau mesh VPN, diagnostic réseau/DNS couche par couche, SSH. |
| Sécurité | Ansible Vault, secrets GitHub, séparation stricte des privilèges. |
| Programmation réseau (C++/Python) | Architecture client/serveur sur socket TCP, daemon systemd, interface Qt 6. |
| Autonomie | Projet mené intégralement seul, technologies nouvelles défrichées, chaque blocage diagnostiqué et résolu. |
Pistes d'évolution & liens
Évolutions envisagées
Servir le registre privé en HTTPS · rendre la configuration DNS entièrement déclarative · ajouter un lint automatisé (ansible-lint, hadolint) dans la CI · enrichir Grafana avec des alertes actives.
Liens du projet
| Infrastructure (Ansible) | github.com/overtexdev/homelab-proxmox-iac |
| Démo CI/CD | github.com/overtexdev/demo-api |