Partie 9: Mise en place du docker-build automatique

Partie 9: Mise en place du docker-build automatique

Introduction

Après avoir mis en place les tests automatisés et le linting dans la Phase 1, nous passons maintenant à l'automatisation du build et du déploiement des images Docker. Cette phase transforme chaque commit en une image Docker prête à l'emploi, disponible sur Docker Hub.

Contexte et Objectifs

Situation Initiale

Dans la Phase 1, nous avions :

  • Tests automatisés avec pytest
  • Linting avec flake8
  • Workflow CI vérifiant la qualité du code

Mais le processus de création et publication des images Docker restait manuel :

# Commandes manuelles à taper à chaque fois
docker buildx build \
    --platform linux/arm64 \
    -t nocoblas/home-fonta-web:v4 \
    --push \
    .

Problèmes de cette approche :

  • Chronophage : il faut taper les commandes à chaque fois
  • Risque d'oubli : pas de garantie qu'une image soit créée pour chaque version
  • Pas de traçabilité : difficile de savoir quelle image correspond à quel commit
  • Pas de sécurité : aucun scan automatique de vulnérabilités
  • Une seule architecture : il faut rebuilder pour amd64 et arm64 séparément

Objectifs de la Phase 2

  1. Automatiser le build Docker : Chaque push sur master déclenche automatiquement la création d'une image
  2. Multi-architecture : Builder simultanément pour amd64 (serveurs classiques) et arm64 (Raspberry Pi)
  3. Push automatique vers Docker Hub : L'image est publiée automatiquement
  4. Tagging intelligent : Plusieurs tags automatiques pour faciliter la traçabilité
  5. Scan de sécurité : Analyse automatique des vulnérabilités avec Trivy
  6. Intégration GitHub Security : Résultats des scans visibles dans l'onglet Security de GitHub

Architecture CI/CD Phase 2

Push sur master
    │
    ▼
GitHub Actions CI
    │
    ├─────────────────────────────────────┐
    │                                     │
    ▼                                     ▼
Phase 1: Tests & Linting          Phase 2: Docker Build
    │                                     │
    ├── Setup Python 3.12                 ├── Setup QEMU (émulation ARM)
    ├── Install dependencies              ├── Setup Docker Buildx
    ├── Run flake8                        ├── Login to Docker Hub
    ├── Run pytest (6 tests)              ├── Extract metadata (tags)
    │                                     ├── Build multi-arch image
    │                                     │   ├── linux/amd64
    │                                     │   └── linux/arm64
     Tests PASS                         ├── Push to Docker Hub
    │                                     ├── Scan with Trivy
    │                                     └── Upload to GitHub Security
    │                                     │
    └─────────────────┬───────────────────┘
                      │
                      ▼
            Image disponible sur Docker Hub
                      │
            ┌─────────┼─────────┐
            ▼         ▼         ▼
        latest   sha-xxxxx   master

Implémentation Détaillée

Étape 1 : Analyse du Dockerfile Existant

Notre Dockerfile utilise un build multi-stage optimisé :

# Stage 1: Builder Python
FROM python:3.12-slim AS python-builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
COPY templates/ templates/

# Stage 2: Production avec NGINX + Supervisor
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
    nginx \
    supervisor \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY --from=python-builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=python-builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
COPY --from=python-builder /app /app
COPY static/ /app/static/
COPY nginx/default.conf /etc/nginx/sites-enabled/default

# Configuration Supervisor pour NGINX + Gunicorn
RUN echo '[supervisord]' > /etc/supervisor/conf.d/app.conf && \
    echo 'nodaemon=true' >> /etc/supervisor/conf.d/app.conf && \
    # ... configuration NGINX et Gunicorn

EXPOSE 80
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

Points clés du Dockerfile :

  • Multi-stage : Réduit la taille de l'image finale
  • NGINX + Gunicorn + Supervisor : Stack production complète
  • Python 3.12 : Version récente et performante
  • Optimisé : Pas de dépendances inutiles

Étape 2 : Création du Workflow GitHub Actions

Fichier .github/workflows/docker-build.yml :

name: Docker Build & Push

on:
  push:
    branches: [main, master]
    tags:
      - 'v*.*.*'
  pull_request:
    branches: [main, master]

env:
  DOCKER_IMAGE: nocoblas/home-fonta-web
  PLATFORMS: linux/amd64,linux/arm64

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: arm64

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Docker Hub
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.DOCKER_IMAGE }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix=sha-
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: ${{ env.PLATFORMS }}
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.DOCKER_IMAGE }}:latest
          format: 'sarif'
          output: 'trivy-results.sarif'
        if: github.event_name != 'pull_request'
        continue-on-error: true

      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: 'trivy-results.sarif'
        if: github.event_name != 'pull_request'
        continue-on-error: true

      - name: Run Trivy vulnerability scanner (table output)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.DOCKER_IMAGE }}:latest
          format: 'table'
          exit-code: '0'
          severity: 'CRITICAL,HIGH'
        if: github.event_name != 'pull_request'

      - name: Image digest
        if: github.event_name != 'pull_request'
        run: |
          echo "✅ Image pushed successfully!"
          echo "📦 Tags: ${{ steps.meta.outputs.tags }}"
          echo ""
          echo "Pull image with:"
          echo "  docker pull ${{ env.DOCKER_IMAGE }}:latest"
          echo "  docker pull ${{ env.DOCKER_IMAGE }}:sha-${{ github.sha }}"

Étape 3 : Explication Détaillée du Workflow

A. Déclencheurs (on)

on:
  push:
    branches: [main, master]
    tags:
      - 'v*.*.*'
  pull_request:
    branches: [main, master]

Le workflow se déclenche dans 3 cas :

  1. Push sur master : Build et push de l'image
  2. Tags Git (ex: v1.0.0) : Crée une version taguée
  3. Pull Request : Build uniquement (pas de push) pour validation

B. Variables d'environnement

env:
  DOCKER_IMAGE: nocoblas/home-fonta-web
  PLATFORMS: linux/amd64,linux/arm64
  • DOCKER_IMAGE : Nom de l'image sur Docker Hub
  • PLATFORMS : Architectures cibles (serveurs x86 + Raspberry Pi ARM)

C. Permissions

permissions:
  contents: read
  packages: write
  security-events: write

Pourquoi ces permissions ?

  • contents: read : Lire le code du repository
  • packages: write : Écrire les packages Docker
  • security-events: write : Uploader les résultats Trivy vers GitHub Security

Important : Sans security-events: write, l'upload Trivy échouera avec :

Resource not accessible by integration

D. Setup QEMU (Émulation ARM)

- name: Set up QEMU
  uses: docker/setup-qemu-action@v3
  with:
    platforms: arm64

QEMU permet de builder des images ARM64 sur une machine AMD64. C'est essentiel pour supporter les Raspberry Pi.

E. Setup Docker Buildx

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

Buildx est l'outil Docker pour les builds multi-architecture et avancés. Il permet :

  • Builds parallèles multi-plateformes
  • Cache avancé
  • Export vers registry

F. Login Docker Hub

- name: Log in to Docker Hub
  if: github.event_name != 'pull_request'
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKER_USERNAME }}
    password: ${{ secrets.DOCKER_PASSWORD }}

Condition importante : if: github.event_name != 'pull_request'

  • Sur PR : pas de login (pas de push)
  • Sur push master : login pour pouvoir pusher

Secrets à configurer sur GitHub :

  1. DOCKER_USERNAME : Votre username Docker Hub (ex: nocoblas)
  2. DOCKER_PASSWORD : Access Token Docker Hub (pas le mot de passe direct)

Comment créer l'Access Token :

  1. hub.docker.com/settings/security
  2. New Access Token
  3. Description : GitHub Actions home-fonta
  4. Permissions : Read & Write
  5. Copier le token et l'ajouter dans GitHub Secrets

G. Extract Metadata (Tags intelligents)

- name: Extract metadata (tags, labels)
  id: meta
  uses: docker/metadata-action@v5
  with:
    images: ${{ env.DOCKER_IMAGE }}
    tags: |
      type=ref,event=branch
      type=ref,event=pr
      type=semver,pattern={{version}}
      type=semver,pattern={{major}}.{{minor}}
      type=sha,prefix=sha-
      type=raw,value=latest,enable={{is_default_branch}}

Tags créés automatiquement :

Événement Tags créés Exemple
Push sur master latest, master, sha-xxx latest, master, sha-6c88512
Tag Git v1.2.3 v1.2.3, v1.2, v1, latest v1.2.3, v1.2, v1
Pull Request #42 pr-42 pr-42

Avantages :

  • latest : Toujours la dernière version stable
  • sha-xxx : Traçabilité exacte du commit
  • master : Version de la branche master
  • Versions sémantiques : Pour les releases (v1.0.0)

H. Build and Push

- name: Build and push Docker image
  uses: docker/build-push-action@v5
  with:
    context: .
    platforms: ${{ env.PLATFORMS }}
    push: ${{ github.event_name != 'pull_request' }}
    tags: ${{ steps.meta.outputs.tags }}
    labels: ${{ steps.meta.outputs.labels }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

Paramètres clés :

  • platforms: linux/amd64,linux/arm64 : Build pour 2 architectures en parallèle
  • push: ${{ github.event_name != 'pull_request' }} : Push seulement si pas une PR
  • cache-from/cache-to: type=gha : Utilise le cache GitHub Actions

Cache GitHub Actions :

  • Premier build : ~5-10 minutes
  • Builds suivants : ~1-2 minutes (grâce au cache)

I. Trivy Security Scan

- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.DOCKER_IMAGE }}:latest
    format: 'sarif'
    output: 'trivy-results.sarif'
  if: github.event_name != 'pull_request'
  continue-on-error: true

Trivy scanne l'image pour détecter :

  • Vulnérabilités CVE dans les packages OS
  • Vulnérabilités dans les dépendances Python
  • Secrets exposés
  • Misconfigurations

Format SARIF : Format standard pour les outils de sécurité, compatible GitHub Security.

Important : continue-on-error: true permet au workflow de continuer même si Trivy trouve des vulnérabilités. C'est un choix pour ne pas bloquer le déploiement, mais on peut mettre false pour bloquer si vulnérabilités critiques.

J. Upload vers GitHub Security

- name: Upload Trivy results to GitHub Security
  uses: github/codeql-action/upload-sarif@v4
  with:
    sarif_file: 'trivy-results.sarif'
  if: github.event_name != 'pull_request'
  continue-on-error: true

Upload les résultats vers l'onglet SecurityCode scanning alerts de GitHub.

Avantages :

  • Visualisation des vulnérabilités dans l'interface GitHub
  • Tracking des vulnérabilités au fil du temps
  • Alertes automatiques si nouvelle vulnérabilité

K. Trivy Table Output

- name: Run Trivy vulnerability scanner (table output)
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.DOCKER_IMAGE }}:latest
    format: 'table'
    exit-code: '0'
    severity: 'CRITICAL,HIGH'
  if: github.event_name != 'pull_request'

Affiche un tableau lisible dans les logs GitHub Actions avec seulement les vulnérabilités CRITICAL et HIGH.

Exemple de sortie :

Total: 5 (CRITICAL: 2, HIGH: 3)

┌─────────────┬────────────────┬──────────┬────────┬───────────────────┐
│   Library   │ Vulnerability  │ Severity │ Status │  Installed Ver.   │
├─────────────┼────────────────┼──────────┼────────┼───────────────────┤
│ openssl     │ CVE-2024-1234  │ CRITICAL │  fixed │ 1.1.1-1ubuntu2.1  │
│ python3.12  │ CVE-2024-5678  │ HIGH     │  fixed │ 3.12.0-1          │
└─────────────┴────────────────┴──────────┴────────┴───────────────────┘

Étape 4 : Mise à Jour du README avec Badges

# Home-Fonta.fr - Documentation complète

![CI Status](https://github.com/Nikob2o/home-fonta/actions/workflows/ci.yml/badge.svg)
![Docker Build](https://github.com/Nikob2o/home-fonta/actions/workflows/docker-build.yml/badge.svg)
[![Docker Hub](https://img.shields.io/docker/v/nocoblas/home-fonta-web?label=Docker%20Hub&logo=docker)](https://hub.docker.com/r/nocoblas/home-fonta-web)

3 badges ajoutés :

  1. CI Status : Statut des tests et linting (Phase 1)
  2. Docker Build : Statut du build Docker (Phase 2)
  3. Docker Hub : Dernière version disponible sur Docker Hub

Erreurs Rencontrées et Résolutions

Erreur 1 : Trivy ne trouve pas l'image avec tag SHA

Message d'erreur :

FATAL: unable to find the specified image "nocoblas/home-fonta-web:sha-6c885128..."
Error: Process completed with exit code 1.

Cause : Trivy essayait de scanner l'image avec le tag sha-xxx avant qu'elle ne soit poussée sur Docker Hub.

Solution : Changer Trivy pour scanner le tag latest qui est déjà disponible :

# Avant (ne marche pas)
image-ref: ${{ env.DOCKER_IMAGE }}:sha-${{ github.sha }}

# Après (fonctionne)
image-ref: ${{ env.DOCKER_IMAGE }}:latest

Erreur 2 : Permission denied pour GitHub Security

Message d'erreur :

Resource not accessible by integration
This run does not have permission to access CodeQL Action API endpoints.
Please ensure the workflow has at least the 'security-events: write' permission.

Cause : Le workflow n'avait pas les permissions nécessaires pour uploader vers GitHub Security.

Solution : Ajouter les permissions au job :

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write  # ← Permission manquante

Erreur 3 : Warning CodeQL v3 deprecated

Message d'avertissement :

CodeQL Action v3 will be deprecated in December 2026.
Please update to v4.

Cause : Utilisation de la version v3 de CodeQL Action.

Solution : Mettre à jour vers v4 :

# Avant
uses: github/codeql-action/upload-sarif@v3

# Après
uses: github/codeql-action/upload-sarif@v4

Résultats et Bénéfices

Avant Phase 2

  • Build Docker manuel à chaque fois
  • Une seule architecture (ARM64)
  • Pas de traçabilité commit → image
  • Pas de scan de sécurité automatique
  • Risque d'oubli de build/push
  • Process chronophage

Après Phase 2

  • Build automatique à chaque push
  • Multi-architecture (AMD64 + ARM64)
  • Tags multiples pour traçabilité
  • Scan Trivy automatique
  • Vulnérabilités visibles dans GitHub Security
  • Cache GitHub Actions (builds rapides)
  • Processus entièrement automatisé

Métriques

Métrique Valeur
Architectures 2 (amd64, arm64)
Tags par build 3-4 (latest, master, sha-xxx)
Temps de build (premier) ~8-10 minutes
Temps de build (avec cache) ~2-3 minutes
Scan de sécurité Automatique (Trivy)
Registry Docker Hub

Workflow Complet

À partir de maintenant, le processus est 100% automatique :

1. Développeur modifie le code
2. git commit -m "Add feature X"
3. git push origin master
   │
   ▼
4. GitHub Actions démarre automatiquement
   │
   ├─ Phase 1: Tests + Linting (2 min)
   │  ├─ flake8
   │  └─ pytest (6 tests)
   │
   ├─ Phase 2: Docker Build (8 min)
   │  ├─ Build amd64
   │  ├─ Build arm64
   │  ├─ Push Docker Hub
   │  ├─ Scan Trivy
   │  └─ Upload GitHub Security
   │
   ▼
5. Image disponible sur Docker Hub
   - nocoblas/home-fonta-web:latest
   - nocoblas/home-fonta-web:master
   - nocoblas/home-fonta-web:sha-6c88512

Utilisation des Images

Pull l'image depuis Docker Hub

# Dernière version stable
docker pull nocoblas/home-fonta-web:latest

# Version spécifique par commit SHA
docker pull nocoblas/home-fonta-web:sha-6c88512

# Version de la branche master
docker pull nocoblas/home-fonta-web:master

Run l'image localement

# AMD64 (PC classique)
docker run -p 8080:80 nocoblas/home-fonta-web:latest

# ARM64 (Raspberry Pi)
docker run -p 8080:80 nocoblas/home-fonta-web:latest
# → Docker choisit automatiquement l'architecture correcte

Déployer sur K3s

apiVersion: apps/v1
kind: Deployment
metadata:
  name: home-fonta
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: web
        image: nocoblas/home-fonta-web:latest
        ports:
        - containerPort: 80

Commandes Utiles

Vérifier les tags disponibles

# Via Docker CLI
docker search nocoblas/home-fonta-web --limit 5

# Via API Docker Hub
curl -s https://hub.docker.com/v2/repositories/nocoblas/home-fonta-web/tags/ | jq '.results[].name'

Inspecter l'image

# Voir les métadonnées
docker image inspect nocoblas/home-fonta-web:latest

# Voir les layers
docker history nocoblas/home-fonta-web:latest

# Voir la taille
docker images nocoblas/home-fonta-web

Scanner localement avec Trivy

# Scanner l'image
trivy image nocoblas/home-fonta-web:latest

# Seulement CRITICAL et HIGH
trivy image --severity CRITICAL,HIGH nocoblas/home-fonta-web:latest

# Format JSON
trivy image -f json -o report.json nocoblas/home-fonta-web:latest

Créer une Version Taguée

Pour créer une release officielle (ex: v1.0.0) :

# Créer le tag Git
git tag v1.0.0 -m "Release version 1.0.0"

# Pusher le tag
git push origin v1.0.0

Résultat : Le workflow créera automatiquement ces tags Docker :

  • nocoblas/home-fonta-web:v1.0.0
  • nocoblas/home-fonta-web:v1.0
  • nocoblas/home-fonta-web:v1
  • nocoblas/home-fonta-web:latest

Prochaines Étapes : Phase 3

La Phase 2 automatise le build Docker. Voici ce qui arrive dans la Phase 3 :

Phase 3 : Mise à Jour Automatique du Helm Chart

Objectifs :

  1. Détecter automatiquement la nouvelle image Docker
  2. Mettre à jour le tag dans helm-charts/home-fonta/values.yaml
  3. Commit et push automatique vers le repo
  4. ArgoCD détecte le changement et déploie automatiquement

Workflow ajouté :

- name: Update Helm chart
  run: |
    NEW_TAG="sha-${{ github.sha }}"
    sed -i "s/tag: .*/tag: $NEW_TAG/" helm-charts/home-fonta/values.yaml
    git add helm-charts/home-fonta/values.yaml
    git commit -m "Update image tag to $NEW_TAG"
    git push

Phases Futures

  • Phase 4 : Environnements staging/production avec promotion manuelle
  • Phase 5 : Tests d'intégration sur l'image Docker
  • Phase 6 : Rollback automatique si échec du déploiement

Conclusion

La Phase 2 du pipeline CI/CD est un succès. Nous avons mis en place :

  1. Build Docker automatique multi-architecture
  2. Push automatique vers Docker Hub
  3. Tags intelligents pour traçabilité
  4. Scan de sécurité Trivy intégré
  5. Upload des vulnérabilités vers GitHub Security
  6. Cache pour builds rapides

Le processus est maintenant 100% automatisé de la modification du code jusqu'à la publication de l'image Docker.

Chaque commit déclenche automatiquement :

  • Tests de qualité (Phase 1)
  • Build et publication Docker (Phase 2)
  • Scan de sécurité
  • Documentation via tags

C'est la base d'un pipeline CI/CD professionnel et moderne.

Dans le prochain article, nous verrons comment automatiser la mise à jour du Helm chart et le déploiement sur K3s via ArgoCD.


Ressources :

Repository : github.com/Nikob2o/home-fonta

Read more