Objectif : une CI/CD fiable pour un stack Spring Boot + Angular
Une approche CI/CD avec GitLab permet d’automatiser la compilation, les tests et le déploiement d’une application Full Stack composée d’un backend Spring Boot et d’un frontend Angular. L’objectif est de garantir des pipelines reproductibles, traçables et adaptés aux environnements (dev, staging, prod).
Architecture recommandée du dépôt
La structure ci-dessous aide à séparer clairement les responsabilités et à faciliter l’exécution des jobs en parallèle.
my-app/
backend/
pom.xml
src/...
frontend/
angular.json
package.json
src/...
.gitlab-ci.yml
README.md
Dockerfile
docker-compose.yml
k8s/
deployment.yaml
.env.example
Prérequis
GitLab et registres d’images
Le déploiement moderne repose généralement sur des conteneurs. Un registre d’images (GitLab Container Registry, ECR, etc.) doit être configuré, ainsi que les variables d’authentification dans GitLab.
Variables sensibles
Les secrets (ex. tokens, mots de passe base de données) doivent être stockés dans CI/CD Variables côté projet ou groupe. Par exemple :
- DOCKER_REGISTRY
- DOCKER_USERNAME
- DOCKER_PASSWORD
- SPRING_PROFILES_ACTIVE
- SPRING_DATASOURCE_URL
- SPRING_DATASOURCE_USERNAME
- SPRING_DATASOURCE_PASSWORD
Modèle de pipeline GitLab : étapes et bonnes pratiques
Un pipeline robuste se découpe généralement en étapes : build, test, package, docker, puis deploy. La séparation backend/front permet d’optimiser le temps de calcul.
Exemple complet de .gitlab-ci.yml
Le modèle ci-dessous illustre une configuration typique. Les images de base et les commandes doivent être ajustées selon les versions (JDK, Node, Angular CLI, Maven/Gradle).
stages:
- lint
- test
- build
- docker
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
NODE_ENV: "production"
APP_VERSION: "$CI_COMMIT_SHORT_SHA"
cache:
key: "$CI_PROJECT_NAME"
paths:
- .m2/repository/
- frontend/node_modules/
- frontend/.angular/cache/
# -----------------------------
# Backend: Lint + Tests
# -----------------------------
backend_test:
stage: test
image: maven:3.9-eclipse-temurin-17
rules:
- changes:
- backend/**/*
script:
- cd backend
- mvn -B -ntp test
# -----------------------------
# Frontend: Lint + Tests
# -----------------------------
frontend_test:
stage: test
image: node:20
rules:
- changes:
- frontend/**/*
script:
- cd frontend
- npm ci
- npm run lint --if-present
- npm test -- --watch=false --browsers=ChromeHeadless
# -----------------------------
# Build backend (jar)
# -----------------------------
backend_build:
stage: build
image: maven:3.9-eclipse-temurin-17
needs: ["backend_test"]
rules:
- changes:
- backend/**/*
script:
- cd backend
- mvn -B -ntp package -DskipTests
artifacts:
paths:
- backend/target/*.jar
expire_in: 1 week
# -----------------------------
# Build frontend (dist)
# -----------------------------
frontend_build:
stage: build
image: node:20
needs: ["frontend_test"]
rules:
- changes:
- frontend/**/*
script:
- cd frontend
- npm ci
- npm run build -- --configuration production
artifacts:
paths:
- frontend/dist/
expire_in: 1 week
# -----------------------------
# Docker: images backend + frontend (option)
# -----------------------------
docker_backend:
stage: docker
image: docker:27
services:
- name: docker:27-dind
command: ["--tls=false"]
needs: ["backend_build"]
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
script:
- docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" "$DOCKER_REGISTRY"
- docker build -t "$DOCKER_REGISTRY/$CI_PROJECT_PATH/backend:$APP_VERSION" -f Dockerfile.backend .
- docker push "$DOCKER_REGISTRY/$CI_PROJECT_PATH/backend:$APP_VERSION"
docker_frontend:
stage: docker
image: docker:27
services:
- name: docker:27-dind
command: ["--tls=false"]
needs: ["frontend_build"]
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
script:
- docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" "$DOCKER_REGISTRY"
- docker build -t "$DOCKER_REGISTRY/$CI_PROJECT_PATH/frontend:$APP_VERSION" -f Dockerfile.frontend .
- docker push "$DOCKER_REGISTRY/$CI_PROJECT_PATH/frontend:$APP_VERSION"
# -----------------------------
# Deploy (exemple sur environnement)
# -----------------------------
deploy_staging:
stage: deploy
image: alpine:3.20
needs: ["docker_backend", "docker_frontend"]
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
environment:
name: staging
url: https://staging.example.com
script:
- echo "Déploiement sur staging via outil (kubectl/helm/ssh) ..."
# Exemples possibles:
# - utiliser kubectl/helm
# - mettre à jour un chart Helm
# - exécuter un script de déploiement via SSH
Dockerfiles typiques
Dockerfile.backend (Spring Boot)
La stratégie recommandée consiste à produire un JAR, puis à créer une image légère (ex. base JRE). Le snippet ci-dessous illustre un pattern standard.
# Dockerfile.backend
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY backend/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]
Dockerfile.frontend (Angular)
Le frontend Angular est empaqueté en statique (répertoire dist) et servi par un serveur web.
# Dockerfile.frontend
FROM nginx:alpine
COPY frontend/dist/ /usr/share/nginx/html
EXPOSE 80
Déploiement : stratégies à envisager
Pour un déploiement cohérent, plusieurs options existent : Kubernetes (Helm/Kustomize), Docker Compose sur une VM, ou encore déploiement via plateformes PaaS.
Déploiement sur Kubernetes (recommandé en entreprise)
Le pipeline peut mettre à jour les images dans les manifests (ou via Helm), puis déclencher un déploiement contrôlé : rollouts, readiness probes, et gestion des versions.
Déploiement sur VM via SSH (plus simple)
Le pipeline exécute un script distant qui : pull les images, redémarre les services et valide un endpoint. Cette approche reste valable pour des environnements légers.
Gestion des environnements et des branches
Une convention fréquente : main → staging (ou prod selon politique), release/* → production, merge requests → exécution du pipeline de validation sans déploiement.
Des règles rules dans GitLab permettent de conditionner les jobs à la présence de changements dans backend/** ou frontend/**, tout en réduisant les exécutions inutiles.
Qualité : lint, coverage, artefacts et notifications
Lint et tests de bout en bout
Les jobs de lint minimisent les erreurs précoces. Les tests E2E (ex. Playwright/Cypress) peuvent être ajoutés en option, exécutés uniquement sur merge requests.
Artefacts
Les artefacts (jar, dist) permettent d’inspecter rapidement les résultats et de tracer ce qui a été construit. Leur durée (expire_in) doit être raisonnable.
Reporting
GitLab peut intégrer des rapports (JUnit pour Maven/Surefire, coverage, etc.) afin de rendre la qualité visible dans l’interface.
Contrats API et intégration frontend/backend
Une approche efficace consiste à : valider la compatibilité via tests contractuels (ex. OpenAPI), ou à utiliser un pipeline qui génère le client/les modèles à partir des schémas. Cela réduit les risques de décalage entre backend et frontend.
Conclusion
Mettre en place une CI/CD avec GitLab pour Spring Boot et Angular consiste à : séparer les builds, enchaîner les tests, packager en artefacts, puis déployer de manière contrôlée (images Docker, Kubernetes/VM). Une configuration basée sur des règles de changement et des environnements clairs améliore fortement la fiabilité et la maintenabilité.
À propos de l'auteur
Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.
Contact : latygueyesamba@gmail.com | Dakar, Sénégal