Maximize Page
Tech & DevOps HubEspace Tech & DevOps: Explorez le monde du Dev, du Cloud et des outils DevOps à travers nos articles et discussions Explore the world of development, the cloud and DevOps tools

Automatisation complète de la PR-watch-Distroless

Date de l'article:20-05-2026
Docker CI/CD Github-Actions UPDATE
Détection automatique des nouveaux digests Distroless et mise à jour sécurisée du Dockerfile via un workflow PR-watch entièrement automatisé. Chaque nouvelle image est ensuite validée dans le pipeline CI/CD avant promotion vers la production.

Cet article documente l'automatisation complète du workflow PR-watch-distroless — passage d'un simple observateur à un véritable point d'entrée de la chaîne CI/CD sécurité. Il fait suite à la résolution d'un incident DevSecOps lié à Trivy, mais peut se lire indépendamment.

Contexte Précédent
Voir le détail de l'analyse et son fix
Des vulnérabilités ont été détectées dans l'image Docker (distroless). Cette situation à été fixée, temporairement à l'aide de:
  • Ajout d'un script de détection, comparant le digest utilisé en CI, avec un tag "latest"
  • Ajout d'un workflow pr-watch-distroless pour vérifier les mises à jour de l'image
    • Condition: Envoye une PR vers[ main] si le digest change.
    • Un Cron est défini pour vérifier le digest tous les jours
    • Test de l'image dans le workflow Staging
  • Ajout du fichier .trivyignore pour ignorer les vulnérabilités présentes
  • Ajout des étapes nécessaires dans les workflows [staging] et [main]
Implication & contrainte de ce système:

PR Watch = Rôle d'observateur

Le workflow "cd-prod" devait :
détecter, décider, scanner différemment et mettre à jour , ce qui impliquait une répétition des étapes de scan et le Changement manuel du Dockerfile
Contexte "entre-deux" et Automatisation
2 Trop de vulnérabilités détectées dans l'image Debian:12
      J'ai donc pris la décision de:
  • Changer l'image de "Debian12" vers un "debian13:nonroot" a entraîné:
    • Le changement de l'image dans les scripts
    • La création du nouveau fichier .digest basé sur la nouvelle image
    • Conséquences directes:
      • Plus d'alertes dans le scan Trivy = Nettoyage des vulnérabilités dans le fichier .trivygnore
Avec l'automation du workflow, "PR-watch-distroless" deviendra un point d'entrée de la chaîne de validation sécurité en cas de nouveau digest detecté

1. Externalisation du script d'installation de Crane

Dans l'étape précédente: la PR-watch appelle Crane, et Crane était installé directement dans les workflows

Pour l'automatiser, donc; ne plus l'installer manuellement dans tous les jobs, ce qui peut-être contraignant en cas de mise à jour. Nous allons donc externaliser le script et, comme nous avons une version déjà, nous pouvons donc la fixer
ci-scripts/install-crane.sh
#!/usr/bin/env bash
set -euo pipefail

CRANE_VERSION="v0.21.5"
echo "[INFO] Installing crane ${CRANE_VERSION}..."

curl -fsSL \
  "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_x86_64.tar.gz" \
  | tar -xz crane

sudo mv crane /usr/local/bin/
echo "[INFO] crane installed: "
crane version
Ajout du script dans la [pr-watch-distroless]
- name: Install crane
  id: install
  run: |
    ci-scripts/install-crane.sh
Suppression de l'étape dans les workflows
Les précédentes étapes d'installation de Crane peuvent être supprimées définitivement des branches [staging] et [main]
Install crane

2. Modification de l'image Docker
1. Changement de la version de l'image
Modifier la ligne dans le Dockerfile:
FROM gcr.io/distroless/java17-debian13:nonroot Dans un premier temps, pour valider qu'elle passe bien le flow et qu'il n'y a plus de vulnérabilités dans l'image Docker.
2. Création/suppression du fichier .digest
→ Suppression de l'ancien qui ne nous sert plus (debian12)
→ Ajout du nouveau fichier ".distroless-java17-debian13.digest", adapté a la nouvelle version de l'image

Résultat de la modification de l'image [staging]
Report Summary
┌──────────────────────────────────┬────────┬─────────────────┬─────────┐
│              Target              │  Type  │ Vulnerabilities │ Secrets │
├──────────────────────────────────┼────────┼─────────────────┼─────────┤
│ flashcards:staging (debian 13.5) │ debian │        2        │    -    │
├──────────────────────────────────┼────────┼─────────────────┼─────────┤
│ app/app.jar                      │  jar   │        0        │    -    │
└──────────────────────────────────┴────────┴─────────────────┴─────────┘

flashcards:staging (debian 13.5)
================================
Total: 2 (HIGH: 2, CRITICAL: 0)

┌───────────┬────────────────┬──────────┬──────────┬───────────────────┬───────────────┐
│  Library  │ Vulnerability  │ Severity │  Status  │ Installed Version │ Fixed Version │
├───────────┼────────────────┼──────────┼──────────┼───────────────────┼───────────────┤
│ libexpat1 │ CVE-2026-25210 │ HIGH     │ affected │ 2.7.1-2           │               │
│           │                │          │          │                   │               │
│           ├────────────────┤          │          │                   ├───────────────┤
│           │ CVE-2026-45186 │          │          │                   │               │
│           │                │          │          │                   │               │
└───────────┴────────────────┴──────────┴──────────┴───────────────────┴───────────────┘
2 vulnérabilités ont été trouvée, mais pas de fix de disponible.

Contrairement à l'image "debian12" où il indiquait qu'un correctif existait, mais qu'il n'était pas encore intégré dans l'image analysée.

En passant à Debian 13,
on bénéficie d'un nettoyage massif des anciennes vulnérabilités grâce à la mise à jour globale des bibliothèques système de l'image
Dans la CI, le comportement diffère volontairement entre les branches:
  • [staging] exécute un scan strict affichant toutes les vulnérabilités HIGH/CRITICAL, même sans correctif disponible.
  • [main] utilise --ignore-unfixed, ce qui ignore les vulnérabilités sans patch officiel disponible afin d'éviter de bloquer inutilement la release.
À partir de là, Docker pull la dernière image du tag, indépendamment du digest détecté.
Donc, quand le Docker build, il n'utilise pas encore le digest. Pour ce faire, il nous faut "figer" le digest dans le Dockerfile.

3. Pin de l'image Docker

Pour une sécurité maximale et des builds 100% reproductibles, l'idéal est donc, de figer l'image par son empreinte unique (digest SHA256) au lieu du simple tag textuel.
Dès que Google publierait une nouvelle image Debian 13, et que la PR-watch l'aura detectée, le .digest et le Dockerfile seront mis à jour et prêt à être testés dans le CI

3. Modifier l'image dans le Dockerfile:
Remplacer: "FROM gcr.io/distroless/java17-debian13:nonroot", par:
FROM gcr.io/distroless/java17-debian13:nonroot@sha256:81d09cac6ec47f6a13c61a941557f95079213320f3ddbf9d353de9317669aab5
Le SHA à été obtenu de la même manière décrite que dans l'article précédent (Docker), le script utilise également cette approche.
On peux aussi voir les tags/Sha, depuis la registry Google

3. Automatisation de la "pr-watch-distroless" pour gérer le digest
EVENT La PR à détecté un nouveau Digest

C'est le moment de tester réellement le système et l'automatiser de manière à ce qu'il suive la stratégie gitlab Flow sur 3 branches

Résultat du PR-watch-distroless
 Run ci-scripts/check-docker-image-latest.sh
[INFO] Checking crane availability...
[INFO] Fetching current digest...
[INFO] Current:  sha256:81d09cac6ec47f6a13c61a941557f95079213320f3ddbf9d353de9317669aab5
[INFO] Previous: sha256:b0e67f7fa5649297e655e37b2cd67471d7d38c2f8617790e9d8e8eab78ed6bcb
[INFO] Update detected
La stratégie
"pr-watch-distroless" detecte un nouveau digest,
  1. Il modifie le Dockerfile avec le nouveau SHA
  2. Il modifie le .digest avec le nouveau SHA
  3. il envoie une PR à la branche [develop]. À partir là, la CI suit le flux normal

1. Modification du fichier "ci-scripts/check-docker-image-latest.sh"
ci-scripts/check-docker-image-latest Dans la première version, le système surveillait simplement l'évolution du tag distroless afin de détecter les rebuilds publiés upstream.
Désormais, le workflow surveille explicitement l'image java17-debian13:nonroot et pin automatiquement le digest SHA256 correspondant
IMAGE="${1:-gcr.io/distroless/java17-debian13:nonroot}"
STATE_FILE="${2:-ci-scripts/.distroless-java17-debian13.digest}"
Ces variables de script sont transmis depuis la pr-watch-distroless.yml, en tant qu'arguments.
Dans le script; la variable $IMAGE est utilisée pour obtenir le CURRENT_DIGEST=$(crane digest "$IMAGE") et le comparer ensuite au digest déjà stocké dans le fichier .digest (STATE_FILE)

2. Modification de la ".github/workflows/pr-watch-distroless.yml" (extrait)

Voir le script en entier dans le repository Github

Dans cette nouvelle version, nous allons changer toute la logique et attribuer + de responsabilité à ce workflow:
1. Pour respecter notre flow Git, au lieu d'appeller --base main \ On appellera: --base develop \
2. Nous installons Crane pour rendre le workflow indépendant (CRON):
 name: Install crane
  id: install
  run: |
    ci-scripts/install-crane.sh
3. Au lieu de laisser [main] mettre à jour le .digest nous le ferons directement depuis le script de détection
4. Comme il à changé le digest, et pour nous permettre de tester la nouvelle image dans staging, il mettra à jour le tag pinné dans le Dockerfile
BRANCH="chore/distroless-update-$(date +%s)"
  git checkout -b $BRANCH
  DIGEST="${{ steps.check.outputs.digest_value }}"

  echo "$DIGEST" > ci-scripts/.distroless-java17-debian13.digest
  sed -i "s|FROM gcr.io/distroless/java17-debian13:nonroot.*|FROM gcr.io/distroless/java17-debian13:nonroot@$DIGEST|" Dockerfile
  git add \
    ci-scripts/.distroless-java17-debian13.digest \
    Dockerfile
5. Nous en profitons pour sécuriser la branche:
Au lieu d'appeller --search "distroless", on appelle --search "in:title chore: distroless image updated" \ qui corresponds au titre de la pr
 gh pr create \
  --base develop \
  --head "$BRANCH" \
  --title "chore: distroless image updated" \ 
6. Et le commit aussi:
 git diff --cached --quiet && {
    echo "No changes detected"
    echo "skip_pr=true" >> $GITHUB_OUTPUT
    exit 0
  }
← Évite une confusion éventuelle avec d'autres branche qui utiliserait ce même nom (distroless)

Modification dans les worflows
On avait déja nettoyé l'installation de Crane, maintenant nous supprimons les conditions qui ne sont plus nécessaires car le digest est automatique ajouté.
Quand le build Docker s'effectue, il prends l'image du Dockerfile directement. Plus besoin d'effectuer d'autres installations ou vérifications dans les workflows.
1. Modification du workflow "ci-staging"

Le digest étant désormais géré en amont par le workflow PR-watch-distroless, les workflows staging et production n'ont plus besoin de détecter dynamiquement les changements d'image. Ils se concentrent uniquement sur la validation et l'exécution des contrôles CI/CD.

Suppression des étapes devenues inutiles:
Check distroless digest
Debug digest
Supprimer ou remplacer également cette étape:
- name: Scan Docker image (Trivy - conditional)
  run: |
    if [ "${{ steps.digest.outputs.digest_changed }}" = "true" ]; then
      echo "[INFO] New distroless → strict scan"
      //..
    else
     //..
    fi
par:
- name: Trivy Scan Docker Image (strict)
  run: |
    echo "[INFO] New distroless → strict scan"
    trivy image flashcards:staging \
      --severity HIGH,CRITICAL \
      --ignorefile /dev/null \
      --exit-code 0 \
      --format table
On allège et simplifie les workflows
Le scan devient strict, sans ignore, à chaque build
[staging] devient un vrai environnement de validation de la sécurité, avant la promotion vers [main]

2. Modification du workflow "cd-prod"

Parce que les responsabilités ont été déplacées en amont vers le workflow "pr-watch-distroless" afin que les nouvelles images soient validées dans le flux Git normal, Le workflow n'à plus besoin de vérifier le digest, ni de le mettre jour

Suppression des étapes devenues inutiles
Check distroless digest
Debug FULL outputs
Update digest file
On supprime ou remplace cette étape:
- name: Trivy Scan Docker Image (new distroless)
  if: steps.digest.outputs.digest_changed == 'true'
  run: |
    trivy image $IMAGE_NAME:$IMAGE_TAG \
      --ignorefile /dev/null \
      //..

- name: Trivy Scan Docker Image (stable distroless)
  if: steps.digest.outputs.digest_changed == 'false'
  run: |
      //...
      --ignorefile .trivyignore.yaml \
      //...
Pour la remplacer par une simple étape:

- name: Trivy Scan Docker Image
  run: |
    trivy image $IMAGE_NAME:$IMAGE_TAG \
      --severity HIGH,CRITICAL \
      --exit-code 1 \
      --ignore-unfixed \
      --ignorefile .trivyignore.yaml \
      --timeout 10m \
      --cache-dir ~/.cache/trivy \
      --format table \
      --no-progress
    
Le fichier .trivyignore.yaml reste conservé dans le pipeline afin de permettre de futures exceptions contrôlées si nécessaire, même si les vulnérabilités historiquement ignorées ont désormais été nettoyées.
À présent, la responsabilité de la décision sécurité a été déplacée en amont du pipeline de production. Donc:
PR Watch → détecte  develop/staging → valide main/prod → release

Résultat et conclusion

Cette évolution marque une montée en maturité du pipeline CI/CD et de la stratégie DevSecOps du projet.
Le workflow "pr-watch-distroless" ne joue désormais plus uniquement un rôle d'observateur, mais devient un véritable point d'entrée de la chaîne de validation sécurité.

L'ensemble permet :
  • des builds reproductibles,
  • une meilleure maîtrise de la supply chain Docker,
  • une réduction des étapes conditionnelles dans les workflows,
  • et une séparation plus claire entre détection, validation et release.

Le workflow "ci-staging" sert désormais de validation runtime/intégration.
Job "functional-tests":
Build flashcards:staging → Trivy Scan Docker Image (strict) → healthcheck

Le workflow "cd-prod" re-devient enfin ce qu'un pipeline de production doit être:
build → tests → scan → smoke test → release → push registry
Distroless Registry
        │
        ▼
PR-watch-distroless
        │
        ├── update Dockerfile
        ├── update .digest
        └── create PR → develop
                        │
                        ▼
                staging validation
                        │
                        ▼
                   production

Ce pattern — un workflow dédié à la détection, qui alimente un flux Git standard — est réutilisable dans tout projet nécessitant une surveillance d'images Docker upstream, quelle que soit la registry utilisée.

Naviguez entre les articlesBrowse through the articles

Laissez-moi un commentaire

En postant un commentaire anonyme, vous adhérez automatiquement aux conditions d'utilisation du site.