Déploiement : CI/CD GitLab + image frontend de prod + Caddy
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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
367dfe42eb
commit
d5f67b2701
@@ -1,101 +0,0 @@
|
||||
name: Déploiement production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: deploy-production
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: benevoles
|
||||
POSTGRES_PASSWORD: benevoles
|
||||
POSTGRES_DB: benevoles_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U benevoles"
|
||||
--health-interval 5s --health-timeout 5s --health-retries 10
|
||||
env:
|
||||
TEST_DATABASE_URL: postgresql+psycopg://benevoles:benevoles@localhost:5432/benevoles_test
|
||||
DATABASE_URL: postgresql+psycopg://benevoles:benevoles@localhost:5432/benevoles_test
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
version: "0.5.11"
|
||||
- name: Installer les dépendances
|
||||
working-directory: backend
|
||||
run: uv sync
|
||||
- name: Lint
|
||||
working-directory: backend
|
||||
run: uv run ruff check .
|
||||
- name: Tests
|
||||
working-directory: backend
|
||||
run: uv run pytest
|
||||
|
||||
build:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}-backend
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=sha,prefix=sha-
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./backend
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Copier la configuration de déploiement
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.SSH_HOST }}
|
||||
username: ${{ secrets.SSH_USER }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
source: deploy/docker-compose.prod.yml
|
||||
target: ${{ secrets.DEPLOY_PATH }}
|
||||
strip_components: 1
|
||||
- name: Redéployer
|
||||
uses: appleboy/ssh-action@v1.2.0
|
||||
with:
|
||||
host: ${{ secrets.SSH_HOST }}
|
||||
username: ${{ secrets.SSH_USER }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
script: |
|
||||
set -e
|
||||
cd "${{ secrets.DEPLOY_PATH }}"
|
||||
export BACKEND_IMAGE="ghcr.io/${{ github.repository }}-backend:sha-${{ github.sha }}"
|
||||
echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
docker image prune -f
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
# CI/CD : tests à chaque push, puis sur `main` seulement, build des images
|
||||
# (registre de conteneurs GitLab) et déploiement SSH sur le VPS.
|
||||
#
|
||||
# Variables CI/CD à définir (Paramètres → CI/CD → Variables du projet) :
|
||||
# SSH_HOST hôte du VPS
|
||||
# SSH_USER utilisateur SSH
|
||||
# SSH_KEY clé privée SSH (format OpenSSH), variable « protégée »
|
||||
# + « masquée », type fichier ou variable multi-lignes
|
||||
# DEPLOY_PATH dossier du projet sur le VPS (ex. /opt/benevoles)
|
||||
# VITE_EDITION_ID UUID de l'édition de production, à renseigner APRÈS la
|
||||
# toute première mise en service (voir deploy/README.md) —
|
||||
# le frontend fige cette valeur au moment du build.
|
||||
#
|
||||
# `CI_REGISTRY`, `CI_REGISTRY_USER`, `CI_REGISTRY_PASSWORD` et
|
||||
# `CI_REGISTRY_IMAGE` sont fournis automatiquement par GitLab.
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
BACKEND_IMAGE: $CI_REGISTRY_IMAGE/backend
|
||||
FRONTEND_IMAGE: $CI_REGISTRY_IMAGE/frontend
|
||||
|
||||
backend-test:
|
||||
stage: test
|
||||
image: python:3.12-slim
|
||||
services:
|
||||
- name: postgres:16-alpine
|
||||
alias: postgres
|
||||
variables:
|
||||
POSTGRES_USER: benevoles
|
||||
POSTGRES_PASSWORD: benevoles
|
||||
POSTGRES_DB: benevoles_test
|
||||
TEST_DATABASE_URL: postgresql+psycopg://benevoles:benevoles@postgres:5432/benevoles_test
|
||||
DATABASE_URL: postgresql+psycopg://benevoles:benevoles@postgres:5432/benevoles_test
|
||||
before_script:
|
||||
- pip install --no-cache-dir uv
|
||||
- cd backend
|
||||
- uv sync
|
||||
script:
|
||||
- uv run ruff check .
|
||||
- uv run alembic upgrade head
|
||||
- uv run alembic check
|
||||
- uv run pytest -q
|
||||
|
||||
frontend-test:
|
||||
stage: test
|
||||
image: node:20-alpine
|
||||
before_script:
|
||||
- cd frontend
|
||||
- npm ci
|
||||
script:
|
||||
- npm run typecheck
|
||||
- npm run lint
|
||||
- npm run build
|
||||
- npm run smoke
|
||||
|
||||
build-backend:
|
||||
stage: build
|
||||
image: docker:27
|
||||
services:
|
||||
- docker:27-dind
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||
script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
|
||||
- docker build -t "$BACKEND_IMAGE:$CI_COMMIT_SHORT_SHA" -t "$BACKEND_IMAGE:latest" backend
|
||||
- docker push "$BACKEND_IMAGE:$CI_COMMIT_SHORT_SHA"
|
||||
- docker push "$BACKEND_IMAGE:latest"
|
||||
|
||||
build-frontend:
|
||||
stage: build
|
||||
image: docker:27
|
||||
services:
|
||||
- docker:27-dind
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||
script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
|
||||
- >
|
||||
docker build
|
||||
--build-arg VITE_API_MODE=http
|
||||
--build-arg VITE_EDITION_ID="$VITE_EDITION_ID"
|
||||
-f frontend/Dockerfile.prod
|
||||
-t "$FRONTEND_IMAGE:$CI_COMMIT_SHORT_SHA" -t "$FRONTEND_IMAGE:latest"
|
||||
frontend
|
||||
- docker push "$FRONTEND_IMAGE:$CI_COMMIT_SHORT_SHA"
|
||||
- docker push "$FRONTEND_IMAGE:latest"
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
image: alpine:3.20
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||
before_script:
|
||||
- apk add --no-cache openssh-client rsync
|
||||
- eval "$(ssh-agent -s)"
|
||||
- echo "$SSH_KEY" | tr -d '\r' | ssh-add -
|
||||
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
- ssh-keyscan -H "$SSH_HOST" >> ~/.ssh/known_hosts
|
||||
script:
|
||||
- rsync -az deploy/docker-compose.prod.yml "$SSH_USER@$SSH_HOST:$DEPLOY_PATH/docker-compose.prod.yml"
|
||||
- |
|
||||
ssh "$SSH_USER@$SSH_HOST" sh -s <<EOF
|
||||
set -e
|
||||
cd "$DEPLOY_PATH"
|
||||
echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
|
||||
export BACKEND_IMAGE="$BACKEND_IMAGE:$CI_COMMIT_SHORT_SHA"
|
||||
export FRONTEND_IMAGE="$FRONTEND_IMAGE:$CI_COMMIT_SHORT_SHA"
|
||||
docker compose -f docker-compose.prod.yml pull
|
||||
docker compose -f docker-compose.prod.yml run --rm backend uv run alembic upgrade head
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
docker image prune -f
|
||||
EOF
|
||||
@@ -335,10 +335,11 @@ fonctions d'IA. *(L'envoi d'emails par SMTP est en place — voir § 2.)*
|
||||
├── README.md
|
||||
├── docker-compose.yml # dev : db + backend (hot reload)
|
||||
├── .env.example
|
||||
├── .github/workflows/deploy.yml # CI/CD : build → GHCR → SSH sur le VPS
|
||||
├── .gitlab-ci.yml # CI/CD : tests → build (registre GitLab) → SSH sur le VPS
|
||||
├── deploy/
|
||||
│ ├── docker-compose.prod.yml
|
||||
│ └── README.md # secrets requis, setup VPS
|
||||
│ ├── docker-compose.prod.yml # db + backend + frontend, ports en 127.0.0.1
|
||||
│ ├── Caddyfile.benevoles # bloc à ajouter au Caddy du VPS (reverse proxy)
|
||||
│ └── README.md # variables CI/CD requises, mise en service du VPS
|
||||
├── docker/postgres-init/ # création de la base de test au 1er boot
|
||||
├── backend/
|
||||
│ ├── pyproject.toml # géré par uv
|
||||
@@ -417,15 +418,26 @@ Aucune dépendance locale requise hors Docker.
|
||||
|
||||
## 9. Déploiement
|
||||
|
||||
- Dépôt GitHub. VPS avec Docker.
|
||||
- `.github/workflows/deploy.yml` : sur chaque push `main` → build de l'image
|
||||
backend → push sur `ghcr.io/<owner>/<repo>-backend` → SSH sur le VPS →
|
||||
`docker compose -f docker-compose.prod.yml pull && up -d`.
|
||||
- Secrets requis (voir `deploy/README.md`) : `SSH_HOST`, `SSH_USER`,
|
||||
`SSH_KEY`, `DEPLOY_PATH`, `GHCR_TOKEN`.
|
||||
- Dépôt GitLab. VPS avec Docker + un Caddy déjà installé en service système
|
||||
(hors de ce dépôt), qui reçoit tout le trafic public en HTTPS et fait
|
||||
reverse proxy vers les conteneurs.
|
||||
- `.gitlab-ci.yml` : à chaque push, tests (backend + frontend) ; sur `main`
|
||||
seulement, build des images `backend` et `frontend` (registre de conteneurs
|
||||
du projet GitLab) puis SSH sur le VPS → copie de
|
||||
`deploy/docker-compose.prod.yml` → `pull` → migration Alembic → `up -d`.
|
||||
- Le frontend est un **build statique** (`frontend/Dockerfile.prod`, servi par
|
||||
nginx) : `VITE_API_MODE`/`VITE_EDITION_ID` sont figées au moment du build,
|
||||
pas à l'exécution — l'édition de production doit donc exister avant la
|
||||
première construction de cette image (voir `deploy/README.md`).
|
||||
- Variables CI/CD requises (voir `deploy/README.md`) : `SSH_HOST`, `SSH_USER`,
|
||||
`SSH_KEY`, `DEPLOY_PATH`, `VITE_EDITION_ID`.
|
||||
- Le VPS porte un fichier `.env` (non versionné) avec `POSTGRES_USER`,
|
||||
`POSTGRES_PASSWORD`, `POSTGRES_DB`, `JWT_SECRET`, `PUBLIC_BASE_URL` et les
|
||||
variables `EMAIL_ENABLED` / `SMTP_*` (voir `deploy/README.md`).
|
||||
- `backend` et `frontend` n'écoutent que sur `127.0.0.1` (voir
|
||||
`docker-compose.prod.yml`) : `deploy/Caddyfile.benevoles` est le bloc à
|
||||
ajouter au Caddyfile du serveur pour les exposer publiquement (`/api/*` vers
|
||||
le backend avec le préfixe retiré, le reste vers le frontend).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -47,5 +47,5 @@ démarrage du conteneur PostgreSQL.
|
||||
|--------------|-------------------------------------------------|
|
||||
| `backend/` | API FastAPI, modèle SQLModel, migrations Alembic |
|
||||
| `frontend/` | Interface React + Vite + TS + Tailwind (service `frontend`) |
|
||||
| `deploy/` | Configuration de déploiement production |
|
||||
| `.github/` | Chaîne CI/CD (build → GHCR → SSH sur le VPS) |
|
||||
| `deploy/` | Configuration de déploiement production (compose, Caddy, runbook) |
|
||||
| `.gitlab-ci.yml` | Chaîne CI/CD (tests → build → SSH sur le VPS) |
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Bloc à ajouter au Caddyfile existant du serveur (ex. /etc/caddy/Caddyfile,
|
||||
# ou un fichier importé par lui — voir deploy/README.md). Remplacer le nom de
|
||||
# domaine par le vôtre. Caddy gère seul le certificat HTTPS (Let's Encrypt).
|
||||
#
|
||||
# Le frontend (nginx, port 8080) et le backend (port 8000) écoutent tous les
|
||||
# deux sur 127.0.0.1 uniquement (voir docker-compose.prod.yml) : ce vhost est
|
||||
# leur unique porte d'entrée publique.
|
||||
|
||||
benevoles.mon-domaine.fr {
|
||||
encode zstd gzip
|
||||
|
||||
# API : même préfixe `/api` que le proxy Vite en développement, retiré
|
||||
# avant de transmettre au backend (qui expose ses routes sous
|
||||
# `/editions/...`, pas `/api`).
|
||||
handle /api/* {
|
||||
uri strip_prefix /api
|
||||
reverse_proxy 127.0.0.1:8000
|
||||
}
|
||||
|
||||
# Reste de l'application : le frontend statique (nginx).
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:8080
|
||||
}
|
||||
}
|
||||
+108
-20
@@ -1,24 +1,46 @@
|
||||
# Déploiement
|
||||
|
||||
Le workflow [`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml)
|
||||
se déclenche sur **chaque push sur `main`** :
|
||||
Le pipeline [`.gitlab-ci.yml`](../.gitlab-ci.yml) se déclenche sur **chaque
|
||||
push** :
|
||||
|
||||
1. build de l'image `backend` ;
|
||||
2. push sur `ghcr.io/<owner>/<repo>-backend` (tags `latest` et `sha-<commit>`) ;
|
||||
1. tests (backend : `ruff` + `pytest` + `alembic check` ; frontend :
|
||||
`typecheck` + `lint` + `build` + `smoke`) ;
|
||||
2. sur `main` seulement : build des images `backend` et `frontend`, poussées
|
||||
sur le registre de conteneurs du projet GitLab ;
|
||||
3. copie de `deploy/docker-compose.prod.yml` sur le VPS par SSH ;
|
||||
4. `docker compose -f docker-compose.prod.yml pull && up -d`.
|
||||
4. `docker compose pull`, migration Alembic, `up -d`.
|
||||
|
||||
## Secrets GitHub Actions requis
|
||||
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`.
|
||||
|
||||
| Secret | Description |
|
||||
|----------------|----------------------------------------------------------------|
|
||||
| `SSH_HOST` | Hôte du VPS |
|
||||
| `SSH_USER` | Utilisateur SSH |
|
||||
| `SSH_KEY` | Clé privée SSH (format OpenSSH) |
|
||||
| `DEPLOY_PATH` | Dossier du projet sur le VPS (ex. `/opt/benevoles`) |
|
||||
| `GHCR_TOKEN` | PAT avec scope `read:packages`, pour que le VPS tire l'image |
|
||||
## Étape 0 — pousser le dépôt sur GitLab
|
||||
|
||||
## Préparation du VPS (une fois)
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/benevoles
|
||||
@@ -28,21 +50,87 @@ 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-festival.fr
|
||||
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-festival.fr
|
||||
SMTP_HOST=smtp.mon-domaine.fr
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=no-reply@mon-festival.fr
|
||||
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-festival.fr>
|
||||
SMTP_FROM=Festival <no-reply@mon-domaine.fr>
|
||||
EOF
|
||||
```
|
||||
|
||||
Le workflow dépose `docker-compose.prod.yml` dans ce dossier et l'utilise tel
|
||||
quel.
|
||||
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.
|
||||
|
||||
1. Sur GitLab, lancer le pipeline sur `main` normalement (Push → CI/CD →
|
||||
Pipelines, ou un simple commit). Le job `build-frontend` échouera ou
|
||||
produira une image sans édition tant que `VITE_EDITION_ID` n'existe pas :
|
||||
c'est attendu, on l'ignore pour l'instant.
|
||||
2. Une fois `build-backend` et `deploy` passés, `db` et `backend` tournent
|
||||
sur le VPS.
|
||||
|
||||
## Étape 3 — créer l'édition de production
|
||||
|
||||
```bash
|
||||
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 :
|
||||
|
||||
```bash
|
||||
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`](./Caddyfile.benevoles) à son `Caddyfile` (ou au
|
||||
fichier qu'il importe pour les sites), avec le vrai nom de domaine, puis :
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
@@ -3,7 +3,12 @@ name: benevoles
|
||||
# Déployé sur le VPS. Les variables sont lues depuis un fichier .env situé à
|
||||
# côté de ce fichier (non versionné) : POSTGRES_USER, POSTGRES_PASSWORD,
|
||||
# POSTGRES_DB, JWT_SECRET, PUBLIC_BASE_URL et les variables SMTP_* / EMAIL_ENABLED.
|
||||
# BACKEND_IMAGE est exporté par le workflow de déploiement.
|
||||
# BACKEND_IMAGE / FRONTEND_IMAGE sont exportées par le pipeline de déploiement
|
||||
# (.gitlab-ci.yml).
|
||||
#
|
||||
# Les deux services ne publient que sur `127.0.0.1` : c'est le Caddy du VPS
|
||||
# (hors de ce compose, voir deploy/README.md) qui reçoit le trafic public et
|
||||
# reverse-proxy vers ces ports.
|
||||
|
||||
services:
|
||||
db:
|
||||
@@ -22,7 +27,7 @@ services:
|
||||
retries: 10
|
||||
|
||||
backend:
|
||||
image: ${BACKEND_IMAGE:-ghcr.io/OWNER/REPO-backend:latest}
|
||||
image: ${BACKEND_IMAGE:-registry.gitlab.com/OWNER/REPO/backend:latest}
|
||||
environment:
|
||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
ENVIRONMENT: production
|
||||
@@ -41,7 +46,15 @@ services:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- "127.0.0.1:8000:8000"
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
image: ${FRONTEND_IMAGE:-registry.gitlab.com/OWNER/REPO/frontend:latest}
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
- "127.0.0.1:8080:80"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Image de production : build Vite statique, servi par nginx.
|
||||
#
|
||||
# `VITE_API_MODE` et `VITE_EDITION_ID` sont des variables **de build** (Vite
|
||||
# les fige dans le bundle, il n'y a rien à configurer à l'exécution) :
|
||||
# l'édition ciblée doit donc déjà exister côté backend avant de construire
|
||||
# cette image. Voir deploy/README.md pour la procédure de première mise en
|
||||
# service.
|
||||
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG VITE_API_MODE=http
|
||||
ARG VITE_EDITION_ID
|
||||
ENV VITE_API_MODE=$VITE_API_MODE
|
||||
ENV VITE_EDITION_ID=$VITE_EDITION_ID
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,18 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Fichiers hachés par Vite (contenu du nom = jamais modifiés une fois
|
||||
# publiés) : cache long côté navigateur.
|
||||
location /assets/ {
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
}
|
||||
|
||||
# Application monopage (react-router-dom) : toute route inconnue du
|
||||
# serveur retombe sur index.html, le routeur prend le relais côté client.
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user