mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
build(r0.13): Dockerfiles api/web + compose Dokploy + runbook — R0 prêt à déployer
- image api : multi-stage (pnpm deploy --legacy --prod), client Prisma régénéré dans l'arborescence déployée, binaryTargets explicites (debian/arm64 openssl-3), entrypoint migrate deploy → seed optionnel (SEED_ON_START, compilé dist/seed) → API ; non-root, healthcheck /health - image web : nginx alpine, statique Vite, proxy /api résolu À LA REQUÊTE (resolver Docker + variable — nginx démarre sans l'API), fallback SPA, cache immuable /assets, healthcheck IPv4 (127.0.0.1) - infra/docker-compose.dokploy.yml : 5 services préfixés siop2-, secrets exigés, seul siop2-web rejoint dokploy-network (API jamais exposée) - runbook docs/06-production/runbook-dokploy.md : topologie, profils d'environnement, checklist première prod, rollback, répétition locale - répétition locale validée de bout en bout : migrate+seed au boot, parcours demo-login → /users/me à travers nginx conteneurisé, conteneurs healthy ; le double verrou ADR-002 (DEMO_MODE sans I_KNOW en production) a refusé de démarrer — observé en situation réelle - prisma passe en dépendance de production (migrations au boot) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
98
docs/06-production/runbook-dokploy.md
Normal file
98
docs/06-production/runbook-dokploy.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Runbook — déploiement Dokploy
|
||||
|
||||
> **Rôle de ce document (playbook)** : la procédure REJOUABLE de mise en production
|
||||
> sur Dokploy (PaaS auto-hébergé du partenaire), écrite en R0.13 et répétée à chaque
|
||||
> release (principe « déployer tôt »). Statut : **répété en local, en attente des
|
||||
> accès au serveur du partenaire** pour la première exécution réelle.
|
||||
|
||||
## 1. Topologie
|
||||
|
||||
Un projet Dokploy = un service « Compose » pointant sur ce dépôt,
|
||||
fichier [`infra/docker-compose.dokploy.yml`](../../infra/docker-compose.dokploy.yml) :
|
||||
|
||||
```
|
||||
domaine (Traefik/Dokploy) ──▶ siop2-web :80 (nginx, statique)
|
||||
│ /api/** (préfixe retiré)
|
||||
▼
|
||||
siop2-api :3000 (NestJS)
|
||||
│── siop2-postgres :5432 (pgvector + PostGIS)
|
||||
│── siop2-redis :6379
|
||||
└── siop2-minio :9000
|
||||
```
|
||||
|
||||
- **Convention `siop2-`** (leçon v1) : le réseau Dokploy est partagé entre projets ;
|
||||
un service nommé `postgres` ou `api` collisionne. Tout est préfixé, sans exception.
|
||||
- Seul `siop2-web` rejoint `dokploy-network` (réseau externe du reverse-proxy) :
|
||||
l'API n'est **jamais** exposée directement, elle est servie via le proxy `/api` de nginx
|
||||
— même topologie que le proxy Vite en dev, donc mêmes chemins partout.
|
||||
- Les migrations Prisma s'appliquent **au démarrage du conteneur API**
|
||||
(`prisma migrate deploy`, idempotent) : la base est toujours au niveau du code déployé.
|
||||
|
||||
## 2. Variables d'environnement (à saisir dans Dokploy, jamais dans le dépôt)
|
||||
|
||||
| Variable | Obligatoire | Rôle |
|
||||
| --- | --- | --- |
|
||||
| `POSTGRES_PASSWORD` | ✅ | mot de passe PostgreSQL (générer : `openssl rand -hex 24`) |
|
||||
| `MINIO_ROOT_PASSWORD` | ✅ | secret MinIO |
|
||||
| `JWT_SECRET` | ✅ | signature des jetons (générer : `openssl rand -hex 32`) |
|
||||
| `POSTGRES_USER` / `POSTGRES_DB` | — | défaut `siop` / `siop` |
|
||||
| `DEMO_MODE` | — | **absent en production client.** `true` uniquement sur l'instance de démonstration |
|
||||
| `DEMO_MODE_I_KNOW` | — | double verrou ADR-002 : requis si `DEMO_MODE=true` en production, sinon l'API **refuse de démarrer** |
|
||||
| `SEED_ON_START` | — | `true` sur l'instance de démonstration : rôles, matrice et comptes démo au boot (idempotent) |
|
||||
| `SEED_DEMO_PASSWORD` | — | mot de passe commun des comptes démo (défaut `Demo!2026`) |
|
||||
|
||||
Profils types :
|
||||
|
||||
- **Production client** : les 4 dernières variables **absentes**. Les routes
|
||||
`/auth/demo-accounts` et `/auth/demo-login` n'existent pas (404, testé en CI).
|
||||
- **Instance de démonstration** : `DEMO_MODE=true`, `DEMO_MODE_I_KNOW=true`,
|
||||
`SEED_ON_START=true`.
|
||||
|
||||
## 3. Première mise en production (checklist)
|
||||
|
||||
1. Dokploy → **Create Project** `siop2` → **Compose** ; source = dépôt
|
||||
`siop-spelev/siop2`, branche `main`, fichier `infra/docker-compose.dokploy.yml`.
|
||||
2. Renseigner les variables d'environnement (§2) dans l'onglet Environment.
|
||||
3. **Deploy**. Dokploy construit les images (contexte = racine du dépôt,
|
||||
Dockerfiles `apps/api` et `apps/web`) puis démarre les 5 services ;
|
||||
l'API attend PostgreSQL/Redis/MinIO sains (healthchecks) et migre la base.
|
||||
4. Onglet Domains du service `siop2-web` : associer le domaine (port **80**,
|
||||
HTTPS Let's Encrypt géré par Dokploy).
|
||||
5. Vérifications :
|
||||
- `https://<domaine>/api/health` → `{"status":"ok", ...}` (les 3 services `up`) ;
|
||||
- production client : `https://<domaine>/api/auth/demo-accounts` → **404** ;
|
||||
- instance démo : écran de connexion avec les 7 comptes, bascule de rôle < 3 s.
|
||||
6. Consigner la mise en production dans `docs/journal/journal.md` (date, version, domaine).
|
||||
|
||||
## 4. Releases suivantes
|
||||
|
||||
`git push` sur `main` (CI verte exigée) → Dokploy **Deploy** (ou webhook auto-deploy).
|
||||
Les migrations de la release s'appliquent au démarrage ; en cas d'échec de migration,
|
||||
le conteneur s'arrête **sans** servir de trafic (l'ancienne version reste visible côté web).
|
||||
|
||||
## 5. Incidents & retours arrière
|
||||
|
||||
- **Rollback applicatif** : Dokploy → Deployments → redéployer le commit précédent.
|
||||
Les migrations Prisma étant additives (convention projet : jamais de `DROP` sans
|
||||
tâche dédiée validée), l'ancienne API fonctionne sur le schéma plus récent.
|
||||
- **API en échec au boot** : `docker logs siop2-api`. Causes classiques :
|
||||
`DEMO_MODE=true` sans `DEMO_MODE_I_KNOW` (verrou ADR-002 — c'est voulu),
|
||||
`DATABASE_URL` erronée, migration échouée.
|
||||
- **Sauvegardes** : volumes `pg-data` (base) et `minio-data` (fichiers).
|
||||
Sauvegarde PostgreSQL planifiée côté Dokploy (onglet Backups) — à configurer
|
||||
à la première mise en production réelle.
|
||||
|
||||
## 6. Répétition locale (sans Dokploy)
|
||||
|
||||
```bash
|
||||
# depuis la racine — construit et lance les 5 services comme en production
|
||||
docker network create dokploy-network 2>/dev/null || true
|
||||
POSTGRES_PASSWORD=repetition MINIO_ROOT_PASSWORD=repetition-minio \
|
||||
JWT_SECRET=$(openssl rand -hex 32) DEMO_MODE=true DEMO_MODE_I_KNOW=true SEED_ON_START=true \
|
||||
docker compose -f infra/docker-compose.dokploy.yml up -d --build
|
||||
# web : http://localhost via un port publié à ajouter ponctuellement, ou :
|
||||
docker compose -f infra/docker-compose.dokploy.yml exec siop2-web wget -qO- http://localhost/api/health
|
||||
```
|
||||
|
||||
C'est cette répétition (images construites, migrations au boot, parcours démo via
|
||||
nginx) qui a validé R0.13 en local — voir le journal du 15/07/2026.
|
||||
@@ -4,6 +4,32 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.13 : Dockerfiles + runbook Dokploy
|
||||
|
||||
**Actions**
|
||||
|
||||
- **Image API** (multi-stage, node:24-slim) : `pnpm deploy --legacy --prod`, client Prisma régénéré dans l'arborescence déployée, entrypoint `prisma migrate deploy` → seed optionnel (`SEED_ON_START=true`, compilé en `dist/seed`) → API ; utilisateur non-root, HEALTHCHECK `/health`. `prisma` passe en dépendance de production (migrations au boot).
|
||||
- **Image web** (nginx:alpine) : statique Vite + proxy `/api` → `${API_UPSTREAM}` (template envsubst) — même topologie que le proxy Vite de dev ; cache immuable sur `/assets`, `no-cache` sur `index.html`.
|
||||
- **`infra/docker-compose.dokploy.yml`** : 5 services préfixés `siop2-`, secrets exigés (`:?`), profils production client / instance démo documentés dans le fichier ; seul `siop2-web` rejoint `dokploy-network` (l'API n'est jamais exposée).
|
||||
- **Runbook** `docs/06-production/runbook-dokploy.md` : topologie, variables, checklist de première mise en production, releases suivantes, rollback, répétition locale.
|
||||
- **Répétition locale validée** : images construites, conteneurs lancés sur le réseau infra (migrate + seed au boot), parcours complet vérifié à travers nginx conteneurisé (`/api/health` tout `up`, demo-login → `/users/me`, fallback SPA).
|
||||
|
||||
**Leçons de la répétition (consignées pour le playbook)**
|
||||
|
||||
- **Prisma en conteneur** : `binaryTargets` explicites dans `schema.prisma` (`debian-openssl-3.0.x` x64 serveur + `linux-arm64-openssl-3.0.x` répétition Mac) — sinon mismatch de moteur au runtime.
|
||||
- **nginx** : upstream résolu **à la requête** (resolver `127.0.0.11` + variable) et non au boot — sinon nginx refuse de démarrer si l'API n'est pas encore là.
|
||||
- **Healthcheck alpine** : `127.0.0.1` et non `localhost` (busybox wget tente ::1, nginx écoute en IPv4).
|
||||
- **Le double verrou ADR-002 a été prouvé en vraie situation** : l'image (NODE_ENV=production) avec `DEMO_MODE=true` sans `DEMO_MODE_I_KNOW` **refuse de démarrer** — comportement observé, pas seulement testé.
|
||||
|
||||
**Décisions**
|
||||
|
||||
- Migrations **au démarrage du conteneur** (idempotentes) : la base suit toujours le code déployé ; un échec de migration arrête l'API sans servir de trafic.
|
||||
- Le web est l'unique service exposé (domaine → nginx → proxy interne `/api`) : mêmes chemins en dev et en prod, surface d'attaque minimale.
|
||||
|
||||
**Statut** : R0.13 prêt — la première exécution réelle attend les **accès au serveur du partenaire**. Reste pour clore R0 : recette avec le référent (revue pixel écrans ↔ maquettes).
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-15 — Pr. Daaif (+ Claude) — R0.12 (2/2) : ESLint + parcours Playwright — R0.12 CLOS
|
||||
|
||||
**Actions**
|
||||
|
||||
Reference in New Issue
Block a user