chore init

This commit is contained in:
devcodetools committed 2026-09-16 21:34:09 +02:00
1 parent 1b7406b1d3
commit a0d312a884
60 files changed
+5046 -125

No files matched your search

+205
View File
@@ -0,0 +1,205 @@
# 08 — Déploiement conteneurisé (Docker / DockerHub)
> Ce document est la **spécification** de la dockerisation de CV-Maker. Les fichiers `Dockerfile`, `docker-compose.yml` et `.dockerignore` seront créés lors de l'implémentation, **conformément** au contenu détaillé ci-dessous.
## 1. Objectif
1. Packager l'application Next.js dans une **image Docker** de production reproductible.
2. Pouvoir **pousser cette image sur Docker Hub** (`docker push`) pour la distribuer et la déployer n'importe où (`docker run` / `docker compose up`).
3. Maîtriser la taille de l'image : seule la **sortie standalone** de Next.js est embarquée (pas le code source ni les dépendances de build).
## 2. Architecture de l'image (multi-stage)
```
node:20-alpine
│
├─ stage [deps] pnpm install --frozen-lockfile (lockfile seul)
│
├─ stage [builder] sources + node_modules → pnpm build
│ → produit : .next/standalone, .next/static, public/
│
└─ stage [runner] .next/standalone + .next/static + public/
→ node server.js (HOSTNAME=0.0.0.0 PORT=3000)
```
| Stage | Image | Contenu | Rôle |
| --- | --- | --- | --- |
| `deps` | `node:20-alpine` | `package.json` + `pnpm-lock.yaml` | Télécharge et fige les dépendances (cache de build efficace) |
| `builder` | `node:20-alpine` | sources + `node_modules` | Compile `pnpm build` ; produit l'artefact standalone |
| `runner` | `node:20-alpine` | `.next/standalone`, `.next/static`, `public/` | Exécution seule : image légère, aucun source ni tooling |
### Pourquoi `output: "standalone"` ?
Next.js trace à la compilation toutes les dépendances réellement utilisées et génère un `server.js` autonome dans `.next/standalone`. Cela permet une image de production **sans node_modules complète** (économie de l'ordre de ×5 à ×10 en taille).
### Pourquoi `node:20-alpine` ?
Base minimale (≈50 Mo) pour la phase runtime, cohérente avec le prérequis Node 20 du projet. Compatible glibc/musl — aucune dépendance native dans ce projet (marked/dompurify/lucide sont des packages JS purs).
## 3. Fichiers à créer — spécifications
### 3.1 `Dockerfile`
```dockerfile
# ── Étape de dépendances ────────────────────────────────────────
FROM node:20-alpine AS deps
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# ── Étape de build ─────────────────────────────────────────────
FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build de production ; la PWA (Serwist) génère public/sw.js ici
RUN pnpm build
# ── Étape d'exécution ──────────────────────────────────────────
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
# .next/standalone auto-contain server.js, package.json…
COPY --from=builder /app/.next/standalone ./
# Assets statiques (CSS/JS optimisés) + public/ (manifest, icônes, sw.js)
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
```
Notes d'intégration :
- `corepack enable` active pnpm à partir du champ `packageManager` du `package.json` (écrit par `create-next-app`) → version de pnpm **figée**.
- `pnpm install --frozen-lockfile` : échec si le lockfile n'est pas à jour (déploiement déterministe).
- `public/` contient génère tout ce qui est généré pendant le build (ex. `sw.js` de Serwist) mais est copié depuis le builder → toujours cohérent.
### 3.2 `.dockerignore`
```gitignore
node_modules
.next
out
.git
.gitignore
docs
*.md
Dockerfile
docker-compose.yml
.dockerignore
*.log
.env*
.DS_Store
```
Justification :
- `node_modules`, `.next`, `docs`, `*.md` : ne doivent **jamais** entrer dans le contexte de build (cache, rapidité, confidentialité — les secrets éventuels d'une doc `.env` ne sont pas copiés).
- Le contexte `docker build` doit contenir **uniquement** ce dont le builder a besoin : sources `app/`, `components/`, `lib/`, `public/`, `next.config.ts`, `tsconfig.json`, `package.json`, `pnpm-lock.yaml`.
### 3.3 `docker-compose.yml`
```yaml
services:
cv-maker:
build:
context: .
dockerfile: Dockerfile
image: cv-maker:local # nom local ; remplacé par <mon-user-dockerhub>/cv-maker:tag au push
container_name: cv-maker
restart: unless-stopped
ports:
- "3000:3000"
environment:
- HOSTNAME=0.0.0.0
- PORT=3000
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
```
- `wget` est fourni par busybox sur Alpine (utilisable pour le healthcheck).
- `127.0.0.1` (et non `localhost`) : busybox wget privilégie `::1` (IPv6) quand `/etc/hosts` définit `localhost` en dual-stack ; le serveur Next écoute en IPv4 (`0.0.0.0`) uniquement → pas de fallback, erreur `Connection refused`. L'adresse IP explicite évite ce piège (validé C6).
- Le **stockage des données reste dans le localStorage du navigateur** : le conteneur n'a besoin d'aucun volume persistant.
## 4. Workflow Docker Hub
### 4.1 Build et test local
```bash
# Build de l'image
docker build -t cv-maker:latest .
# Test rapide
docker run --rm -p 3000:3000 cv-maker:latest
# → http://localhost:3000
# Inspection (taille, couches)
docker images cv-maker
```
### 4.2 Publication sur Docker Hub
```bash
# 1. Se connecter au registre
docker login
# 2. Taguer avec le namespace du compte (placeholder à remplacer)
docker tag cv-maker:latest <mon-user-dockerhub>/cv-maker:latest
# 3. Optionnel : tag avec version (semver)
docker tag cv-maker:latest <mon-user-dockerhub>/cv-maker:1.0.0
# 4. Pousser
docker push <mon-user-dockerhub>/cv-maker:latest
docker push <mon-user-dockerhub>/cv-maker:1.0.0
```
> Remplacez `<mon-user-dockerhub>` par votre identifiant Docker Hub. Le pattern `latest` + `1.0.0` (semver) est recommandé : `latest` pour les déploiements courants, un tag précis pour les retours arrière.
### 4.3 Déploiement depuis l'image publiée
```bash
docker pull <mon-user-dockerhub>/cv-maker:latest
docker run -d -p 3000:3000 --name cv-maker <mon-user-dockerhub>/cv-maker:latest
# ou via compose (après édition de image: dans docker-compose.yml)
docker compose up -d
docker compose down # arrêt
```
## 5. Recette de validation conteneurisée
| # | Action | Résultat attendu |
| --- | --- | --- |
| C1 | `docker build -t cv-maker:latest .` | Build multi-stage sans erreur ; maintenant `deps` cache le layer npm |
| C2 | `docker images cv-maker` | Image < 200 Mo (standalone) |
| C3 | `docker run --rm -p 3000:3000 cv-maker:latest` puis ouvrir `localhost:3000` | L'éditeur s'affiche ; le PWA `sw.js` des réponses |
| C4 | Saisir du contenu, recharger | Persistance localStorage fonctionnelle dans le conteneur |
| C5 | `docker exec <ctr> wget -qO- localhost:3000/documentation` | HTTP 200, contenu de la page Documentation |
| C6 | `docker compose up -d` puis `docker compose ps` | Conteneur `healthy`, port mappé |
| C7 | `docker push <mon-user-dockerhub>/cv-maker:latest` | Image visible sur Docker Hub (page du compte) |
| C8 | `docker pull/run` depuis Docker Hub sur une autre machine | L'application démarre identiquement |
## 6. Dépannage
| Problème | Cause probable | Remède |
| --- | --- | --- |
| `pnpm: command not found` dans le build | `corepack enable` avant `pnpm` | Vérifier l'ordre des instructions dans `deps` |
| `Error: Cannot find module server.js` | sortie standalone absente | Vérifier `output: "standalone"` dans `next.config.ts` avant `pnpm build` |
| `sw.js` (PWA) non servi | `public/` oublié dans le runner | Ajouter `COPY --from=builder /app/public ./public` |
| Port déjà occupé en local | un autre process sur 3000 | Changer la mappage : `-p 3001:3000` |
| Image trop lourde | `.dockerignore` absent/incomplet | Re-vérifier les exclusions `node_modules`, `.next` |
| `frozen-lockfile` échoue | `pnpm-lock.yaml` obsolète | Re-synchroniser en local (`pnpm install`) puis commit du lockfile |
## 7. Évolutions (non bloquantes v1)
- **CI/CD** : workflow GitHub Actions (ou GitLab CI) : stage `build` → `push` sur Docker Hub uniquement sur les tags/`main` ; secrets `DOCKERHUB_USERNAME` / `DOCKERHUB_TOKEN` stockés dans les paramètres du dépôt.
- **Multiplateformes** : `docker buildx build --platform linux/amd64,linux/arm64` pour ARM (Raspberry, Apple Silicon serveurs).
- **Scan de sécurité** : `docker scout cves <image>` avant chaque push.