Skip to content

Déploiement

English version
Retour au sommaire


Source principale : docs/DEPLOIEMENT.md (référence complète, conservée en place).


Architecture

Internet
    │
    ▼
┌─────────────────────────────────────────┐
│              Traefik                    │
│  (TLS Let's Encrypt, CORS MinIO)        │
└──────────────────┬──────────────────────┘
                   │
      ┌────────────┴────────────┐
      ▼                         ▼
┌─────────────┐        ┌─────────────────────┐
│  Next.js    │◄──────►│  MinIO (S3)         │
│  port 3000  │ upload │  port 9000 (API)    │
│             │        │  port 9001 (console)│
└──────┬──────┘        └─────────────────────┘
       │                         ▲
       ▼                         │ GET audio (direct)
┌─────────────┐             Navigateur client
│ PostgreSQL  │
│  port 5432  │
└─────────────┘

Rôle de chaque composant

Composant Rôle
Traefik Reverse proxy, TLS, middleware CORS pour MinIO
Next.js Application + API REST (auth, upload audio, proxy fallback dev)
PostgreSQL Données métier (langues, cartes, items, speakers, admins)
MinIO Stockage audio — bucket audios public en lecture

Accès direct navigateur → MinIO pour les audios : zéro charge sur Next.js lors de la lecture, URLs stables compatibles avec le Service Worker.


Prérequis

Composant Version minimale
Docker ≥ 24
Docker Compose ≥ 2.20
Traefik Déployé sur le réseau Docker externe
GitLab Runner Avec accès Docker (pour CI/CD)

Variables d'environnement

Variables build-time (NEXT_PUBLIC_*)

Intégrées au bundle lors du docker build. Un changement de valeur nécessite un rebuild.

Variable Description Exemple
NEXT_PUBLIC_APP_URL URL publique de l'app (emails de confirmation) https://racines.kiyara.net
NEXT_PUBLIC_PWA_ENABLED Activer le Service Worker true (prod), false (dev)

AUDIO_CDN_URL n'est plus un build-arg — c'est une variable runtime injectée au démarrage du container.

Variables runtime (container)

Passées via env_file dans docker-compose.yml. Aucun rebuild requis.

Audio CDN (critique) :

Variable Description Valeur exemple
AUDIO_CDN_URL URL publique MinIO (inclut le bucket) — obligatoire https://racines-s3.id2real.net/audios
AUDIO_PROXY_DISABLED Désactiver le fallback proxy true (prod)
AUDIO_CDN_DOMAIN Hostname pour la CSP — dérivé automatiquement si absent racines-s3.id2real.net

⚠️ L'entrypoint Docker (docker/entrypoint.sh) refuse de démarrer si AUDIO_CDN_URL est vide.

PostgreSQL :

Variable Valeur exemple
PG_HOST postgres
PG_PORT 5432
PG_DATABASE racines_db
PG_USER racines
PG_PASSWORD [secret]
PG_SSL false

MinIO (accès serveur pour upload) :

Variable Description
MINIO_ENDPOINT URL interne (Node.js → MinIO) : http://minio:9000
MINIO_ACCESS_KEY Clé d'accès applicative
MINIO_SECRET_KEY Clé secrète applicative
MINIO_BUCKET Nom du bucket : audios
MINIO_PUBLIC_URL URL publique côté serveur (construction des audio_url)

Application :

Variable Description
JWT_SECRET Secret JWT (sessions admin/speaker)
NODE_ENV production

SMTP (formulaire de contact) :

Variable Description
SMTP_HOST Serveur SMTP
SMTP_PORT Port SMTP (587 STARTTLS, 465 SSL)
SMTP_SECURE true pour port 465, sinon false
SMTP_USER Utilisateur SMTP
SMTP_PASS Mot de passe SMTP
SMTP_FROM_EMAIL Adresse expéditeur
ADMIN_EMAIL_1/2/3 Destinataires des messages de contact

Format du fichier .app_env

# Toutes les valeurs entre double quotes (obligatoire pour les valeurs avec #, =, espaces)
AUDIO_CDN_URL="https://racines-s3.id2real.net/audios"
JWT_SECRET="un_secret_long_et_aleatoire_generé"
PG_PASSWORD="mot_de_passe_postgres"
MINIO_SECRET_KEY="cle_secrete_minio"

Pipeline CI/CD GitLab

Stages

push branche
    │
    ├── test
    │     ├── test:quality:sonarqube   (main/develop/qa/test-ci + tags)
    │     └── test:build:docker        (MR uniquement — target builder)
    │
    ├── containerize
    │     ├── package:image:dev        (branche dev)
    │     ├── package:image:staging    (develop/qa/test-ci)
    │     └── package:image:prod       (main + tags)
    │
    └── deploy
          ├── deploy:int:ansible       (develop/test-ci → staging, automatique)
          ├── deploy:dev:ansible       (dev → dev, automatique)
          └── deploy:prod:manual       (main/tags → prod, ⚠️ déclenchement MANUEL)

Une seule image par commit — identique pour tous les environnements. L'URL CDN MinIO est injectée au démarrage du container.

Images Docker

Job Branche(s) Tag image
package:image:dev dev dev
package:image:staging develop, qa, test-ci develop / qa
package:image:prod main, tags main / v1.2.3

Variables GitLab CI/CD

À définir dans GitLab → Settings → CI/CD → Variables :

Variable Description
DOCKER_REG_URL URL du registry Docker
DOCKER_REG_LOGIN Login registry
DOCKER_REG_PASS Mot de passe registry
SSHKEY_CI Clé SSH vers le serveur staging
SSHKEY_DEV Clé SSH vers le serveur dev
DEPLOY_SSHKEY_FILE Clé SSH vers le serveur production
SONAR_HOST_URL URL SonarQube (analyse qualité)

Déploiement en production

GitLab → Pipelines → pipeline sur main → deploy:prod:manual → ▶ (cliquer Play)

Le déploiement est manuel : aucune mise en production automatique.


Infrastructure MinIO

Bucket et politiques

mc alias set minio https://racines-s3.id2real.net ACCESS_KEY SECRET_KEY

# Créer le bucket avec versioning
mc mb minio/audios --ignore-existing
mc version enable minio/audios

# Lecture publique (navigateur → audios sans auth)
mc anonymous set download minio/audios

# Compte applicatif (upload/delete, sans droits admin)
mc admin user add minio app_racines_rw MOT_DE_PASSE_FORT
mc admin policy attach minio readwrite --user app_racines_rw

CORS MinIO via Traefik

MinIO AGPL ne supporte pas la configuration CORS via l'API S3. Le CORS est géré par un middleware Traefik :

labels:
  - traefik.http.routers.racines-minio-api.middlewares=minio-cors
  - traefik.http.middlewares.minio-cors.headers.accesscontrolalloworiginlist=https://racines.kiyara.net,https://staging.racinesbykiyara.net
  - traefik.http.middlewares.minio-cors.headers.accesscontrolallowmethods=GET,HEAD,OPTIONS
  - traefik.http.middlewares.minio-cors.headers.accesscontrolallowheaders=*
  - traefik.http.middlewares.minio-cors.headers.accesscontrolmaxage=86400

Ajouter le domaine de chaque nouvel environnement à accesscontrolalloworiginlist.

Healthcheck MinIO

healthcheck:
  test: ["CMD", "mc", "ready", "local"]
  interval: 30s
  timeout: 20s
  retries: 3

⚠️ Utiliser mc ready local — pas mc admin info (requiert droits admin) ni curl (absent de l'image).


Dockerfile (multi-stage)

# Stage 1: deps — dépendances Node.js
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Stage 2: builder — build Next.js
FROM node:20-alpine AS builder
ARG NEXT_PUBLIC_PWA_ENABLED=true
ARG NEXT_PUBLIC_APP_URL
ENV NEXT_PUBLIC_PWA_ENABLED=$NEXT_PUBLIC_PWA_ENABLED
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
WORKDIR /app
COPY . .
RUN npm ci && NODE_ENV=production npm run build

# Supprimer le SW si PWA désactivée
RUN if [ "$NEXT_PUBLIC_PWA_ENABLED" != "true" ]; then \
      rm -f public/sw*.js public/register-sw*; \
    fi

# Stage 3: runner — image finale (standalone)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
COPY docker/entrypoint.sh ./entrypoint.sh
RUN chmod +x entrypoint.sh
EXPOSE 3000
ENTRYPOINT ["./entrypoint.sh"]

Build en production :

docker build \
  --build-arg NEXT_PUBLIC_PWA_ENABLED=true \
  --build-arg NEXT_PUBLIC_APP_URL=https://racines.kiyara.net \
  -t registry.id2real.net/racines-app:main .

Premier déploiement

1. Préparer le serveur

mkdir -p /docker-apps/racines
cd /docker-apps/racines

# Copier le docker-compose de l'environnement (fourni par Ansible)
cp ansible/docker-compose.integration.yaml docker-compose.yml

# Créer le fichier de variables
cp .env.example .app_env
nano .app_env  # Remplir toutes les valeurs (double quotes obligatoires)

2. Configurer MinIO

# Démarrer MinIO uniquement
docker compose up -d minio

# Configurer le bucket
docker exec -it racines-minio mc alias set local http://localhost:9000 root root_password
docker exec -it racines-minio mc mb local/audios --ignore-existing
docker exec -it racines-minio mc version enable local/audios
docker exec -it racines-minio mc anonymous set download local/audios

3. Initialiser la base de données

Les migrations sont dans le dossier migrations/ :

# Démarrer PostgreSQL uniquement
docker compose up -d postgres

# Appliquer les migrations
docker exec -i racines-postgres psql -U racines -d racines_db \
  < migrations/001_initial_schema.sql

docker exec -i racines-postgres psql -U racines -d racines_db \
  < migrations/002_create_admins.sql

docker exec -i racines-postgres psql -U racines -d racines_db \
  < migrations/004_quiz_results.sql

003_idb_card_id_index_note.sql est une note de documentation — ne pas l'exécuter.

4. Créer le premier compte admin

# Sur le serveur, une fois le container app démarré
docker exec -it racines-app npm run create-admin
# → Saisir email et mot de passe

Ou depuis un poste de développement avec accès à la base :

npm run create-admin

5. Lancer l'application

docker compose pull
docker compose up -d
docker compose logs -f app

6. Vérifier le démarrage

# Liveness probe Next.js
curl https://racines.kiyara.net/api/health/live
# → {"status":"ok"}

# Accès audio direct MinIO
curl -I https://racines-s3.id2real.net/audios/pul/uuid-item/audio.mp3
# → HTTP 200, Content-Type: audio/mpeg

# CORS audio
curl -I -H "Origin: https://racines.kiyara.net" \
  https://racines-s3.id2real.net/audios/pul/uuid-item/audio.mp3
# → Access-Control-Allow-Origin: https://racines.kiyara.net

Mise à jour

Via pipeline CI/CD (méthode normale)

git push origin develop   # → build + deploy staging automatique
git push origin main      # → build prod, puis deploy MANUEL

Mise à jour manuelle sur le serveur

cd /docker-apps/racines
docker compose pull app
docker compose up -d --force-recreate app

Après un changement de variables d'environnement uniquement

# Modifier .app_env sur le serveur, puis :
docker compose up -d --force-recreate app
# Aucun rebuild nécessaire — AUDIO_CDN_URL est lu au démarrage du container

PWA et déploiements

Impact d'un déploiement sur les utilisateurs offline

  • Les utilisateurs avec une langue téléchargée continuent à l'utiliser offline (ancienne version en cache).
  • Au prochain chargement en ligne, le nouveau SW s'installe → invalide les anciens caches (racines-cache-vXXracines-cache-vXX+1).
  • Un re-téléchargement de la langue met à jour le contenu offline.

Si AUDIO_CDN_URL change entre deux déploiements

Les audios précédemment mis en cache (clés = URLs absolues) deviennent orphelins. Les utilisateurs doivent re-télécharger la langue pour avoir les audios offline avec les nouvelles URLs.


Checklist post-déploiement

[ ] GET /api/health/live          → {"status":"ok"}
[ ] Interface admin accessible    → /admin → page de login
[ ] Login admin fonctionne        → identifiants valides acceptés
[ ] Liste des langues             → /api/languages → JSON valide
[ ] Audio direct MinIO            → curl -I [AUDIO_CDN_URL]/pul/uuid/audio.mp3 → 200
[ ] CORS MinIO                    → Access-Control-Allow-Origin présent
[ ] Pas de violation CSP          → DevTools → Console, aucune erreur CSP
[ ] Lecture audio sur une carte   → bouton 🔊 fonctionne
[ ] PWA installable               → icône d'installation visible dans le navigateur
[ ] Téléchargement offline        → télécharger une langue, mode avion, naviguer
[ ] Formulaire de contact         → soumission → email reçu + toast succès
[ ] Statistiques tracées          → actions dans l'app → dashboard admin mis à jour

Troubleshooting

Container refuse de démarrer : AUDIO_CDN_URL must be set

Cause : Variable absente du docker-compose.yml ou du .app_env.
Fix : Ajouter AUDIO_CDN_URL="https://[CDN]/audios" et relancer. Aucun rebuild.

Audios bloqués par CSP

Symptôme : Refused to connect to 'https://s3.example.com/...' because it violates CSP
Cause : AUDIO_CDN_DOMAIN incorrect ou absent (dérivation automatique a échoué).
Fix : Définir explicitement AUDIO_CDN_DOMAIN="s3.example.com" et relancer.

Erreur CORS sur les audios

Symptôme : Access-Control-Allow-Origin absent dans les headers de réponse MinIO.
Cause : Domaine de l'app absent du middleware Traefik minio-cors.
Fix : Ajouter le domaine à accesscontrolalloworiginlist dans les labels Traefik.

Healthcheck MinIO unhealthy : Access Denied

Cause : Commande mc admin info utilisée (requiert droits admin).
Fix : Utiliser mc ready local dans le healthcheck.

SW non mis à jour après déploiement

Symptôme : L'utilisateur voit l'ancienne version.
Fix : Hard refresh (Ctrl+Shift+R) ou DevTools → Application → Service Workers → Unregister.

Page offline → retour à la home à la place de la carte

Cause possible 1 : La page n'a pas été précachée (téléchargement interrompu).
Cause possible 2 : Le SW n'est pas encore controller (première installation).
Fix : Recharger en ligne pour activer le nouveau SW, puis re-télécharger la langue.


Étapes suivantes