🔄 Mise à jour — juin 2026. ArgoCD ne surveille plus GitHub mais un Gitea self-hosted (URL interne
gitea.gitea.svc.cluster.local:3000, hors Cloudflare Access). Le pattern app-of-apps décrit ici est inchangé ; seule la source de vérité a basculé. Détails : Migrer un homelab GitOps de GitHub vers Gitea.
Principe du GitOps
La première fois que j’ai testé un kubectl edit à la main sur une ressource gérée par ArgoCD, ma modification a tenu trois minutes — le temps que selfHeal détecte l’écart avec Git et l’écrase pour revenir à l’état déclaré dans le dépôt. C’est ce jour-là que « Git comme source de vérité unique » a cessé d’être une bonne pratique qu’on coche, pour devenir une contrainte que le système impose vraiment.
GitOps est une pratique d’exploitation dans laquelle Git est la source de vérité unique pour l’état de l’infrastructure. Toute modification passe par un commit — pas de kubectl apply manuel en production. Le cluster converge automatiquement vers l’état décrit dans le dépôt.
Ce modèle apporte trois bénéfices concrets :
- Auditabilité : l’historique Git est le journal de toutes les modifications apportées au cluster.
- Reproductibilité : reconstruire le cluster depuis zéro revient à pointer ArgoCD vers le dépôt.
- Collaboration : les pull requests deviennent le mécanisme de revue des changements d’infrastructure.
Pourquoi ArgoCD ?
Flux et ArgoCD sont les deux outils GitOps dominants dans l’écosystème Kubernetes. J’ai choisi ArgoCD pour son interface web, qui rend l’état du cluster immédiatement lisible — pratique pour un homelab où l’on veut comprendre rapidement ce qui se passe.
ArgoCD surveille en continu le dépôt Git configuré et compare l’état désiré (les manifests YAML) avec l’état réel du cluster. Toute divergence est signalée et peut être auto-résolue via la politique selfHeal.
Installation
ArgoCD est installé bootstrappé manuellement une seule fois :
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Ensuite, ArgoCD se gère lui-même via GitOps — le manifest d’installation n’est jamais réappliqué manuellement.
Le pattern App-of-Apps
Le pattern App-of-Apps résout un problème de bootstrapping : comment déclarer plusieurs applications ArgoCD en GitOps ?
La solution : une “root app” qui pointe vers un répertoire contenant d’autres Application manifests. ArgoCD crée ces applications, qui à leur tour synchronisent leurs propres ressources.
Structure du dépôt
k3s-homelab/
├── argocd/
│ ├── bootstrap/
│ │ └── root-app.yaml # La root app — bootstrappée manuellement
│ └── apps/
│ ├── blog.yaml
│ ├── gitea.yaml
│ ├── longhorn.yaml
│ ├── monitoring.yaml
│ └── ... # Une Application par service
├── blog/
│ ├── deployment.yaml
│ └── service.yaml
├── gitea/
│ ├── deploy-gitea.yaml
│ ├── deploy-postgres.yaml
│ └── ...
└── ...
La root app
# argocd/bootstrap/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
finalizers: [resources-finalizer.argocd.argoproj.io]
spec:
project: default
source:
repoURL: [email protected]:YellowBeanie/k3s-homelab.git
targetRevision: actual-state
path: argocd/apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Une application enfant (exemple : Gitea)
# argocd/apps/gitea.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gitea
namespace: argocd
finalizers: [resources-finalizer.argocd.argoproj.io]
spec:
project: default
source:
repoURL: [email protected]:YellowBeanie/k3s-homelab.git
targetRevision: actual-state
path: gitea
destination:
server: https://kubernetes.default.svc
namespace: gitea
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
Note : les repoURL ci-dessus ([email protected]:...) reflètent l’état pré-migration de cet article (fin 2025). Depuis juin 2026, ces dépôts pointent vers Gitea self-hosted — voir Migrer un homelab GitOps de GitHub vers Gitea.
prune: true : ArgoCD supprime les ressources qui ont été retirées du dépôt Git. selfHeal: true : si une ressource est modifiée manuellement dans le cluster, ArgoCD la remet dans l’état Git automatiquement. CreateNamespace=true : le namespace est créé s’il n’existe pas.
git push sur actual-state est détecté par ArgoCD en ~30 s —
la root app relit argocd/apps/ et propage la synchronisation
en cascade vers chaque Application enfant, puis vers les ressources du cluster.
A git push to actual-state is picked up by ArgoCD in ~30 s —
the root app re-reads argocd/apps/ and propagates the sync
down the cascade to every child Application, then to the cluster resources.
Branche actual-state
Une décision importante : utiliser une branche dédiée actual-state comme source de vérité ArgoCD plutôt que main.
actual-state est la branche de travail principale. C’est sur elle que tous les commits sont faits, et c’est elle qu’ArgoCD surveille. La branche main est maintenue comme miroir synchronisé via force-push — elle reflète toujours actual-state mais n’est pas utilisée directement pour le déploiement.
🔄 Mise à jour — juin 2026. Depuis le passage à Gitea (voir l’encart en tête d’article),
actual-stateest devenue la branche par défaut unique du dépôt ; l’anciennemaina été retirée le 2026-06-27. Le mécanisme de miroir force-push décrit ci-dessus est donc historique.
Cette convention évite toute confusion : pousser sur actual-state = déploiement immédiat sur le cluster.
Workflow quotidien
Le flux complet pour modifier ou ajouter un service :
# 1. S'assurer d'être sur la bonne branche
git checkout actual-state
# 2. Modifier les manifests
vim mon-service/deployment.yaml
# 3. Committer avec le format conventionnel
git commit -m "fix(mon-service): augmenter les resource limits"
# 4. Pusher → ArgoCD se charge du reste
git push
ArgoCD détecte le changement en ~30 secondes et synchronise automatiquement. On peut suivre le déploiement dans l’UI : argocd.olig-homelab-2025.win.
Format des commits
type(service): description courte
feat(gitea): ajouter persistance PostgreSQL
fix(n8n): corriger la variable d'env DB_HOST
chore(blog): mettre à jour l'image Docker
security(code-server): révoquer le token exposé
Services actifs
| Service | Namespace | URL |
|---|---|---|
| ArgoCD | argocd | argocd.olig-homelab-2025.win |
| Blog | blog | blog.olig-homelab-2025.win |
| Code Server | code-server | code.olig-homelab-2025.win |
| Gitea | gitea | gitea.olig-homelab-2025.win |
| Homepage (décommissionné 2026-06-28, remplacé par un hub perso) | — | — |
| Claude WebUI | claude-webui | claude.olig-homelab-2025.win |
| n8n | n8n | n8n.olig-homelab-2025.win |
| Grafana | monitoring | grafana.olig-homelab-2025.win |
| Homelab Admin | homelab-admin | homelab-admin.olig-homelab-2025.win |
| Silex | silex | silex.olig-homelab-2025.win |
| Tapaz (Prsm Studio) | tapaz | tapaz.olig-homelab-2025.win |
Ajout d’un nouveau service
Ajouter un nouveau service au cluster se résume à :
- Créer un répertoire
nouveau-service/avec les manifests YAML. - Créer
argocd/apps/nouveau-service.yamlavec l’ApplicationArgoCD. - Committer et pusher sur
actual-state. - ArgoCD détecte le nouveau fichier dans
argocd/apps/, crée l’Application, puis synchronise les manifests du répertoire.
Aucune intervention manuelle sur le cluster. La boucle complète prend ~30 secondes.
Cas particulier : app dont la source vit dans un autre repo
Tous les services au-dessus sont des manifests + Dockerfile committés directement dans k3s-homelab. Mais certaines apps ont leur code source dans un repo dédié — c’est par exemple le cas de tapaz (le site Prsm Studio, dans YellowBeanie/prsm-studio).
Le risque : repasser à un GitOps “moitié-manuel” où, après chaque release du repo source, il faut copier le nouveau digest d’image dans k3s-homelab/<app>/deployment.yaml. Trois solutions classiques :
imagePullPolicy: Always+ tag mutable (:latest) — simple mais perd l’immutabilité, complique les rollbacks.- ArgoCD Image Updater — un controller qui watch GHCR et patch les manifests. Élégant mais ajoute un composant à maintenir.
- CI cross-repo — le workflow du repo source clone
k3s-homelab, patch le digest avecsed, commit, push. C’est ce qu’on utilise pourtapaz.
Le step ajouté au workflow build-image.yml du repo source :
- name: Bump digest in k3s-homelab
env:
DIGEST: ${{ steps.push.outputs.digest }}
SRC_SHA: ${{ github.sha }}
HOMELAB_REPO_TOKEN: ${{ secrets.HOMELAB_REPO_TOKEN }}
run: |
git clone --depth 1 --branch actual-state \
https://x-access-token:${HOMELAB_REPO_TOKEN}@github.com/YellowBeanie/k3s-homelab.git homelab
cd homelab
sed -i -E "s|(image: ghcr\.io/yellowbeanie/tapaz@)sha256:[a-f0-9]+|\1${DIGEST}|" tapaz/deployment.yaml
if git diff --quiet; then echo "no change"; exit 0; fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add tapaz/deployment.yaml
git commit -m "tapaz: bump image to ${DIGEST} (prsm-studio@${SRC_SHA})"
git push origin actual-state
Le secret HOMELAB_REPO_TOKEN est un PAT (idéalement fine-grained, scope Contents: write limité à k3s-homelab) déposé sur le repo source via gh secret set HOMELAB_REPO_TOKEN -R YellowBeanie/prsm-studio.
Résultat : un git push sur le repo source déclenche le build d’image et met à jour les manifests homelab. Le digest reste pinné, l’historique git montre quel commit source a produit quelle image, et le rollback est un git revert standard. Toute la traçabilité du GitOps, sans controller supplémentaire à exploiter.
Sealed Secrets — gérer les secrets en GitOps
Un problème classique du GitOps : les secrets ne peuvent pas être committés en clair dans Git. La solution utilisée ici est Sealed Secrets de Bitnami.
Le controller Sealed Secrets tourne dans le cluster et détient une clé asymétrique. L’outil CLI kubeseal chiffre un Secret Kubernetes en SealedSecret — un objet qu’il est parfaitement sûr de committer dans Git. Seul le controller peut le déchiffrer.
# Créer un secret et le chiffrer
kubectl create secret generic my-secret \
--from-literal=password=s3cr3t \
--dry-run=client -o yaml | \
kubeseal --controller-name sealed-secrets-controller \
--controller-namespace kube-system \
--format yaml > sealedsecret.yaml
Le SealedSecret résultant est committé dans Git. ArgoCD le synchronise, et le controller le déchiffre pour créer le Secret natif dans le cluster.
Leçons apprises
ServerSideApply=true est nécessaire pour certaines ressources complexes (CRDs, ressources avec champs immutables) qui causent des conflits avec l’apply client-side. Mieux vaut l’activer dès le départ.
Attention aux finalizers : resources-finalizer.argocd.argoproj.io garantit que ArgoCD nettoie les ressources orphelines avant de supprimer une Application. Sans lui, supprimer une Application dans ArgoCD ne supprime pas les ressources dans le cluster — ce qui peut créer des ressources zombies.
Ne jamais modifier manuellement les ressources gérées par ArgoCD sans désactiver temporairement selfHeal. Toute modification manuelle sera écrasée dans les 3 minutes suivantes.