Files
cv-maker/docs/conception/08-deploiement-docker.md
T
2026-09-16 21:34:09 +02:00

8.7 KiB
Raw Blame History

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

# ── É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

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

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

# 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

# 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

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.