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

Mise en place d'un pipeline CI/CD (1/4) - Poser les Bases

Date de l'article:15-10-2025
Github-Actions
Création d'un premier workflow GitHub Actions couvrant le build Maven, la conteneurisation Docker et le push automatique vers Docker Hub. On pose les fondations de base: scripts CI réutilisables, versionning d'image et authentification sécurisée via les secrets

Les objectifs sont la création d'un pipeline de base (sur la branche unique [main]), lui ajouter les scripts bash facilitant le build et la ré-utilisation des ces scripts sur différentes multi-plateformes CI/CD (plan futur). Nous ajoutons les fichiers qui vont avec: le Dockerfile, le .gitignore ainsi que ses badges.
Nous améliorons le pipeline en y ajoutant un versionning de l'image ainsi que la publication de l'image dans la registry Docker (Hub)

  Rappel: Ce projet utilise Maven Wrapper, qui est un utilitaire qui vous permet d'exécuter des projets Apache Maven sans avoir à le préinstaller sur le système.
Il garantit que chaque développeur et chaque environnement CI/CD utilise exactement la même version de Maven que celle spécifiée pour le projet.
La commande mvnw est également utilisée dans les scripts et workflows CI/CD

Stack technologique
Configuration du projet:
SpringBoot: 3.5.4 - Build tool: Maven
Language: Java - Packaging: JAR
Java: 17 - Dépendances: Spring Web, Spring Data JPA, PostgreSQL Driver, Lombok

Pré-requis
La branche [main] démarre avec un pipeline minimal.
Les contrôles qualité avancés seront ajoutés progressivement pour les branches [develop] et [staging] au fur et à mesure des articles


FLASHCARDS/
├── .github/workflows/
│           └─ main.yml
├── ci-scripts/
├── src/
│   ├─ main/java/com/example/flashcards/
│   │   ├─ config/          # security config
│   │   ├─ controller/      # REST controllers
│   │   ├─ dto/             # DTOs for API exchanges
│   │   ├─ entity/          # JPA entities
│   │   ├─ mapper/          # mappers
│   │   ├─ repository/      # JPA repositories
│   │   ├─ service/         # services
│   │   └─ FlashcardsApplication.java
│   ├─ resources/
│   │   └─ application.properties
│   └─ test/java/com/example/flashcards/
│       ├─ controller/
│       ├─ dto/
│       ├─ entity/
│       ├─ integration/
│       ├─ mapper/
│       ├─ service/
│       └─ resources/
└── pom.xml  

Les scripts CI
FLASHCARDS/
├── ci-scripts/
│   ├── build.sh
│   ├── docker-build.sh
│   └── test.sh
Ces scripts:
  • Permettent d'exécuter des commandes depuis les scripts
  • Utilises les commandes du wrapper, comme l'app et les workflows
  • Sont en prévision de gérer plusieurs scénarios CI/CD pour le même projet
En voici le contenu:
Compile + package sans exécuter les tests
ci-scripts/build.sh
#!/bin/bash
set -e
echo "=== Building app ==="
./mvnw clean package -DskipTests

Exécution explicite des tests
ci-scripts/test.sh
#!/bin/bash
set -e
echo "=== Running tests ==="
./mvnw clean verify

Uniquement si tout est OK
ci-scripts/docker-build.sh
#!/bin/bash
set -e
IMAGE_NAME="flashcards-app"
echo "Building Docker image: $IMAGE_NAME"
docker build -t $IMAGE_NAME .
Rendre les scripts executables: chmod +x ci-scripts/*.sh (en local)
Utilisez l'astuce Git pour rendre tous vos fichiers executables dans le pipeline
Astuce Git
Pour ne pas écrire d'étape comme: ( ex: rendre le Wrapper executable)
- name: Make scripts and mvnw executable
  run: |
    chmod +x mvnw
    chmod +x ci-scripts/*.sh

et/ou ajouter la ligne d'ajout de permission aux scripts dans les workflows,


1. Exécutez (1seule fois) cette commande, avant le commit :
git update-index --chmod=+x mvnw
git update-index --chmod=+x ci-scripts/*.sh
2. Vérification à faire, en local:
git ls-files --stage
git diff --summary
retour attendus des vérifications (étape 2):
100755 ci-scripts/build.sh
100755 ci-scripts/docker-build.sh
100755 ci-scripts/test.sh
100755 mvnw

 


3. Continuer ensuite la procédure Git normale avec:
git add .
git commit -m "Make scripts and mvnw executable"
git push origin feature/branchName
Résultat: Les étapes du genre: "Make scripts and mvnw executable" ou les ligne chmod +x deviennent inutiles.

Le workflow Github Actions
Dans un premier temps, ce pipeline unique à pour objectif de construire l'application, générer l'image Docker et de la publier sur Docker Hub
Ce pipeline constitue une base fonctionnelle.
     Il sera enrichi en fin de projet, après validation des contrôles et étapes intermédiaires dans les branches develop et staging
     Cela permettra d'introduire progressivement des vérifications supplémentaires et d'affiner le processus avant sa consolidation définitive sur la branche principale

Mise en place et étapes du pipeline
Les workflow sont localisés dans: .github/workflows/
name: Build and Test Flashcards App
on:
  push:
    branches: [ main ]
  pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Cache Maven dependencies
        uses: actions/cache@v4
        with:
          path: ~/.m2
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
          restore-keys: |
            ${{ runner.os }}-maven-

      - name: Build
        run: ci-scripts/build.sh
      - name: Run tests
        run: ci-scripts/test.sh
      - name: Build Docker image
        if: success() && hashFiles('Dockerfile') != ''
        run: ci-scripts/docker-build.sh


Voir tous les workflows
Ce pipeline se compose de 6 étapes:
- name: Checkout repository
  Récupération du code source (GitHub)
- name: Set up JDK 17
  Installation de JDK, distribution Temurin
- name: Cache Maven dependencies
  Mise en cache des dépendances Maven
- name: Build
  Exécution du script build.sh:
  Compilation et généreration de l'artefact
- name: Run tests
  Exécution du script test.sh:
  Lancement des tests (unitaires et intégration)
- name: Build Docker image
  Exécution du script docker-build.sh:
  Conteneurise l'app dans une image Docker

Ajout du Dockerfile
Ajoutez ce fichier à la racine du projet
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/flashcards-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Ajout du fichier .dockerignore

→ Qui dit "Dockerfile", dit ".dockerignore"

On dispose de l'artefact (JAR) produit par l'étape "Build" de Maven;
On peut donc, utiliser la commande COPY depuis le target/
On expose le port et on lui fournis la commande de lancement de l'application
Résultat:
L'image utilise JRE Alpine et + légère que JDK (≈ 108 Mo contre 352 Mo)
Les builds sont rapides et les conteneurs + performants
Une bonne pratique qui réduit la taille du contexte de build et accélère la construction de l'image. Les dossiers à exclure, à minima sont:
target/ .git/ .github/ .idea/ *.log

Amélioration des scripts et du pipeline (main)
1. Ajout du versionning de l'image Docker
1. Dans le pipeline, ajoutez au dessus de steps:
env:
  IMAGE_NAME: flashcards
  IMAGE_TAG: v${{ github.run_number }}
v${{ github.run_number }} construit automatiquement une image versionnée
2. Adapter la commande du "Build Docker image"
- name: Build Docker image with version tag
  run: |
    ci-scripts/docker-build.sh "$IMAGE_NAME" "$IMAGE_TAG"
3. Dans ci-scripts/docker-build.sh, remplacer ↓
IMAGE_NAME="flashcards-app"
echo "Building Docker image: $IMAGE_NAME"
→ Chaque exécution du pipeline déclenche un nouveau build,
→ qui génère une nouvelle version (v1, v2, v3 + latest)
→ 4. par ↓
IMAGE_NAME=${1:-flashcards}
IMAGE_TAG=${2:-latest}

echo "== Building Docker image: ${IMAGE_NAME}:${IMAGE_TAG} =="
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} . 

L'utilisation de v${{ github.run_number }} n'est qu'une approche temporaire,
     Plus tard, lors de la finalisation de la branche main, nous créons une vraie version, ainsi qu'une release


2. Authentification sécurisée et push automatique de l'image dans Docker Hub

Vous devez disposer d'un compte Docker Hub

1. Créer un token depuis votre profil Docker Hub:
Account Settings → Security → New Access Token

 github-actions-push - Expires on  Never
 Access permissions: Read, Write

2. Créer les secrets depuis votre repo
Settings → Secrets and variables → Actions

DOCKERHUB_TOKEN        (value: your_DockerHub_token)
DOCKERHUB_USERNAME     (value: your_DockerHub_account)

3. Ajouter cette étapes à la fin du pipeline:

- name: Log in to Docker Hub
  run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

- name: Push image to Docker Hub
  run: |
    docker tag $IMAGE_NAME:$IMAGE_TAG ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:$IMAGE_TAG
    docker tag $IMAGE_NAME:$IMAGE_TAG ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest
    docker push ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:$IMAGE_TAG
    docker push ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest

Résultat:
La branche [main] représente l'état stable du projet et produit les artefacts distribuables (image Docker)
→ Le pipeline se connecte à DockerHub avec le token sécurisé
→ L'image est automatiquement taguée (2 tags: latest et vX) et publiée dans la registry Docker: /flashcards/tags

Ajout des badges Docker

Les fichiers readme.md | readme_ci.md affichent en temps réel l'état du pipeline et les informations Docker, grâce à ces balises

CI/CD Status Docker Image Version Docker Pulls Docker Image Size

Reflète le résultat du workflow [main]
Exemple de script à intégrer dans votre readme pour afficher l'état de votre workflow nommé "main":
[![CI/CD](https://github.com/user/repo/actions/workflows/main.yml/badge.svg)](https://github.com/user/repo/actions/workflows/main.yml)
L'image Docker (Hub)
[![Docker Image](https://img.shields.io/docker/v/user/imageName?sort=semver)](https://hub.docker.com/r/user/imageName/tags)
[![Docker Pulls](https://img.shields.io/docker/pulls/user/imageName)](https://hub.docker.com/r/user/imageName)
[![Docker Image Size](https://img.shields.io/docker/image-size/user/imageName/latest)](https://hub.docker.com/r/user/imageName)

Déploiement & Test local
Une fois l'image Docker poussé dans la registry, on peut la tester localement:
docker pull valeriejeanne/flashcards:latest
docker run -p 8080:8080 valeriejeanne/flashcards:latest

Ouvrir ensuite http://localhost:8080

Dans cette première version, l'application nécessite déjà une base PostgreSQL
ainsi qu'un profile local (dev) adapté est configuré (application.properties défini avec: SPRING_PROFILES_ACTIVE=dev )

Le repository reste la source de vérité en cas de divergence liées soit aux mises à jour applicative, soit à ceux des actions dans le workflows CI/CD
Le tableau de suivi & mise à jour peut être consulté pour savoir ce qui est en cours ou prévu prochainement.
Dans l'article suivant, nous verrons comment définir et intégrer les profils Spring dans l'application ce qui préparera le multi-branche (develop/staging)

Laissez-moi un commentaire

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