Environnements & CI/CD — DYNORS
Guide pratique pour mettre en place une plateforme DevOps gratuite permettant de reproduire fidèlement la chaîne locale → dev → RMOA → prod.
Principe : 5 environnements, 1 pipeline
Local ──► DEV ──► RMOA DYNORS ──► RMOA CLIENT ──► PROD
(dev) (auto CI) (manuel QA) (manuel client) (manuel CTO)
Chaque environnement est reproductible par un fichier docker-compose.<env>.yml + un fichier .env.<env>.
Stack CI/CD gratuite recommandée
DYNORS utilise déjà GitLab (gitlab.com). La solution la plus simple et gratuite est d'utiliser GitLab CI + runners auto-hébergés sur un petit VPS.
| Composant | Outil | Coût | Pourquoi |
|---|---|---|---|
| Code + pipeline | GitLab CI | Gratuit (gitlab.com) | Déjà utilisé |
| Runners CI | GitLab Runner self-hosted | Gratuit (VPS 10–15 €/mois) | Contrôle total |
| Registry Docker | GitLab Container Registry | Gratuit | Intégré à GitLab |
| Secrets / variables | GitLab CI/CD Variables | Gratuit | Protégées, masquées |
| Notifications | Slack (plan gratuit) | Gratuit | Webhooks CI |
| Monitoring | Grafana + Prometheus | Gratuit (self-hosted) | Stack standard |
| Logs | Loki + Grafana | Gratuit (self-hosted) | Intégré à Grafana |
Alternative si pas de GitLab : Woodpecker CI — open source, Docker-based, compatible GitHub/Gitea/Forgejo.
Installation d'un runner GitLab self-hosted
# Sur un VPS Ubuntu/Debian
# 1. Installer Docker
curl -fsSL https://get.docker.com | sh
# 2. Installer GitLab Runner
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get install gitlab-runner
# 3. Enregistrer le runner (token depuis GitLab → Settings → CI/CD → Runners)
sudo gitlab-runner register \
--url https://gitlab.com/ \
--registration-token YOUR_TOKEN \
--executor docker \
--docker-image "docker:latest" \
--description "dynors-runner-prod" \
--tag-list "dynors-runner" \
--docker-privileged
Un runner peut servir tous vos projets GitLab.
Structure des fichiers par projet
dynors-projects/clients/mon-projet/
├── .gitlab-ci.yml # Pipeline CI/CD
├── sirrat.config.yml # Config SIRRAT du projet
├── docker-compose.local.yml # Stack locale (dev machine)
├── docker-compose.dev.yml # Stack DEV (serveur DYNORS)
├── docker-compose.rmoa-dynors.yml # Stack RMOA DYNORS
├── docker-compose.rmoa-client.yml # Stack RMOA CLIENT (infra client)
├── docker-compose.prod.yml # Stack PRODUCTION
├── .env.example # Template (commité)
├── .env.local # LOCAL (gitignored)
├── .env.dev # DEV (gitignored)
├── .env.rmoa-dynors # RMOA DYNORS (gitignored)
├── .env.rmoa-client # RMOA CLIENT (gitignored)
├── .env.prod # PROD (gitignored)
├── backend/
│ └── src/main/resources/
│ ├── application.yml # Config commune
│ ├── application-local.yml
│ ├── application-dev.yml
│ ├── application-rmoa-dynors.yml
│ ├── application-rmoa-client.yml
│ └── application-prod.yml
└── scripts/
├── deploy-dev.sh
├── deploy-rmoa-dynors.sh
├── deploy-rmoa-client.sh
├── deploy-prod.sh
├── backup-prod.sh
└── rollback.sh
Template docker-compose.dev.yml
version: "3.8"
services:
backend:
image: ${CI_REGISTRY_IMAGE}/backend:dev-${CI_COMMIT_SHORT_SHA}
env_file: .env.dev
ports:
- "8080:8080"
depends_on:
- postgres
- redis
frontend:
image: ${CI_REGISTRY_IMAGE}/frontend:dev-${CI_COMMIT_SHORT_SHA}
env_file: .env.dev
ports:
- "80:80"
postgres:
image: postgres:15
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_dev_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
postgres_dev_data:
Template .env.example
# ─── Environnement ─────────────────────────────────────
ENVIRONMENT= # local | dev | rmoa-dynors | rmoa-client | prod
LOG_LEVEL= # DEBUG | INFO | WARN | ERROR
# ─── URLs ──────────────────────────────────────────────
BASE_URL=
API_URL=
# ─── Base de données ────────────────────────────────────
DB_HOST=
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASSWORD=
DB_SSL_MODE= # disable | require
# ─── Inter-app DYNORS (SLY) ─────────────────────────────
SLY_BASE_URL= # https://sly.dynors.com (prod) | http://sly-dev.dynors.com (dev)
SLY_FORWARD_SECRET= # partagé avec SLY — OBLIGATOIRE en prod, vide OK en local
# ─── Sécurité ──────────────────────────────────────────
JWT_SECRET=
JWT_EXPIRATION=3600
# ─── Paiements (Wave, Orange Money) ─────────────────────
PAYMENT_PROVIDER= # mock | wave-sandbox | wave-production | orange-sandbox | orange-production
PAYMENT_API_KEY=
# ─── Emails ─────────────────────────────────────────────
EMAIL_PROVIDER= # mock | smtp-dev | sendgrid
EMAIL_API_KEY=
# ─── Storage ────────────────────────────────────────────
STORAGE_PROVIDER= # filesystem | minio | s3
MINIO_ENDPOINT=
MINIO_BUCKET=
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=
# ─── SIRRAT ─────────────────────────────────────────────
SIRRAT_ENABLED= # true | false
SIRRAT_API_URL= # https://sirrat.dynors.com/api
SIRRAT_PROJECT_ID=
SIRRAT_API_TOKEN=
# ─── Monitoring ─────────────────────────────────────────
SENTRY_ENABLED=false
SENTRY_DSN=
GRAFANA_ENABLED=false
Pipeline GitLab CI — structure complète
# .gitlab-ci.yml — DYNORS projet satellite
stages:
- build
- test
- deploy-dev
- deploy-rmoa-dynors
- deploy-rmoa-client
- deploy-prod
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
DOCKER_DRIVER: overlay2
SIRRAT_API_URL: "https://sirrat.dynors.com/api"
# ─── BUILD ──────────────────────────────────────────────────────────
build:backend:
stage: build
image: gradle:8.5-jdk17
script:
- cd backend && gradle clean build -x test
artifacts:
paths: [backend/build/libs/*.jar]
expire_in: 1 day
only: [develop, release/*, main]
tags: [dynors-runner]
build:frontend:
stage: build
image: node:20
script:
- cd frontend && npm ci && npm run build
artifacts:
paths: [frontend/dist/]
expire_in: 1 day
only: [develop, release/*, main]
tags: [dynors-runner]
# ─── TESTS ──────────────────────────────────────────────────────────
test:unit:
stage: test
image: gradle:8.5-jdk17
services: [postgres:15, redis:7]
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test
POSTGRES_PASSWORD: test
script:
- cd backend && gradle test jacocoTestReport
artifacts:
reports:
junit: backend/build/test-results/test/TEST-*.xml
only: [develop, release/*, main]
tags: [dynors-runner]
test:integration:
stage: test
image: gradle:8.5-jdk17
services: [postgres:15, redis:7]
script:
- cd backend && gradle integrationTest
only: [develop, release/*, main]
tags: [dynors-runner]
security:scan:
stage: test
image: gradle:8.5-jdk17
script:
- cd backend && gradle dependencyCheckAnalyze
- |
CRITICAL=$(cat build/reports/dependency-check-report.json \
| jq '.vulnerabilities | map(select(.severity=="CRITICAL")) | length')
if [ "$CRITICAL" -gt 0 ]; then
echo "CRITICAL vulnerabilities found: $CRITICAL"
exit 1
fi
only: [develop, release/*, main]
tags: [dynors-runner]
# ─── DEPLOY DEV (automatique sur develop) ───────────────────────────
deploy:dev:
stage: deploy-dev
image: docker:latest
services: [docker:dind]
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- cd backend
- docker build -t $CI_REGISTRY_IMAGE/backend:dev-$CI_COMMIT_SHORT_SHA .
- docker push $CI_REGISTRY_IMAGE/backend:dev-$CI_COMMIT_SHORT_SHA
- ssh dynors-dev@dev.dynors.internal
"cd /opt/$CI_PROJECT_NAME &&
docker-compose -f docker-compose.dev.yml pull &&
docker-compose -f docker-compose.dev.yml up -d"
# Notifier SIRRAT
- |
curl -sX POST $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/deployments \
-H "Authorization: Bearer $SIRRAT_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"environment\":\"dev\",\"version\":\"$CI_COMMIT_SHORT_SHA\",\"status\":\"deployed\"}"
environment:
name: development
url: https://dev-$CI_PROJECT_NAME.dynors.com
only: [develop]
when: on_success
tags: [dynors-runner]
# ─── DEPLOY RMOA DYNORS (manuel — après Porte #1) ───────────────────
deploy:rmoa-dynors:
stage: deploy-rmoa-dynors
script:
# Vérifier Quality Gate #1 dans SIRRAT
- |
STATUS=$(curl -s $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/quality-gates/1/status \
-H "Authorization: Bearer $SIRRAT_API_TOKEN" | jq -r '.status')
[ "$STATUS" = "passed" ] || { echo "QG #1 non passé"; exit 1; }
# ... build + deploy ...
only: [release/*-internal]
when: manual
tags: [dynors-runner]
# ─── DEPLOY RMOA CLIENT (manuel — après Porte #2) ───────────────────
deploy:rmoa-client:
stage: deploy-rmoa-client
script:
# Vérifier QG #2 + générer PV de recette
- |
curl -sX POST $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/pv-recette/generate \
-H "Authorization: Bearer $SIRRAT_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"version\":\"$CI_COMMIT_TAG\"}"
only: [release/*]
when: manual
tags: [dynors-runner]
# ─── DEPLOY PROD (manuel — PV signé + CTO) ──────────────────────────
deploy:prod:
stage: deploy-prod
script:
# Vérifier PV signé
- |
SIGNED=$(curl -s $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/pv-recette/status \
-H "Authorization: Bearer $SIRRAT_API_TOKEN" | jq -r '.signed')
[ "$SIGNED" = "true" ] || { echo "PV non signé"; exit 1; }
# ... build prod image + deploy ...
- sleep 30 && curl -f $PROD_URL/actuator/health || exit 1
only: [tags]
when: manual
tags: [dynors-runner]
# ─── NOTIFICATIONS ──────────────────────────────────────────────────
notify:failure:
stage: .post
script:
- |
curl -sX POST $SLACK_WEBHOOK \
-H 'Content-Type: application/json' \
-d "{\"text\":\"Pipeline failed — $CI_PROJECT_NAME ($CI_COMMIT_REF_NAME) — $CI_PIPELINE_URL\"}"
when: on_failure
tags: [dynors-runner]
Variables CI/CD GitLab à configurer
Dans GitLab → Settings → CI/CD → Variables (protégées + masquées pour les valeurs sensibles) :
# SIRRAT
SIRRAT_API_TOKEN # token d'authentification SIRRAT
SIRRAT_PROJECT_ID # ex. supergest-001
# SLY
SLY_BASE_URL # https://sly.dynors.com
SLY_FORWARD_SECRET # secret HMAC partagé avec SLY — PROTÉGÉ + MASQUÉ
# Docker Registry
CI_REGISTRY # registry.dynors.com ou registry.gitlab.com
CI_REGISTRY_USER
CI_REGISTRY_PASSWORD
# Base de données par env
DB_DEV_PASSWORD
DB_RMOA_DYNORS_PASSWORD
DB_RMOA_CLIENT_PASSWORD
DB_PROD_PASSWORD
# JWT
JWT_DEV_SECRET
JWT_PROD_SECRET # ≥ 256 bits — PROTÉGÉ + MASQUÉ
# Paiement (sandbox → prod)
WAVE_SANDBOX_KEY
WAVE_PROD_KEY # PROTÉGÉ + MASQUÉ
ORANGE_MONEY_PROD_KEY # PROTÉGÉ + MASQUÉ
# Notifications
SLACK_WEBHOOK
SLACK_WEBHOOK_QA
SLACK_WEBHOOK_PROD
# Monitoring
SENTRY_DSN
SENTRY_PROD_DSN
Environnement pré-prod (RMOA DYNORS) — aussi proche que possible de la PROD
Points critiques pour que RMOA DYNORS soit crédible :
| Aspect | RMOA DYNORS | PROD | Comment aligner |
|---|---|---|---|
| Base de données | PostgreSQL 15 | PostgreSQL 15 | ✅ même version |
| SSL | Requis (DB_SSL_MODE=require) |
Requis | ✅ même config |
| Storage | MinIO (compatible S3) | S3 ou MinIO | ✅ même API |
| Paiements | Sandbox Wave/OM | Production | ⚠ Les providers sandbox ont des limites (ne pas tester les cas edge en RMOA CLIENT) |
| SLY | sly-dev.dynors.com |
sly.dynors.com |
✅ même stack, URL différente |
SLY_FORWARD_SECRET |
Même secret | Production | ✅ variable GitLab protégée |
| Redis | Oui | Oui | ✅ même config |
| Sentry | Activé (env rmoa-dynors) |
Activé (env production) |
✅ séparer les environnements dans Sentry |
Branches Git (GitFlow adapté DYNORS)
main # 😇 PRODUCTION
develop # 🔥 DEV (auto-deploy)
release/v1.3.0-internal # 🌫 RMOA DYNORS
release/v1.3.0 # 🌟 RMOA CLIENT
feature/nom-feature # 😈 En dev
hotfix/correctif-critique # 🚨 Urgence prod
Protections GitLab (Settings → Repository → Protected branches) :
main:
push: No one
merge: Maintainers
force_push: false
develop:
push: Developers + Maintainers
merge: Developers + Maintainers
release/*:
push: Maintainers only
merge: Maintainers only
Checklist nouveau projet
☐ 1. Créer le repo GitLab dans dynors-projects/
☐ 2. Copier template .gitlab-ci.yml + adapter les stages
☐ 3. Créer sirrat.config.yml à la racine
☐ 4. Créer les docker-compose.*.yml
☐ 5. Créer .env.example (commité) et les .env.* (gitignorés)
☐ 6. Configurer les branches protégées GitLab
☐ 7. Ajouter les variables CI/CD GitLab (cf. liste § 4)
☐ 8. Déclarer le projet dans SIRRAT
☐ 9. Configurer les canaux Slack (#dev-X, #qa-X, #prod-alerts)
☐ 10. Premier push → vérifier pipeline build + deploy DEV