# Runbook — déploiement Dokploy

> **Rôle de ce document (playbook)** : la procédure REJOUABLE de mise en production
> sur Dokploy, écrite en R0.13 et répétée à chaque release (principe « déployer tôt »).
>
> **Instances** : démonstration/cours → `https://siop2.apps.enset.top` (Dokploy ENSET,
> projet créé le 15/07/2026) ; production client SPELEV → en attente des accès au
> serveur du partenaire (même procédure, profil d'environnement « production client »).

## 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) :

```text
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 — déploiement manuel depuis GitHub (checklist)

Dans le service **Compose** du projet Dokploy (déjà créé pour l'instance ENSET) :

1. **Onglet General — source GitHub** : Provider `GitHub` (application GitHub
   Dokploy autorisée sur `siop-spelev/siop2`, dépôt privé), Branch `main`,
   **Compose Path** `infra/docker-compose.dokploy.yml`.
2. **Onglet Environment** : coller les variables du §2. Pour l'instance de
   démonstration ENSET :

   ```env
   POSTGRES_PASSWORD=<openssl rand -hex 24>
   MINIO_ROOT_PASSWORD=<openssl rand -hex 24>
   JWT_SECRET=<openssl rand -hex 32>
   DEMO_MODE=true
   DEMO_MODE_I_KNOW=true
   SEED_ON_START=true
   ```

3. **Deploy** (bouton). Dokploy clone le dépôt, construit les images (contexte =
   racine, Dockerfiles `apps/api` et `apps/web` — ~5-10 min au premier build) puis
   démarre les 5 services ; l'API attend PostgreSQL/Redis/MinIO sains et migre la base.
   Suivi : onglet **Deployments** (logs de build) puis **Logs** par service.
4. **Onglet Domains** : Add Domain → Host `siop2.apps.enset.top`,
   **Service Name `siop2-web`**, **Container Port `80`**, HTTPS activé
   (certificat Let's Encrypt géré par Dokploy/Traefik).
5. Vérifications :
   - `https://siop2.apps.enset.top/api/health` → `{"status":"ok", ...}` (3 services `up`) ;
   - écran de connexion : 7 comptes démo, bascule de rôle < 3 s, bi-thème ;
   - production client (quand elle existera) : `/api/auth/demo-accounts` → **404**.
6. Consigner la mise en production dans `docs/journal/journal.md` (date, commit, domaine).

## 4. Releases suivantes — déploiement continu par GitHub Actions

Le job `deploy` de `.github/workflows/ci.yml` appelle le **webhook Dokploy**
à chaque push sur `main`, **uniquement si les 5 jobs (lint, contrat, api, web, e2e)
sont verts** — c'est la CI qui garde la porte, pas l'inverse. Mise en place (une fois) :

1. Dokploy → service compose → onglet **Deployments** → copier la **Webhook URL**.
2. Dépôt GitHub → Settings → Secrets and variables → Actions →
   **New repository secret** : nom `DOKPLOY_WEBHOOK_URL`, valeur = l'URL copiée.
   (Ou en CLI : `gh secret set DOKPLOY_WEBHOOK_URL`.)
3. C'est tout : le prochain push vert sur `main` déclenche le build + redéploiement
   côté Dokploy. Tant que le secret n'existe pas, le job `deploy` se termine en
   « skip » explicite sans faire échouer le pipeline.

> **Ne pas activer** l'« Auto Deploy » natif de Dokploy (webhook GitHub direct) :
> il déploierait aussi les commits dont la CI est rouge.

Les migrations de la release s'appliquent au démarrage du conteneur API ; 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.
