Retex — CI/CD sur ce site
Comment j'ai mis en place une CI sur DevIA Notes : la première version qui marche presque, les 5 erreurs rencontrées en route, et le pattern final auquel on a abouti. Plus utile que n'importe quel tuto, parce que le narratif est le cours.
La fiche CI/CD explique ce qu'est un pipeline d'intégration continue, ses concepts, ses outils. Cette fiche-ci raconte une application concrète : la CI qui tourne sur ce site, comment elle est née, et pourquoi elle a la forme qu'elle a. Lis la théorie d'abord si les mots workflow, job, action ne te parlent pas encore.
1. Le contexte
DevIA Notes est un site statique : du HTML/CSS/JS pur,
aucun build (pas de Hugo, pas de Jekyll, pas de bundler). Il est hébergé
sur GitHub Pages, déployé automatiquement à chaque push
sur main. Côté contenu, ~25 fiches qui se citent les unes
les autres via la section « écosystème ».
Le risque principal :
- Un lien interne cassé (typo dans
href="mflow.html", fiche renommée pas mise à jour partout). - Un lien externe qui pourrit avec le temps (doc officielle déplacée, repo GitHub renommé).
Avant la CI, rien ne détectait ça : le déploiement partait en silence, et c'était à moi de cliquer dans le site pour vérifier. Pas viable quand on ajoute des fiches au rythme de plusieurs par jour.
2. Ce qu'on voulait
- Vérifier les liens à chaque push.
- Bloquer le déploiement si un lien est cassé.
- Déployer automatiquement sinon.
- Garder simple : pas de over-engineering pour un projet perso.
Outils choisis : GitHub Actions pour orchestrer, lychee (un crawler de liens écrit en Rust) pour la vérification, actions/deploy-pages pour la publication. Trois actions du marché, zéro code maison.
3. Première version (naïve)
Voici en gros ce que j'ai écrit au départ — un workflow minimal, deux jobs en série :
jobs:
link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: lycheeverse/lychee-action@v2
with:
args: --no-progress --max-retries 2 --exclude-mail './**/*.html'
fail: true
deploy:
needs: link-check
# ... 3 étapes Pages standard
Sur le papier ça paraissait OK. En pratique, ça a planté 5 fois d'affilée avant de stabiliser. Chaque échec a apporté une leçon — les voici dans l'ordre.
4. Les leçons (par ordre d'apparition)
@v2 n'est pas une version, c'est un alias mouvant
Symptôme :
error: unexpected argument '--exclude-mail' found.
Cause : entre lychee 0.22 et 0.23, le flag
--exclude-mail a été supprimé (les mailto:
sont désormais exclus par défaut). L'action
lycheeverse/lychee-action@v2 télécharge la dernière
version compatible v2 du binaire — donc la 0.23 est arrivée
sans prévenir et a cassé mon flag.
Décision : retirer le flag (c'est devenu le
défaut). Pour un projet perso, on accepte cette dérive et on
patche quand ça casse. En prod, on épinglerait au SHA du
commit (@a1b2c3d) pour figer le comportement
exactement.
localhost dans les fiches sont des faux positifs structurels
Symptôme :
http://localhost:8000/ — Connection refused sur
docker.html et fastapi.html.
Cause : les fiches contiennent des
<a href="http://localhost:8000/docs"> à but
pédagogique. Lychee tente de les contacter ; rien ne tourne sur le
runner GitHub.
Décision : ajouter le flag
--exclude-loopback (couvre localhost,
127.0.0.1, ::1, tous ports). Une règle
générique vaut mieux qu'une liste d'URLs : voir Leçon 5.
Symptômes (à 4 reprises) :
https://try.redis.io— Connection failedhttps://helm.sh— Connection reset by peerhttps://demo.uptime.kuma.pet— Timeouthttps://streamlit.io— Connection failed
Cause : Cloudflare, Fastly et consorts filtrent les plages d'IPs cloud (les runners GitHub partagent leurs IPs avec des milliers d'autres acteurs, dont du trafic abusif). Les liens sont valides — ils marchent dans n'importe quel navigateur.
Constat : ce n'est pas mon problème, je ne peux rien y faire, et ça va se reproduire à chaque nouveau lien externe vers une grosse marque tech.
Le déclic : tant qu'un seul job vérifie tout, il échoue pour des raisons légitimes et pour des raisons hors de mon contrôle, sans distinction. Le déploiement est bloqué pour des liens qui ne sont pas cassés. C'est inacceptable.
Solution : couper en deux jobs.
- Liens internes (lychee
--offline) — sous mon contrôle. Une erreur ici est un VRAI bug. → bloque le déploiement. - Liens externes (
continue-on-error: true) — dépend du monde. → ne bloque rien, juste informatif.
C'est le pattern fail-loud / fail-soft : on hurle fort sur ce qu'on contrôle, on chuchote sur ce qu'on ne contrôle pas. Universel, pas spécifique à lychee.
Pour les localhost, deux approches étaient possibles :
- Lister chaque URL dans
.lycheeignore:^http://localhost:8000/$,^http://localhost:8000/docs$… À chaque nouvelle fiche citantlocalhost, ajouter une ligne. Pénible et non-exhaustif. - Exprimer une règle :
--exclude-loopback. Un flag, toutes les variantes (n'importe quel port, n'importe quel chemin), couverture automatique des cas futurs.
À chaque fois que tu peux caractériser une famille de faux positifs par une règle, fais-le. La liste reste utile pour les cas vraiment particuliers (par ex. une URL publique précise qui flanche depuis le CI).
Le job externe est best-effort : il peut être rouge sans bloquer le déploiement. Tentation : ne plus s'en occuper du tout.
Mauvaise idée — sans entretien, il sera rouge tout le temps, et donc personne ne lira jamais le rapport. Du coup, le jour où un vrai lien meurt (doc officielle déplacée, repo renommé), ça passe inaperçu. C'est le berger qui crie au loup : trop d'alertes → on ignore les vraies.
D'où .lycheeignore : on y met les URLs durablement
flaky pour que le job externe soit vert la plupart du
temps. Il devient un signal utile : rouge = quelque chose
à regarder.
5 commits successifs juste pour stabiliser le link-check. Sur le moment ça frustre ; avec du recul, c'est normal. Un pipeline n'est pas une config qu'on écrit une fois et qu'on oublie : il vit avec le projet. Chaque nouvelle dépendance, chaque nouvelle plateforme apporte ses faux positifs. L'objectif n'est pas d'écrire le bon workflow du premier coup, c'est de le faire évoluer vite quand il déconne.
5. Le résultat final
Le workflow stabilisé tient en trois jobs :
┌──────────────────────────┐
│ link-check-internal │ STRICT (offline, zéro réseau)
│ → liens fiche→fiche, CSS │
└────────────┬─────────────┘
│ bloque si rouge
▼
┌──────────────────────────┐
│ deploy │ Publie sur GitHub Pages
│ → uniquement push/main │
└──────────────────────────┘
┌──────────────────────────┐
│ link-check-external │ INFORMATIF (best-effort)
│ → URLs externes │ continue-on-error: true
└──────────────────────────┘ N'INFLUE PAS sur le déploiement
Deux fichiers sont entièrement commentés à fin pédagogique dans le repo (lis-les en parallèle de cette fiche, c'est l'intérêt) :
.github/workflows/ci.yml— le workflow lui-même. Chaque section a son commentaire expliquant le pourquoi plutôt que le quoi..lycheeignore— la liste des URLs ignorées, avec l'explication du format regex et la règle de décision (« quand ajouter une URL ici, quand corriger la fiche à la place »).
Tu peux les ouvrir sur GitHub : ci.yml et .lycheeignore.
Liens vers les fiches concernées
- CI/CD — la théorie générale (concepts, outils, vocabulaire). À lire avant ce retex si tu débutes.
- GitHub — l'hébergement et les Actions.
- Mettre un projet sur GitHub — l'étape précédente : avant la CI, il faut un repo et un déploiement Pages qui marche.