Un artefact est le fichier que la pipeline produit et que les environnements installent : archive, paquet, binaire ou image de conteneur. La règle qui évite la plupart des mauvaises surprises tient en une phrase : on le construit une seule fois, on le nomme par une version et une empreinte SHA-256 (une signature de son contenu), on le stocke dans un registre (le service qui range les artefacts publiés) qui refuse l'écrasement, puis on le retrouve et on le vérifie au lieu de le refaire. Cette leçon le montre sur un petit service Python, avec le registre de paquets d'une forge locale, et mesure ce qui arrive quand on reconstruit quand même.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Distinguer la version d'un artefact (son nom pour les humains) de son empreinte (son identité pour la machine).
- Expliquer pourquoi un registre refuse de republier une version existante.
- Publier l'artefact que la pipeline vient de construire, sur une étiquette de version, avec un jeton dédié.
- Retrouver un artefact publié et vérifier son empreinte avant de l'utiliser.
- Justifier, mesures à l'appui, qu'on ne reconstruit pas un artefact déjà publié.
Prérequis
Section intitulée « Prérequis »- Faire un commit et créer une branche avec Git.
- Lancer une commande dans un terminal et lire son code de sortie.
- Lire un fichier YAML.
- Avoir lu la leçon Étapes et déclencheurs, dont le fil rouge est repris ici.
Aucun compte GitHub ni GitLab n'est nécessaire : le fil rouge tourne sur une forge locale (Forgejo 16.0.6) et son runner (Forgejo Runner 13.2.0) dans une machine virtuelle de lab. La syntaxe des workflows Forgejo est proche de celle de GitHub Actions. Cette leçon n'en enseigne pas le détail : pour la syntaxe, voir les formations GitHub Actions et GitLab CI.
Qu'est-ce qu'un artefact, et comment le nommer ?
Section intitulée « Qu'est-ce qu'un artefact, et comment le nommer ? »Un artefact est le résultat d'une construction (build) : ce que l'on déploie, pas ce que l'on écrit. Selon la technologie, c'est un binaire, une archive, un paquet système ou une image de conteneur. Le fil rouge du lab produit un fichier inventaire-1.0.0.pyz, une application Python empaquetée en un seul fichier exécutable par python3 -m zipapp.
Le fichier Makefile du dépôt porte la cible qui le produit : make build écrit dist/inventaire-<version>.pyz et le fichier .sha256 qui l'accompagne.
Répertoireinventaire/
Répertoire.forgejo/
Répertoireworkflows/
- ci.yml
Répertoiresrc/
Répertoireinventaire/
- __init__.py
- __main__.py
- donnees.py
Répertoiretests/
- test_donnees.py
- Makefile
Répertoiredist/
- inventaire-1.0.0.pyz
- inventaire-1.0.0.pyz.sha256
La version est écrite une seule fois, dans src/inventaire/__init__.py,
et le répertoire dist/ n'existe qu'après make build : il n'est jamais
versionné, puisque le dépôt porte la source et le registre l'artefact.
Deux noms désignent le même fichier, et ils ne servent pas à la même chose.
- La version (
1.0.0) est un nom choisi par une personne. Elle dit quelle livraison on veut, elle se communique à l'oral et se range dans un journal. - L'empreinte (digest) est le résultat de la fonction de hachage SHA-256 appliquée au contenu du fichier : une suite de 64 caractères (chiffres et lettres de
aàf) qui sert de signature du contenu. Elle dépend de chaque octet, donc un seul octet modifié change toute l'empreinte.
La version peut mentir (deux fichiers différents portent le même nom si l'on s'y prend mal). L'empreinte ne ment pas : deux fichiers de même empreinte sont identiques. C'est pourquoi on publie les deux et que l'on compare l'empreinte, pas le nom, quand il faut être sûr.
Cette leçon traite l'artefact sous la forme d'un fichier. Le vocabulaire général (job, runner, cache) est posé dans Qu'est-ce qu'une pipeline CI/CD ?, les gestionnaires de dépôts dans le guide Artefacts, et le cas des images de conteneur, avec leurs propres empreintes, dans Images en CI/CD.
Pourquoi un registre refuse-t-il d'écraser une version ?
Section intitulée « Pourquoi un registre refuse-t-il d'écraser une version ? »Un registre est le service qui range les artefacts publiés, par nom et par version, et les redonne à qui les demande. Forgejo en embarque un, le registre de paquets, avec un type générique qui accepte n'importe quel fichier : c'est celui du lab.
Chaque fichier y a une adresse prévisible, de la forme /api/packages/<propriétaire>/generic/<paquet>/<version>/<fichier>. Pour le fil rouge, la version 1.0.0 vit à l'adresse suivante.
http://localhost:3000/api/packages/apprenant/generic/inventaire/1.0.0/inventaire-1.0.0.pyzLe registre applique la règle d'immuabilité : une version publiée ne se remplace pas par un simple envoi. Dans le lab, republier la même version est refusé, avec le code HTTP 409 (conflit) et le message package file already exists. La documentation de Forgejo précise qu'un même nom de fichier ne se publie pas deux fois dans un paquet, sauf à supprimer d'abord la version : l'immuabilité protège contre l'écrasement accidentel, pas contre une suppression volontaire par une personne habilitée.
Ce refus est voulu. Si l'on pouvait écraser 1.0.0, la version ne désignerait plus un fichier précis : la recette aurait testé un fichier, la production en recevrait un autre, et personne ne saurait lequel tourne. Pour corriger un défaut, on publie une nouvelle version (1.0.1), jamais un remplacement silencieux.
Comment publier depuis la pipeline ?
Section intitulée « Comment publier depuis la pipeline ? »La publication est une étape comme les autres : une cible make publish, appelée par la pipeline, qui envoie au registre le fichier déjà construit et son empreinte. Le fichier de pipeline orchestre, la cible fait le travail, et vous pouvez la lire sans connaître la syntaxe du moteur.
REGISTRE := http://localhost:3000/api/packages/apprenant/generic/inventaire
.PHONY: publishpublish: @test -f $(ARTEFACT) || { echo "Aucun artefact : lancez d'abord make build"; exit 1; } @test -n "$$REGISTRE_JETON" || { echo "REGISTRE_JETON absent de l'environnement"; exit 1; } @test "$${GITHUB_REF_NAME:-v$(VERSION)}" = "v$(VERSION)" || { echo "L'étiquette $$GITHUB_REF_NAME ne correspond pas à la version $(VERSION)"; exit 1; } curl --fail-with-body --silent --show-error --user "apprenant:$$REGISTRE_JETON" \ --upload-file $(ARTEFACT) $(REGISTRE)/$(VERSION)/inventaire-$(VERSION).pyz curl --fail-with-body --silent --show-error --user "apprenant:$$REGISTRE_JETON" \ --upload-file $(ARTEFACT).sha256 $(REGISTRE)/$(VERSION)/inventaire-$(VERSION).pyz.sha256 @echo "Publié : inventaire $(VERSION)"Les trois contrôles du début sont des garde-fous. La cible refuse de publier s'il n'y a aucun artefact (on n'envoie pas un fichier fantôme), si le jeton manque, ou si l'étiquette Git ne correspond pas à la version écrite dans le code : une étiquette v1.0.1 sur un code qui annonce 1.0.0 publierait sous un faux nom.
Le déclencheur est une étiquette de version (un tag Git, un repère nommé posé sur un commit). La pipeline d'intégration ne publie pas à chaque push : elle ne le fait que lorsqu'une étiquette v* est poussée, c'est-à-dire quand une personne décide « cette version existe ». Poser et pousser l'étiquette tient en deux commandes, lancées sur le commit à publier :
git tag v1.0.0git push origin v1.0.0Côté pipeline, deux ajouts suffisent : le déclencheur tags: ['v*'] et une
étape conditionnelle qui publie, placée après la construction.
on: push: branches: [main] tags: ['v*'] pull_request:
jobs: integrer: runs-on: debian steps: - uses: https://data.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: make lint - run: make test - run: make build # Publier ce qui vient d'être construit et testé, seulement pour une # étiquette de version : jamais de seconde construction. - if: startsWith(github.ref, 'refs/tags/v') run: make publish env: REGISTRE_JETON: ${{ secrets.REGISTRE_JETON }}Le persist-credentials: false du checkout, vu dans la leçon précédente, empêche l'action de laisser le jeton Git de la pipeline dans le dépôt cloné. Lisez l'ordre : lint, tests, construction, puis publication. L'artefact publié est donc celui qui vient de passer les tests dans le même job, et l'étape de publication n'appelle pas make build une seconde fois. L'action checkout est épinglée par son SHA de commit, avec la version en commentaire, pour qu'un tag déplacé ne change pas ce que la pipeline exécute.
Quel jeton donner à la pipeline ?
Section intitulée « Quel jeton donner à la pipeline ? »Pour écrire dans le registre, la pipeline s'authentifie. Le réflexe à éviter est d'y mettre le mot de passe d'une personne, ou un jeton qui ouvre tous les droits. Le lab crée un jeton dédié (un mot de passe généré, propre à cet usage), limité à la portée write:package. La portée est la liste des droits que le jeton accorde : celui-ci peut publier des paquets et rien d'autre.
Ce jeton est stocké comme secret du dépôt (une valeur que la forge garde chiffrée et masque dans les journaux) sous le nom REGISTRE_JETON, jamais dans le dépôt. La pipeline le lit par ${{ secrets.REGISTRE_JETON }} et le passe à l'étape sous forme de variable d'environnement : make publish y lit $REGISTRE_JETON. Si le jeton fuit, on le révoque sans toucher à aucun autre droit. Dans Forgejo, les jetons se gèrent dans les réglages du compte (Applications) ou par l'API /api/v1/users/<compte>/tokens, et les secrets de pipeline dans les réglages du dépôt (Actions, Secrets) ou par l'API /api/v1/repos/<compte>/<dépôt>/actions/secrets/<NOM> ; le lab les a créés par ces routes. Le détail de la gestion des secrets en CI se trouve dans Sécuriser les secrets dans vos pipelines CI/CD.
Comment retrouver un artefact et le vérifier ?
Section intitulée « Comment retrouver un artefact et le vérifier ? »Publier n'a de sens que si l'on retrouve ce qu'on a publié. Dans le lab, une fois la pipeline de l'étiquette v1.0.0 verte, l'API de la forge liste les fichiers de la version : inventaire-1.0.0.pyz avec son empreinte SHA-256, et inventaire-1.0.0.pyz.sha256 qui contient la même.
inventaire-1.0.0.pyzsha256 e4dca78a7cd88dc856d018a38fc2e0659c908c29e084dd3ae90ef6d9ea74babaPour s'en servir, on télécharge le fichier, puis on recalcule son empreinte avec sha256sum et on la compare à celle publiée. Un fichier tronqué ou corrompu en route ne passe pas ce contrôle. Il a une limite : l'empreinte attendue vient du même registre que le fichier, donc une version supprimée puis republiée dans le registre passerait. D'où le réflexe de noter l'empreinte d'une version validée et de l'imposer ensuite, que la leçon suivante met en œuvre.
curl -fsS -O http://localhost:3000/api/packages/apprenant/generic/inventaire/1.0.0/inventaire-1.0.0.pyzsha256sum inventaire-1.0.0.pyzDans le lab, l'empreinte du fichier téléchargé est e4dca78a…, identique à celle publiée. L'artefact se lance alors comme n'importe quel programme, ici en lui donnant un nom d'environnement et un port d'écoute, puis on l'interroge depuis un second terminal :
INVENTAIRE_ENV=essai INVENTAIRE_PORT=8099 python3 inventaire-1.0.0.pyz# dans un second terminal :curl -s http://localhost:8099/versionLe service répond {"version": "1.0.0", "environnement": "essai"} : il sait quelle version il est, et dans quel environnement il tourne. Notez que le nom de l'environnement vient de la variable, pas du fichier. La leçon Environnements et promotion s'appuie sur cette séparation pour installer le même fichier en recette puis en production.
Pourquoi ne pas reconstruire l'artefact ?
Section intitulée « Pourquoi ne pas reconstruire l'artefact ? »Reconstruire semble innocent : même source, mêmes commandes, donc même fichier. Le lab montre que non. Le schéma suit la version 1.0.0 : en traits pleins, le chemin normal, construit une fois par la pipeline, stocké et retrouvé avec la même empreinte ; en pointillés, la reconstruction à partir de la même étiquette, qui aboutit à une autre empreinte.
Voici les empreintes mesurées le 2026-10-13, lors de deux passages distincts. Dans le premier lab, une construction locale et une construction de la pipeline sur le même commit ; dans le second lab, la version publiée et une reconstruction locale de cette même version. Les quatre valeurs ne viennent donc pas d'un seul passage et ne se comparent que deux à deux, à l'intérieur de chaque lab.
| Lab | Construction | Empreinte SHA-256 (début) |
|---|---|---|
| Premier lab | Locale (make build) | ae59c425… |
| Premier lab | Pipeline, même commit | 3d87b1d0… |
| Second lab | Publiée par l'étiquette v1.0.0 | e4dca78a… |
| Second lab | Reconstruction locale de v1.0.0 | 2f6ab1b5… |
La reconstruction de v1.0.0 donne 2f6ab1b50b1995972e72eae3946a9a2b37bc62b5be9a0f44f7b68963016449e1, contre e4dca78a… pour la version publiée. Le code est pourtant le même. La cause probable tient au format : un .pyz est une archive zip, et un zip enregistre la date de modification de chaque fichier qu'il contient (ZipInfo.date_time dans le module zipfile de Python). Ces dates sont celles des fichiers sources sur le disque, qui diffèrent entre un dépôt cloné par la pipeline et votre copie locale : octets différents, donc autre empreinte. Le lab n'a pas isolé cette cause octet par octet ; un essai séparé confirme que deux constructions successives des mêmes fichiers donnent la même empreinte, et qu'il suffit de changer la date d'un fichier source pour la changer.
La conséquence compte plus que la cause. Une reconstruction pour la production ne serait pas le fichier qui a passé les tests : même si elle fonctionne, rien ne le prouve, et l'empreinte vérifiée en recette ne correspondrait plus. Voilà le principe build once, deploy many : on construit une fois, et chaque environnement reçoit ce fichier-là. La page Bonnes pratiques CI/CD l'étudie comme invariant, et Les formes de pipelines le compare au motif inverse (reconstruire par environnement).
Il existe des constructions dites reproductibles, où la même source redonne les mêmes octets. Elles demandent un effort particulier (dates normalisées, dépendances figées) et ne remplacent pas la règle ici : tant qu'on ne l'a pas démontré, on stocke le fichier et on le réutilise.
Cache ou artefact : lequel choisir ?
Section intitulée « Cache ou artefact : lequel choisir ? »Le cache accélère et peut disparaître ; l'artefact est le produit, il se nomme, se versionne, s'empreinte et se conserve. La page Qu'est-ce qu'une pipeline CI/CD ? détaille cette opposition. Le test le plus simple : supprimez le fichier. Si la pipeline devient plus lente, c'était un cache ; si elle ne peut plus livrer, c'était un artefact. Dans le fil rouge, dist/inventaire-1.0.0.pyz est un artefact publié dans le registre, alors qu'un cache de paquets Python n'a pas à y figurer.
D'où vient ce binaire ?
Section intitulée « D'où vient ce binaire ? »Une empreinte prouve que le fichier n'a pas changé depuis la publication. Elle ne dit pas qui l'a construit, à partir de quel commit ni dans quel environnement. Quand un fichier arrive d'un registre, la question « d'où vient-il vraiment ? » est celle de la provenance.
Une attestation de provenance est un document signé qui relie l'artefact à son commit et à la pipeline qui l'a produit. Ce lab n'en produit pas : il pose le socle, version, empreinte et registre immuable, sur lequel elle s'appuie. Pour voir comment vérifier une provenance et en faire une décision, lisez De la provenance SLSA à la décision.
Dépannage
Section intitulée « Dépannage »Le refus HTTP 409 est le message du registre, relevé dans le lab. Les trois messages suivants sont ceux écrits par les contrôles de la cible make publish du Makefile ci-dessus. Le tableau part du symptôme.
| Symptôme | Cause | Solution |
|---|---|---|
Publication refusée, HTTP 409, package file already exists | La version existe déjà dans le registre, qui est immuable | Ne pas chercher à écraser : corriger le code, incrémenter la version (1.0.1), poser une nouvelle étiquette |
Aucun artefact : lancez d'abord make build | make publish appelé sans dist/inventaire-<version>.pyz | Lancer make build avant, ou vérifier que l'étape de construction précède la publication dans la pipeline |
REGISTRE_JETON absent de l'environnement | Le secret n'est pas défini sur le dépôt, ou l'étape ne le passe pas par env: | Créer le secret REGISTRE_JETON et vérifier le bloc env: de l'étape de publication |
L'étiquette … ne correspond pas à la version … | L'étiquette Git et la version écrite dans le code divergent | Aligner la version dans __init__.py et l'étiquette, puis repousser une étiquette cohérente |
Aucune publication après un push sur main | La publication n'a lieu que sur une étiquette v*, pas sur une branche | Pousser l'étiquette : git tag v1.0.0 && git push origin v1.0.0 |
| L'empreinte d'une reconstruction diffère de celle publiée | Un .pyz embarque les dates de modification des fichiers sources : deux copies du dépôt donnent en général des octets différents | Ne pas reconstruire : télécharger la version publiée et vérifier son empreinte |
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab vous fait ajouter la cible make publish au dépôt inventaire, étendre la pipeline pour qu'elle publie sur une étiquette v* avec le jeton lu dans REGISTRE_JETON, pousser l'étiquette v1.0.0, puis télécharger la version publiée. La validation porte sur l'état de la forge et de la machine : le Makefile de main doit avoir une cible publish, la pipeline doit se déclencher sur une étiquette v* et recevoir REGISTRE_JETON depuis les secrets, aucun jeton ne doit figurer dans le dépôt, la pipeline de v1.0.0 doit être verte, le registre doit contenir inventaire-1.0.0.pyz et son fichier .sha256, et le fichier téléchargé dans votre répertoire personnel doit avoir la même empreinte que celui du registre. Les commandes que vous tapez ne sont pas évaluées, seul le résultat l'est.
Le lab se lance avec dsoxlab run artefacts, après dsoxlab provision qui prépare la machine virtuelle. L'installation de l'outil est décrite dans DSOXLab.
À retenir
Section intitulée « À retenir »- Un artefact est le fichier produit par la pipeline et installé dans les environnements : on le construit une seule fois.
- La version est un nom pour les humains ; l'empreinte SHA-256 est l'identité du contenu, et seule elle permet de comparer deux fichiers.
- Un registre refuse de republier une version existante (HTTP 409) : pour corriger, on publie une nouvelle version.
- On publie sur une étiquette de version, avec un jeton dédié (
write:package) gardé en secret et passé par l'environnement. - On retrouve l'artefact en le téléchargeant et en recalculant son empreinte avant usage.
- Reconstruire la même version donne une autre empreinte (mesuré :
e4dca78a…publiée contre2f6ab1b5…reconstruite) : ce n'est plus le fichier testé. - Un cache accélère et peut disparaître ; un artefact est le produit et doit se retrouver.
- L'empreinte garantit l'intégrité, pas l'origine : c'est le rôle de la provenance.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Environnements et promotion : de la recette à la production
Continuer le cours : Comprendre OCI : tag, digest, manifest, multi-arch et artefacts
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications