Remplace la chaîne GitHub Actions (jamais réellement branchée : aucun remote git n'existait) par une chaîne GitLab CI/CD, et comble un manque qu'elle avait déjà : le frontend n'était jusqu'ici jamais construit ni déployé en production. - `.gitlab-ci.yml` : tests (backend + frontend) à chaque push ; sur `main`, build des images backend et frontend (registre de conteneurs GitLab) puis déploiement SSH sur le VPS (pull, migration Alembic, up -d). - `frontend/Dockerfile.prod` + `frontend/nginx.conf` : build Vite statique servi par nginx (repli SPA sur index.html). `VITE_API_MODE`/ `VITE_EDITION_ID` sont des variables de build, passées en `--build-arg` depuis la variable CI/CD `VITE_EDITION_ID`. - `deploy/docker-compose.prod.yml` : ajout du service `frontend` ; `backend` et `frontend` ne publient plus que sur `127.0.0.1` (Caddy, déjà installé sur le serveur, devient l'unique porte d'entrée publique). - `deploy/Caddyfile.benevoles` : bloc de vhost à ajouter au Caddy existant (`/api/*` vers le backend avec préfixe retiré, le reste vers le frontend). - `deploy/README.md` : runbook complet (remote GitLab, variables CI/CD, préparation du VPS, création de l'édition de production avant le premier build frontend, vhost Caddy). - CLAUDE.md et README.md mis à jour en conséquence. Vérifié : `docker compose -f deploy/docker-compose.prod.yml config`, build réel de l'image frontend de prod, / et une route SPA (/grille) répondent 200 via nginx. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Déploiement
Le pipeline .gitlab-ci.yml se déclenche sur chaque
push :
- tests (backend :
ruff+pytest+alembic check; frontend :typecheck+lint+build+smoke) ; - sur
mainseulement : build des imagesbackendetfrontend, poussées sur le registre de conteneurs du projet GitLab ; - copie de
deploy/docker-compose.prod.ymlsur le VPS par SSH ; docker compose pull, migration Alembic,up -d.
Le VPS n'expose rien directement : un Caddy déjà installé sur le
serveur (service système, hors de ce dépôt) reçoit le trafic public sur
443 et reverse-proxy vers les deux conteneurs, qui n'écoutent que sur
127.0.0.1.
Étape 0 — pousser le dépôt sur GitLab
# Créer d'abord un projet vide sur GitLab (sans README ni .gitignore), puis :
git remote add origin git@gitlab.com:<namespace>/<projet>.git
git push -u origin main
Variables CI/CD à définir (Paramètres → CI/CD → Variables)
| Variable | Description |
|---|---|
SSH_HOST |
Hôte du VPS |
SSH_USER |
Utilisateur SSH |
SSH_KEY |
Clé privée SSH (format OpenSSH) — masquée, sur main seulement |
DEPLOY_PATH |
Dossier du projet sur le VPS (ex. /opt/benevoles) |
VITE_EDITION_ID |
UUID de l'édition de production — à ajouter après l'étape 3 |
CI_REGISTRY, CI_REGISTRY_USER, CI_REGISTRY_PASSWORD et
CI_REGISTRY_IMAGE sont fournies automatiquement par GitLab, rien à
configurer.
La clé publique correspondant à SSH_KEY doit être dans le
~/.ssh/authorized_keys de SSH_USER sur le VPS.
Étape 1 — préparation du VPS (une fois)
mkdir -p /opt/benevoles
cd /opt/benevoles
cat > .env <<'EOF'
POSTGRES_USER=benevoles
POSTGRES_PASSWORD=<mot de passe fort>
POSTGRES_DB=benevoles
JWT_SECRET=<secret fort ; openssl rand -hex 32>
PUBLIC_BASE_URL=https://benevoles.mon-domaine.fr
# Emails (récapitulatifs de planning + liens de connexion). Laisser
# EMAIL_ENABLED=false tant que le SMTP n'est pas prêt : les emails sont alors
# seulement journalisés.
EMAIL_ENABLED=true
SMTP_HOST=smtp.mon-domaine.fr
SMTP_PORT=587
SMTP_USER=no-reply@mon-domaine.fr
SMTP_PASSWORD=<mot de passe SMTP>
SMTP_STARTTLS=true
SMTP_SSL=false
SMTP_FROM=Festival <no-reply@mon-domaine.fr>
EOF
Le pipeline dépose docker-compose.prod.yml dans ce dossier et l'utilise tel
quel — il n'y a rien d'autre à y copier.
Étape 2 — premier déploiement (backend + base seuls)
Le frontend a besoin de l'UUID d'une édition pour être construit (voir plus bas) : le tout premier déploiement se fait donc en deux temps.
- Sur GitLab, lancer le pipeline sur
mainnormalement (Push → CI/CD → Pipelines, ou un simple commit). Le jobbuild-frontendéchouera ou produira une image sans édition tant queVITE_EDITION_IDn'existe pas : c'est attendu, on l'ignore pour l'instant. - Une fois
build-backendetdeploypassés,dbetbackendtournent sur le VPS.
Étape 3 — créer l'édition de production
ssh SSH_USER@SSH_HOST
cd /opt/benevoles
docker compose -f docker-compose.prod.yml run --rm backend \
uv run python -m app.bootstrap \
--name "Festival 2026" --start 2026-07-01 --end 2026-07-04 \
--email coordination@mon-domaine.fr --password <mot-de-passe-solide>
La commande affiche l'UUID de l'édition créée. Récupérer aussi celui-ci à tout moment avec :
docker compose -f docker-compose.prod.yml exec -T db \
psql -U benevoles -tAc "select id from edition order by created_at desc limit 1"
Étape 4 — brancher le frontend sur cette édition
Dans GitLab, ajouter la variable CI/CD VITE_EDITION_ID avec cet UUID, puis
relancer le pipeline sur main (bouton Retry sur le dernier pipeline, ou un
nouveau commit). Cette fois build-frontend construit une image qui pointe
sur la bonne édition, et deploy démarre le service frontend.
VITE_EDITION_ID ne change plus ensuite : elle ne se touche à nouveau que
pour une nouvelle édition (festival de l'année suivante) qu'on voudrait
publier sur ce même domaine.
Étape 5 — le vhost Caddy
Caddy tourne déjà sur ce serveur (service système) : ajouter le bloc
deploy/Caddyfile.benevoles à son Caddyfile (ou au
fichier qu'il importe pour les sites), avec le vrai nom de domaine, puis :
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Le DNS du domaine doit déjà pointer (enregistrement A/AAAA) vers ce VPS —
Caddy obtient et renouvelle seul le certificat HTTPS (Let's Encrypt) au
premier accès.
Vérifier ensuite que PUBLIC_BASE_URL dans le .env du VPS (étape 1)
correspond bien à https://<ce domaine> — c'est cette valeur qui est utilisée
dans les liens des emails envoyés aux bénévoles.
Déploiements suivants
Un simple push sur main suffit : tests → build → déploiement → migration →
redémarrage, sans aucune intervention sur le VPS ni sur Caddy.