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:
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
||||
**/node_modules
|
||||
**/dist
|
||||
**/coverage
|
||||
**/test-results
|
||||
**/playwright-report
|
||||
**/.turbo
|
||||
.git
|
||||
.github
|
||||
.env
|
||||
**/.env
|
||||
docs
|
||||
infra
|
||||
*.md
|
||||
@@ -38,5 +38,6 @@ pnpm + Turborepo. `apps/api` : NestJS, Prisma, PostgreSQL (pgvector + PostGIS),
|
||||
- ✅ **R0.10 `apps/api`** : NestJS + Prisma (migration `r0_identity`), guard JWT global fermé par défaut + `@Public()`, PermissionsGuard (matrice en base, cache 60 s), démo-login ADR-002 (module conditionnel, 404 sinon, double verrou prod), seed idempotent (7 rôles, 70 lignes de matrice, 7 comptes démo), `FileStorage`/health ; `packages/shared` (Zod) + `pnpm contract` → `docs/openapi.json` committée ; 19 tests Jest verts + smoke test build prod.
|
||||
- ✅ **R0.11 `apps/web`** : React 19 + Vite + Tailwind v4, `tokens.css` copié tel quel + classes extraites de la maquette validée, Manrope auto-hébergée ; connexion + sélecteur démo (masqué si 404 — une seule source de vérité : l'API), coquille sidebar (rail safran, écrans futurs marqués R1-R3) / topbar (thème, chip DÉMO, sélecteur de rôle), page /design ; client typé `schema.d.ts` généré depuis `docs/openapi.json` et committé ; vérifié en navigateur réel (bascule de rôle 101 ms, bi-thème, 0 erreur console).
|
||||
- ✅ **R0.12 — tests + CI** (5 jobs verts) : `lint` (ESLint 10 flat config racine, règle ADR-001 anti-import MinIO codée et vérifiée), `ci-contract` (régénération spec+client, diff bloquant), `api` (PostgreSQL 18 + Redis, migrate deploy, Jest couverture ≥ 70 % bloquante — mesurée 97,5 %), `web` (typecheck + vitest + build), `e2e` (Playwright : parcours démo, bascule < 3 s chronométrée, déconnexion). Badge au README.
|
||||
- 🔄 **R0 (2/2) — reprise ici** : R0.13 Dockerfiles (api, web) + runbook Dokploy (le déploiement réel attend les accès au serveur du partenaire), puis recette R0 avec le référent (revue pixel écrans ↔ maquettes).
|
||||
- ✅ **R0.13 — Dockerfiles + runbook Dokploy** : image api (multi-stage pnpm deploy, `prisma migrate deploy` au boot, seed optionnel `SEED_ON_START`, non-root, healthcheck ; `binaryTargets` explicites) ; image web (nginx, proxy `/api` résolu à la requête, fallback SPA, cache assets) ; `infra/docker-compose.dokploy.yml` (5 services `siop2-`, seul le web sur `dokploy-network`) ; runbook `docs/06-production/runbook-dokploy.md`. **Répétition locale complète validée** (migrate+seed au boot, parcours via nginx conteneurisé, double verrou ADR-002 observé). Le déploiement réel attend les accès au serveur du partenaire.
|
||||
- 🔄 **R0 — reste** : recette R0 avec le référent (revue pixel écrans ↔ maquettes), puis ouverture de R1 Référentiel.
|
||||
- Détail quotidien : `docs/journal/journal.md`. Dépôt : `siop-spelev/siop2` (privé), jalons R0→R5.
|
||||
|
||||
39
apps/api/Dockerfile
Normal file
39
apps/api/Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
# SIOP V2 — image API (NestJS + Prisma).
|
||||
# Contexte de build : RACINE du monorepo (docker build -f apps/api/Dockerfile .)
|
||||
# Étage 1 : dépendances + builds ; étage 2 : runtime minimal (node:24-slim).
|
||||
|
||||
FROM node:24-slim AS builder
|
||||
RUN corepack enable
|
||||
WORKDIR /repo
|
||||
|
||||
# Manifestes d'abord (cache de couche pour pnpm install)
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY packages/shared/package.json packages/shared/package.json
|
||||
COPY apps/api/package.json apps/api/package.json
|
||||
RUN pnpm install --frozen-lockfile --filter @siop/api --filter @siop/shared
|
||||
|
||||
COPY packages/shared packages/shared
|
||||
COPY apps/api apps/api
|
||||
RUN pnpm --filter @siop/shared build \
|
||||
&& pnpm --filter @siop/api prisma:generate \
|
||||
&& pnpm --filter @siop/api build \
|
||||
&& pnpm --filter @siop/api deploy --legacy --prod /out
|
||||
# Client Prisma regénéré DANS l'arborescence déployée (node_modules à plat)
|
||||
RUN cd /out && ./node_modules/.bin/prisma generate \
|
||||
&& cp -r /repo/apps/api/dist /out/dist
|
||||
|
||||
FROM node:24-slim
|
||||
# openssl : requis par les moteurs Prisma
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends openssl ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=builder --chown=node:node /out /app
|
||||
COPY --chown=node:node apps/api/docker-entrypoint.sh ./docker-entrypoint.sh
|
||||
RUN chmod +x docker-entrypoint.sh
|
||||
USER node
|
||||
EXPOSE 3000
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 \
|
||||
CMD node -e "fetch('http://localhost:3000/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
15
apps/api/docker-entrypoint.sh
Normal file
15
apps/api/docker-entrypoint.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
# SIOP V2 — démarrage du conteneur API :
|
||||
# 1. migrations Prisma (idempotentes — la base est toujours à jour du code) ;
|
||||
# 2. seed optionnel (SEED_ON_START=true : dev/démo — idempotent, jamais
|
||||
# destructif : il n'écrase ni matrice existante ni mot de passe) ;
|
||||
# 3. API. Le double verrou DEMO_MODE (ADR-002) est vérifié au boot.
|
||||
set -e
|
||||
|
||||
./node_modules/.bin/prisma migrate deploy
|
||||
|
||||
if [ "$SEED_ON_START" = "true" ]; then
|
||||
node dist/seed/seed.js
|
||||
fi
|
||||
|
||||
exec node dist/main.js
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"description": "SIOP V2 — API NestJS (auth fermée par défaut, matrice de permissions en base, démo-login ADR-002)",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"build": "nest build && tsc -p tsconfig.seed.json",
|
||||
"dev": "nest start --watch",
|
||||
"start": "node dist/main.js",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
@@ -29,6 +29,7 @@
|
||||
"dotenv": "^16.4.5",
|
||||
"ioredis": "^5.4.0",
|
||||
"minio": "^8.0.0",
|
||||
"prisma": "^6.8.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^4.0.0"
|
||||
@@ -41,7 +42,6 @@
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"jest": "^29.7.0",
|
||||
"prisma": "^6.8.0",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.3.0",
|
||||
"tsx": "^4.19.0",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
// Cibles explicites : poste de dev (native) + conteneur node:24-slim
|
||||
// (OpenSSL 3) en x64 (serveur partenaire) et arm64 (répétition locale).
|
||||
binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
|
||||
9
apps/api/tsconfig.seed.json
Normal file
9
apps/api/tsconfig.seed.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist/seed",
|
||||
"rootDir": "prisma",
|
||||
"incremental": false
|
||||
},
|
||||
"include": ["prisma/seed.ts"]
|
||||
}
|
||||
25
apps/web/Dockerfile
Normal file
25
apps/web/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
# SIOP V2 — image web (React statique derrière nginx).
|
||||
# Contexte de build : RACINE du monorepo (docker build -f apps/web/Dockerfile .)
|
||||
|
||||
FROM node:24-slim AS builder
|
||||
RUN corepack enable
|
||||
WORKDIR /repo
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY packages/shared/package.json packages/shared/package.json
|
||||
COPY apps/web/package.json apps/web/package.json
|
||||
RUN pnpm install --frozen-lockfile --filter @siop/web --filter @siop/shared
|
||||
|
||||
COPY packages/shared packages/shared
|
||||
COPY apps/web apps/web
|
||||
RUN pnpm --filter @siop/shared build && pnpm --filter @siop/web build
|
||||
|
||||
FROM nginx:1.29-alpine
|
||||
# Cible du proxy /api — surchargée par le compose (réseau interne)
|
||||
ENV API_UPSTREAM=siop2-api:3000
|
||||
COPY apps/web/nginx/default.conf.template /etc/nginx/templates/default.conf.template
|
||||
COPY --from=builder /repo/apps/web/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
# 127.0.0.1 et non localhost : busybox wget tente ::1 alors que nginx écoute en IPv4
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD wget -q -O /dev/null http://127.0.0.1/ || exit 1
|
||||
36
apps/web/nginx/default.conf.template
Normal file
36
apps/web/nginx/default.conf.template
Normal file
@@ -0,0 +1,36 @@
|
||||
# SIOP V2 — nginx du conteneur web : statique + proxy /api → API interne.
|
||||
# ${API_UPSTREAM} est substitué au démarrage (envsubst intégré à l'image nginx) ;
|
||||
# même topologie que le proxy Vite en dev : /api est RETIRÉ avant transmission.
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# DNS Docker interrogé À LA REQUÊTE (et pas une fois au boot) : nginx
|
||||
# démarre et sert le statique même si l'API n'est pas encore résolvable.
|
||||
resolver 127.0.0.11 valid=10s ipv6=off;
|
||||
set $api_upstream http://${API_UPSTREAM};
|
||||
|
||||
location /api/ {
|
||||
rewrite ^/api/(.*)$ /$1 break;
|
||||
proxy_pass $api_upstream;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Assets fingerprintés par Vite : cache long immuable
|
||||
location /assets/ {
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# SPA : toute route applicative sert index.html (jamais mis en cache)
|
||||
location / {
|
||||
add_header Cache-Control "no-cache";
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
}
|
||||
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**
|
||||
|
||||
110
infra/docker-compose.dokploy.yml
Normal file
110
infra/docker-compose.dokploy.yml
Normal file
@@ -0,0 +1,110 @@
|
||||
# SIOP V2 — compose de PRODUCTION (Dokploy). Voir docs/06-production/runbook-dokploy.md.
|
||||
# CONVENTION (leçon v1) : tout service/conteneur est préfixé « siop2- » — le
|
||||
# réseau Dokploy est partagé entre projets, un nom générique collisionne.
|
||||
# Les secrets (${...}) vivent dans l'environnement du projet Dokploy, jamais ici.
|
||||
name: siop2
|
||||
|
||||
services:
|
||||
siop2-postgres:
|
||||
container_name: siop2-postgres
|
||||
build: ./postgres
|
||||
image: siop2/postgres:18-pgvector-postgis
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-siop}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?définir POSTGRES_PASSWORD dans Dokploy}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-siop}
|
||||
volumes:
|
||||
- pg-data:/var/lib/postgresql
|
||||
- ./postgres/init.sql:/docker-entrypoint-initdb.d/10-extensions.sql:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siop} -d ${POSTGRES_DB:-siop}"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
siop2-redis:
|
||||
container_name: siop2-redis
|
||||
image: redis:7.4.9-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
siop2-minio:
|
||||
container_name: siop2-minio
|
||||
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
|
||||
restart: unless-stopped
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-siop}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?définir MINIO_ROOT_PASSWORD dans Dokploy}
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
siop2-api:
|
||||
container_name: siop2-api
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: apps/api/Dockerfile
|
||||
image: siop2/api:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-siop}:${POSTGRES_PASSWORD}@siop2-postgres:5432/${POSTGRES_DB:-siop}
|
||||
REDIS_URL: redis://siop2-redis:6379
|
||||
JWT_SECRET: ${JWT_SECRET:?définir JWT_SECRET dans Dokploy}
|
||||
MINIO_ENDPOINT: siop2-minio
|
||||
MINIO_PORT: 9000
|
||||
MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:-siop}
|
||||
MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
|
||||
# ADR-002 — production client : les 3 variables restent ABSENTES.
|
||||
# Instance de démonstration publique UNIQUEMENT :
|
||||
# DEMO_MODE=true + DEMO_MODE_I_KNOW=true (double verrou) + SEED_ON_START=true
|
||||
DEMO_MODE: ${DEMO_MODE:-}
|
||||
DEMO_MODE_I_KNOW: ${DEMO_MODE_I_KNOW:-}
|
||||
SEED_ON_START: ${SEED_ON_START:-}
|
||||
SEED_DEMO_PASSWORD: ${SEED_DEMO_PASSWORD:-}
|
||||
depends_on:
|
||||
siop2-postgres:
|
||||
condition: service_healthy
|
||||
siop2-redis:
|
||||
condition: service_healthy
|
||||
siop2-minio:
|
||||
condition: service_healthy
|
||||
|
||||
siop2-web:
|
||||
container_name: siop2-web
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: apps/web/Dockerfile
|
||||
image: siop2/web:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
API_UPSTREAM: siop2-api:3000
|
||||
depends_on:
|
||||
- siop2-api
|
||||
networks:
|
||||
- default
|
||||
- dokploy-network
|
||||
|
||||
volumes:
|
||||
pg-data:
|
||||
redis-data:
|
||||
minio-data:
|
||||
|
||||
networks:
|
||||
# Réseau du reverse-proxy Dokploy (Traefik) : seul siop2-web y est exposé,
|
||||
# le domaine pointe sur lui (port 80) ; l'API reste interne (proxy /api).
|
||||
dokploy-network:
|
||||
external: true
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -56,6 +56,9 @@ importers:
|
||||
minio:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.7
|
||||
prisma:
|
||||
specifier: ^6.8.0
|
||||
version: 6.19.3(typescript@5.9.3)
|
||||
reflect-metadata:
|
||||
specifier: ^0.2.2
|
||||
version: 0.2.2
|
||||
@@ -87,9 +90,6 @@ importers:
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@24.13.3)
|
||||
prisma:
|
||||
specifier: ^6.8.0
|
||||
version: 6.19.3(typescript@5.9.3)
|
||||
supertest:
|
||||
specifier: ^7.0.0
|
||||
version: 7.2.2(supports-color@10.2.2)
|
||||
|
||||
Reference in New Issue
Block a user