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:
pr-daaif
2026-07-15 22:58:42 +01:00
parent 36de93bb7e
commit ce24d9c32b
13 changed files with 381 additions and 6 deletions

39
apps/api/Dockerfile Normal file
View 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"]

View 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

View File

@@ -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",

View File

@@ -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 {

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist/seed",
"rootDir": "prisma",
"incremental": false
},
"include": ["prisma/seed.ts"]
}