❯ gitops · Running

Migrer un homelab GitOps de GitHub vers Gitea self-hosted Migrating a GitOps homelab from GitHub to self-hosted Gitea

Basculer la source de vérité d'un app-of-apps ArgoCD de GitHub vers un Gitea interne — conversion des repos, repointage en cascade, un pull-mirror cassé qui masquait un état obsolète, la cred en SealedSecret, et garder une ancre de rollback propre. Flipping the source of truth of an ArgoCD app-of-apps from GitHub to an internal Gitea — repo conversion, a cascade repoint, a broken pull-mirror that hid stale state, the SealedSecret credential, and keeping a clean rollback anchor.

· 6 min de lecture 6 min read ·
k3s-clustergiteagitopsargocdmigrationk8s
🇬🇧 Read this article in English

Le point de départ

Pendant des mois, le homelab a tourné sur une boucle GitOps classique : le code vit sur GitHub, ArgoCD surveille [email protected]:YellowBeanie/k3s-homelab.git (branche actual-state), et un Gitea self-hosted en garde un miroir local — 23 dépôts synchronisés en pull toutes les 8h. GitHub était la source de vérité ; Gitea, une copie de secours pratique.

L’objectif de cette migration : inverser le rapport. Faire de Gitea la source de vérité — code, GitOps et CI — et reléguer GitHub au rôle de simple backup. Moins de dépendance externe, et plus de raison de payer un abonnement pour quelque chose qu’on héberge déjà.

Sur le papier, c’est un changement de repoURL. En pratique, un app-of-apps qui pilote 25 applications n’aime pas qu’on lui change le sol sous les pieds sans préparation.

Étape 1 — Gitea devient primaire

Un pull-mirror Gitea est en lecture seule : il ne fait que recopier GitHub. Pour que Gitea devienne writable (et donc source de vérité), il faut convertir le miroir en dépôt normal. Côté Gitea, c’est une bascule d’état : on retire le repo de la table mirror et on passe is_mirror = false.

UPDATE repository SET is_mirror=false WHERE id=$RID;
DELETE FROM mirror WHERE repo_id=$RID;

On répète l’opération sur les dépôts à basculer, on ajoute un push-mirror Gitea → GitHub (pour garder GitHub à jour comme backup), et on vérifie le last_error de chaque mirror. Un seul mot d’ordre : synchroniser depuis GitHub juste avant de convertir, pour partir du contenu le plus frais.

Le piège : le miroir était cassé

C’est là que la migration a failli déraper. Le test de connectivité d’ArgoCD vers Gitea échouait avec app path does not exist sur un dossier qui existe pourtant. En creusant : la branche actual-state du miroir Gitea de k3s-homelab était figée des semaines en arrière. Le mirror-sync répondait bien 200, mais ne récupérait rien — credentials de miroir expirées, échec silencieux. Des apps récentes (alerting, victorialogs…) manquaient purement et simplement.

Le danger était réel : convertir ce miroir tel quel aurait fait revenir le cluster à un état obsolète. La parade : ne pas faire confiance au miroir. On clone le contenu actuel depuis GitHub, on repointe les repoURL, et on force-push vers Gitea. Gitea repart ainsi du vrai état courant, pas de la copie périmée.

Leçon : un mirror-sync qui répond 200 ne garantit pas que le contenu est à jour. Toujours vérifier le HEAD réel de la branche, pas le code retour de l’API.

Étape 2 — ArgoCD lit Gitea (en interne)

ArgoCD tourne dans le cluster. Or l’instance Gitea publique est protégée par Cloudflare Access : toute requête non-navigateur vers https://gitea.olig-homelab-2025.win est redirigée vers une page de login Cloudflare. ArgoCD n’est pas un navigateur — il se ferait jeter.

La solution est évidente une fois posée : ArgoCD parle à Gitea via l’URL interne du service Kubernetes, qui ne sort jamais sur Internet et ne croise donc jamais Cloudflare Access :

http://gitea.gitea.svc.cluster.local:3000/<gitea-user>/k3s-homelab.git

On déclare ce dépôt à ArgoCD via un secret de type repository (URL interne + un token Gitea en lecture seule), puis on valide la connexion avec une app jetable en sync manuel : si elle réussit à lire le repo, le chemin est bon — sans toucher aux apps gérées.

La cred en SealedSecret (et son piège)

Un secret créé à la main via kubectl disparaît au prochain rebuild du cluster. Pour la durabilité, on le scelle avec kubeseal et on le committe dans le repo (argocd/bootstrap/).

Piège rencontré : si un Secret du même nom existe déjà et n’est pas géré par le SealedSecret, le controller refuse de l’écraser — Synced=False avec Resource already exists and is not managed by SealedSecret. Il a fallu supprimer le secret manuel pour laisser le controller le recréer proprement depuis le SealedSecret. À refaire : sceller dès le départ, ne pas créer le secret à la main d’abord.

La bascule : repointer le root, laisser cascader

Le cœur de la migration tient en une commande. L’app-of-apps fonctionne en couches : un root app lit argocd/apps/*.yaml, et chaque fichier définit une child app avec son propre repoURL. On a donc :

  1. Édité les 25 repoURL (24 child apps + le root) vers l’URL interne Gitea, committé sur Gitea.
  2. Repointé le root app live vers Gitea (kubectl patch).

Le root, en automated + selfHeal, a lu les définitions mises à jour depuis Gitea et propagé le nouveau repoURL à toutes les child apps dans la foulée :

[1] gitea=25  github=0  healthy=25/25  >>> CONVERGE

Convergence en une douzaine de secondes. Et surtout : zéro pod relancé. La bascule ne change que la source de lecture, pas le contenu des manifests — ArgoCD n’a donc rien réappliqué.

L’ancre de rollback

Tout changement irréversible mérite un filet. Ici, l’ancre est gratuite : on ne pousse pas le changement de repoURL sur GitHub. GitHub conserve l’ancienne version (avec les repoURL GitHub). En cas de pépin, le rollback est immédiat — on repointe le root app vers GitHub, qui sert encore les anciennes définitions, et la cascade revient en arrière.

Le push-mirror Gitea → GitHub de k3s-homelab reste donc désactivé tant qu’on veut préserver cette ancre. Une fois la confiance acquise sur quelques jours, on pourra l’activer pour faire de GitHub un vrai backup vivant.

Bilan

  • 25 apps ArgoCD migrées vers Gitea, Synced + Healthy, sans interruption de service. (24 début juillet 2026 — Homepage a depuis été décommissionné le 2026-06-28.)
  • Source de vérité 100 % self-hosted : ArgoCD lit Gitea interne, on committe sur Gitea, GitHub n’est plus qu’un backup.
  • Un piège majeur évité (miroir stale) grâce à une vérification de connectivité avant la bascule.
  • Un chemin de rollback propre conservé du début à la fin.

La CI suit la même logique — builds et digest-pin passent de GitHub Actions à Gitea Actions, sur un runner hébergé sur le NAS. C’est l’objet de l’article CI self-hosted avec Gitea Actions.

k3s-clustergiteagitopsargocdmigrationk8s