Aller au contenu
English
CI/CD & Automatisation medium

Promouvoir un artefact : construire une fois, déployer deux fois

Read this page in English

30 min de lecture

La règle tient en une phrase : ce qui part en production doit être exactement ce qui a été validé en staging. Cela interdit de reconstruire entre les deux étapes, et impose de désigner l'artefact par son digest plutôt que par un tag. Cette page construit la chaîne de promotion correspondante, avec la vérification de provenance juste avant le déploiement.

  • Identifier l'anti-pattern du rebuild par environnement et ce qu'il casse
  • Promouvoir une image par son digest immuable, de staging à production
  • Transporter un artefact de fichiers entre deux workflows distincts
  • Vérifier l'attestation de l'artefact avant de le déployer
  • Paramétrer un redéploiement manuel sur une version précise

Le pipeline naïf reconstruit à chaque cible, parce que c'est ce que produit l'assemblage successif de workflows :

# ❌ Chaque environnement reconstruit son image
jobs:
deploy-staging:
environment: staging
steps:
- run: docker build -t mon-app:staging . && ./deploy.sh staging
deploy-production:
needs: deploy-staging
environment: production
steps:
- run: docker build -t mon-app:prod . && ./deploy.sh production

Trois garanties tombent d'un coup. D'abord la reproductibilité : les deux builds résolvent leurs dépendances à des instants différents, donc rien ne prouve qu'ils produisent le même binaire. Ensuite la valeur de la validation : les tests de staging ont porté sur une image que plus personne ne possède. Enfin la provenance : l'attestation signée lors de la release ne couvre pas l'image reconstruite, la vérification en production échouera ou, pire, sera retirée du pipeline pour que ça passe.

Un tag de conteneur est mutable : mon-app:1.4.2 peut être republié. Un digest est l'empreinte cryptographique du contenu, de la forme sha256:... : il désigne un et un seul assemblage d'octets.

mon-app:latest -> peut changer à tout moment
mon-app:1.4.2 -> peut être republié sur le même tag
mon-app@sha256:9f2a... -> immuable, c'est l'artefact lui-même

La chaîne de promotion transporte donc un digest, jamais un tag. Le tag reste utile pour les humains, il ne sert jamais de référence d'exécution.

Le job de build publie l'image et exporte son digest comme sortie de job. C'est ce que fait déjà le workflow de release du dépôt de référence, et c'est ce qui rend la suite possible :

jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
packages: write # push vers GHCR
id-token: write # OIDC keyless pour la provenance
attestations: write # attestation native
outputs:
digest: ${{ steps.build.outputs.digest }}
env:
IMAGE: ghcr.io/mon-org/mon-app
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Login GHCR
uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # v4.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build et push
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
push: true
tags: ${{ env.IMAGE }}:${{ github.sha }}
- name: Attestation de provenance
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true

La ligne qui compte est outputs.digest. Sans elle, les jobs suivants n'ont aucun moyen de désigner l'image produite autrement que par un tag, et toute la chaîne retombe sur une référence mutable.

Les deux jobs de déploiement consomment le même digest, et se distinguent uniquement par leur environnement. Le job de production dépend du staging, ce qui garantit l'ordre, et déclenche les règles de protection de son environnement.

deploy-staging:
needs: build
runs-on: ubuntu-24.04
environment:
name: staging
url: https://staging.mon-app.example.com
env:
IMAGE: ghcr.io/mon-org/mon-app
DIGEST: ${{ needs.build.outputs.digest }}
steps:
- name: Vérifier la provenance avant de déployer
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh attestation verify "oci://${IMAGE}@${DIGEST}" --owner mon-org
- name: Déployer sur staging
run: ./deploy.sh "${IMAGE}@${DIGEST}"
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
deploy-production:
needs: [build, deploy-staging]
runs-on: ubuntu-24.04
environment:
name: production # approbation requise sur cet environnement
url: https://mon-app.example.com
env:
IMAGE: ghcr.io/mon-org/mon-app
DIGEST: ${{ needs.build.outputs.digest }}
steps:
- name: Vérifier la provenance avant de déployer
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh attestation verify "oci://${IMAGE}@${DIGEST}" --owner mon-org
- name: Déployer en production
run: ./deploy.sh "${IMAGE}@${DIGEST}"
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Le DEPLOY_TOKEN n'est pas le même dans les deux jobs : chacun lit celui de son environnement. Le workflow, lui, est écrit une seule fois.

Transporter un artefact de fichiers entre workflows

Section intitulée « Transporter un artefact de fichiers entre workflows »

Tout ne se déploie pas sous forme d'image. Pour un site statique, un binaire ou une archive, l'artefact voyage via upload-artifact puis download-artifact. À l'intérieur d'un même workflow, le téléchargement est direct. D'un workflow à l'autre, il faut désigner l'exécution d'origine et fournir un jeton :

- name: Récupérer l'artefact du workflow de build
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

Deux limites à garder en tête. Les artefacts ont une rétention par défaut de 90 jours : au-delà, l'artefact promu n'existe plus, et un rollback tardif devra passer par un rebuild. Et un artefact n'est pas signé par le simple fait d'être stocké : c'est l'attestation produite au build qui le rend vérifiable, pas son emplacement.

La chaîne automatique couvre le cas nominal. Il faut aussi pouvoir choisir la version déployée, pour rejouer une mise en production ou revenir en arrière. Un déclencheur manuel paramétré par le digest suffit :

on:
workflow_dispatch:
inputs:
digest:
description: "Digest de l'image à déployer (sha256:...)"
required: true
type: string
permissions: {}
jobs:
deploy:
runs-on: ubuntu-24.04
environment:
name: production
url: https://mon-app.example.com
permissions:
contents: read
env:
IMAGE: ghcr.io/mon-org/mon-app
DIGEST: ${{ inputs.digest }}
steps:
- name: Vérifier la provenance du digest demandé
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh attestation verify "oci://${IMAGE}@${DIGEST}" --owner mon-org
- name: Déployer
run: ./deploy.sh "${IMAGE}@${DIGEST}"
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

L'entrée est une donnée fournie par un humain, donc non fiable par principe : elle transite par un bloc env: et n'est jamais interpolée directement dans la commande. La vérification d'attestation joue ici un second rôle, elle refuse un digest qui ne proviendrait pas de vos workflows.

  1. Construire une fois et publier l'artefact avec son attestation de provenance.

  2. Exporter le digest comme sortie de job, seule référence transportée ensuite.

  3. Déployer sur staging en vérifiant la provenance juste avant.

  4. Attendre l'approbation portée par les règles de l'environnement de production.

  5. Déployer le même digest en production, après une nouvelle vérification.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

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

  • Reconstruire entre staging et production casse la reproductibilité, annule la valeur des tests et invalide l'attestation de provenance.
  • Le digest sha256:... est la seule adresse immuable d'un artefact ; le tag sert aux humains, jamais à l'exécution.
  • Le job de build exporte son digest en sortie, et tous les jobs de déploiement consomment cette même valeur.
  • Un workflow de déploiement unique sert les deux cibles : c'est l'environment: qui change les secrets lus.
  • Vérifier l'attestation juste avant chaque déploiement, y compris en production, parce qu'une approbation a pu durer longtemps.
  • Les artefacts de fichiers se transportent entre workflows avec run-id et un jeton, et disparaissent après 90 jours par défaut.
  • Le redéploiement manuel se paramètre par le digest, traité comme une entrée non fiable, via env:.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn