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.

AnsibleProxmox VEDockerPrometheusGrafanaGitHub ActionsTailscaleFastAPIQt 6 / C++

Rôle

Conception & réalisation intégrale

Type

Projet personnel

Statut

Opérationnel, 24/7

Dépôts

iac·demo-api

4
Conteneurs LXC
6
Étapes CI/CD auto.
Photo du rack physique : PC Proxmox, switch réseau et Raspberry Pi
Le rack en vrai - PC Proxmox, switch et Raspberry Pi 4, 8 Go de RAM
Dashboard Grafana « Node Exporter Full » affichant les métriques temps réel du Proxmox
Dashboard Grafana - métriques temps réel du Proxmox (CPU, RAM, disque, réseau)
01Contexte

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.

↑ Sommaire
02Vue d'ensemble

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

ConteneurRôleFonction
Uptime KumaDisponibilitéSurveille que les services web répondent encore.
Prometheus + GrafanaMétriquesCollecte et visualise les données système des deux machines.
Docker RegistryDistributionRegistre privé d'images Docker, interne au réseau Tailscale.
CI/CD RunnerAutomatisationExé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)

Interface Proxmox VE listant les 4 conteneurs LXC avec leur usage disque, mémoire, CPU et uptime
Vue Proxmox des 4 conteneurs LXC - disque, mémoire, CPU et uptime réels
↑ Sommaire
03Pilier

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.

roles/docker_registry/tasks/main.ymlyaml
- 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/registry

Extrait 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.

↑ Sommaire
04Pilier

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.

Le même dashboard Grafana « Node Exporter Full », basculé sur le Raspberry Pi via le sélecteur de machine
Même tableau de bord, sélecteur basculé sur le Raspberry Pi - la supervision multi-machines en pratique
↑ Sommaire
05Pilier

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).

#ÉtapeDétail
1RécupérationLe runner auto-hébergé (Proxmox) récupère le code à chaque push.
2Build croiséImage Docker compilée x86 → ARM64 (buildx + QEMU).
3PublicationImage poussée sur le registre Docker privé (Tailscale).
4Connexion SSHClé dédiée au déploiement, isolée de toute clé personnelle.
5RedéploiementLe Pi récupère la nouvelle image et redéploie le conteneur.
6Health checkValidation automatique que le déploiement a réussi.
.github/workflows/deploy.ymlyaml
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/health

Extrait 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.

Historique des workflows GitHub Actions du dépôt homelab-proxmox-iac, tous réussis
CI de validation Ansible sur le dépôt homelab-proxmox-iac - même principe d'automatisation que le pipeline de déploiement
↑ Sommaire
06Ingénierie

Les 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).

01

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.

02

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.

/etc/buildkit/buildkitd.tomltoml
[registry."registry.internal:5000"]
  http = true
  insecure = true
03

Ré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.

04

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.

↑ Sommaire

Projet complémentaire - pas la plateforme DevOps ci-dessus

07Focus annexe

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

ServiceRôleDescription
AdGuard HomeDNSBloqueur de publicités et de traqueurs au niveau du réseau, pour tous les appareils connectés à la maison.
VaultwardenSecretsImplémentation légère en Rust compatible Bitwarden, pour le stockage chiffré des identifiants et clés d'accès.
Nextcloud + MariaDBStockagePlateforme cloud auto-hébergée avec base MariaDB dédiée, sauvegarde automatique de documents et médias.
CouchDBSynchronisationSynchronisation chiffrée de bout en bout des notes de prise de connaissance (Obsidian), entre tous mes appareils.
ZoraxyRéseauReverse proxy moderne écrit en Go : certificat wildcard unique via challenge DNS-01 (DuckDNS) pour tous les sous-domaines, interface de supervision intégrée.
PortainerAdministrationVue en temps réel sur les conteneurs, images et stacks Docker Compose du Pi.
HomepageTableau de bordRaccourcis et statut de tous les services du réseau local, regroupés en un point d'entrée unique.
IA Portfolio APIBackendMicro-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.

/etc/systemd/system/piserver.serviceini
[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.target

Commandes exposées

CatégorieCommandes
SurveillanceCPU & charge, température SoC (vcgencmd), disque (df -h), RAM & uptime
ConteneursListe (docker ps), logs temps réel, start / stop / restart
ÉnergieReboot propre, extinction sécurisée
Interface GUI Windows de la télécommande, Qt 6 / C++
Client GUI Windows (Qt 6 / C++), console de réponse en temps réel
Schéma d'architecture et flux réseau de la télécommande
Schéma d'architecture & flux réseau - client Windows, daemon Pi, mesh Tailscale

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.

↑ Sommaire
08Bilan

Ce que ce projet démontre pour mon profil

DomaineDémontré par
Infrastructure as CodeRôles Ansible modulaires, idempotents et versionnés - zéro configuration manuelle.
DevOps / CI-CDChaî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éseauConteneurs 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.
AutonomieProjet mené intégralement seul, technologies nouvelles défrichées, chaque blocage diagnostiqué et résolu.
↑ Sommaire
09Prochaines étapes

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