Cours NestJS · Module 12: Conteneurs et CI/CD
Projet principal - conteneurisation et pipeline CI/CD
Dans cette leçon7
Tout le module se composait d'outils séparés : l'image, Compose, le pipeline, les secrets, les métriques, le tracing, le plan d'infrastructure. Nous allons maintenant les rassembler en une seule configuration - et rappeler au passage pourquoi chaque décision se présente ainsi. Ce sont ces justifications qui séparent une configuration copiée d'une configuration comprise.
L'image - deux étapes
1FROM node:20-alpine AS builder
2WORKDIR /app
3COPY package.json yarn.lock ./
4RUN yarn install --frozen-lockfile
5COPY . .
6RUN yarn build
7
8FROM node:20-alpine AS production
9WORKDIR /app
10RUN addgroup -g 1001 -S nodejs && adduser -S nestjs -u 1001
11COPY /app/dist ./dist
12COPY /app/node_modules ./node_modules
13USER nestjs
14EXPOSE 4000
15HEALTHCHECK \
16 CMD curl -f http://localhost:4000/health || exit 1
17CMD ["node", "dist/main"]Trois décisions de ce fichier méritent d'être justifiées.
COPY --from=builder transporte le résultat de la première étape vers la seconde. L'ordre de ses éléments est fixe : COPY, --from=builder, /app/dist (d'où), ./dist (vers où). Grâce à lui, l'image de production ne contient ni compilateur, ni sources, ni dépendances de développement - seulement le résultat.
USER nestjs - nous créons un utilisateur sans droits root afin de limiter les dégâts potentiels en cas d'intrusion. Non pour que le conteneur démarre plus vite, ni pour que l'image soit plus petite, ni pour permettre le multithreading. Un processus tournant en root à l'intérieur d'un conteneur, une fois échappé de l'isolation, est root sur l'hôte également.
HEALTHCHECK indique à Docker comment vérifier que l'application est réellement vivante, et non seulement que le processus existe. L'ordre : healthcheck:, test: curl -f http://localhost:4000/health, interval: 30s, retries: 3.
Compose - trois services
Dans le fichier Compose, vérifie les deux points que l'on confond le plus souvent.
Le conteneur api se connecte à la base par le nom de service : mongodb:27017 - non par l'adresse IP du conteneur, non par localhost:27017 et non par un DNS externe. localhost à l'intérieur d'un conteneur désigne ce même conteneur, il pointerait donc vers l'API et non vers la base.
Le drapeau -v de docker compose down -v supprime les volumes avec leurs données. Il n'active pas de mode verbeux, ne valide pas le fichier et n'affiche pas la version. Cette commande efface la base - emploie-la en conscience.
Le déploiement - l'ordre
La route complète d'un changement vers la production compte quatre étapes :
- Push du code sur la branche main.
- CI : lint, test, build.
- Docker build & push image.
- Smoke tests et surveillance après déploiement.
La quatrième étape est souvent omise, et c'est elle qui sépare « déployé » de « fonctionne ». Les smoke tests sont une poignée de vérifications des chemins essentiels juste après le déploiement - la page s'ouvre-t-elle, la connexion passe-t-elle, la santé répond-elle. Le pipeline a pu passer au vert sans que l'application se lève, faute d'une variable d'environnement dont personne n'avait besoin en CI.
Kubernetes - deux commandes
Une fois l'image prête, le cluster accepte une description de ressources depuis un fichier :
1kubectl apply -f deployment.yamlL'ordre des éléments : kubectl, apply, -f, deployment.yaml. Le drapeau -f désigne le fichier de description ; apply amène le cluster à l'état qui y est inscrit - créant ce qui manque et modifiant ce qui diffère.
La mise à l'échelle est une commande distincte :
1kubectl scale deployment roman-api --replicas=5Ici l'ordre est : kubectl scale, deployment roman-api, --replicas=5. Cinq répliques signifient cinq copies parallèles de l'application, entre lesquelles le cluster répartit le trafic.
Terraform - les modules
Un dernier point de la couche infrastructure. Les modules Terraform sont des blocs d'infrastructure réutilisables et paramétrables - non des plugins pour dialoguer avec un cloud (ce sont les providers), ni des tests unitaires, ni des fichiers de journaux.
L'intérêt est le même qu'une fonction dans du code : tu décris une fois « un serveur applicatif avec une base et un réseau », puis tu l'appelles trois fois pour les environnements de test, de préproduction et de production, en ne changeant que les paramètres.
La checklist du projet
Rassemble l'ensemble et vérifie chaque point :
- Docker - image multi-étapes, utilisateur sans root,
HEALTHCHECK, - Docker Compose - trois services, connexions par nom, un volume pour les données de la base,
depends_onavecservice_healthy, - CI/CD - workflow sur
pushetpull_request, jobs dans l'ordre lint → build → test → docker, secrets dans les réglages du dépôt, - Secrets - aucun mot de passe dans le dépôt ;
.enven local seulement, - Observability - endpoint
/metrics, métriques RED, tracing avecspan.end()dansfinally, - Infrastructure as Code - le plan dans des fichiers
.tf, l'état dans un backend distant, pas dans Git.
Récapitulatif
La campagne est prête à partir :
COPY --from=buildertransporte le résultat du build :COPY,--from=builder, source, destination,- nous créons un utilisateur sans root pour limiter les dégâts en cas d'intrusion - non pour la vitesse ni la taille de l'image,
HEALTHCHECKdans l'ordre :healthcheck:,test:,interval:,retries:,- les conteneurs se connectent par nom de service (
mongodb:27017), non par IP ni parlocalhost, docker compose down -vsupprime les volumes avec leurs données,- ordre de déploiement : push → CI (lint, test, build) → docker build & push → smoke tests et surveillance,
kubectlapply-fdeployment.yamlintroduit une description dans le cluster,kubectl scaledeployment nom--replicas=5change le nombre de copies,- les modules Terraform sont des blocs d'infrastructure réutilisables et paramétrables - ni providers, ni tests, ni journaux.
C'est la dernière leçon du module. Tu sais désormais construire une image, décrire un environnement, faire passer un changement par un pipeline, dissimuler les secrets, voir ce qui se passe en production et reproduire toute l'infrastructure depuis un fichier. En attendant, retiens ceci : chaque élément de cette configuration a une justification - et c'est leur connaissance, non le fichier lui-même, qui se transporte au projet suivant.
Code de cette leçon : src/deployment-pipeline.ts
1// Projet principal - Pipeline de déploiement complet
2console.log("=== ROUTES DE L'EMPIRE - DEPLOYMENT PIPELINE ===\n");
3
4interface ChecklistItem {
5 category: string;
6 items: { name: string; status: boolean }[];
7}
8
9const checklist: ChecklistItem[] = [
10 {
11 category: 'Docker',
12 items: [
13 { name: 'Dockerfile multi-stage', status: true },
14 { name: 'Utilisateur non-root', status: true },
15 { name: '.dockerignore', status: true },
16 { name: 'HEALTHCHECK dans le Dockerfile', status: true },
17 ],
18 },
19 {
20 category: 'Docker Compose',
21 items: [
22 { name: 'API + MongoDB + Redis', status: true },
23 { name: 'Health checks sur les services', status: true },
24 { name: 'Volumes pour les données', status: true },
25 { name: 'Réseau isolé', status: true },
26 ],
27 },
28 {
29 category: 'CI/CD',
30 items: [
31 { name: 'Lint + Test + Build', status: true },
32 { name: 'Docker build & push', status: true },
33 { name: 'GitHub Secrets', status: true },
34 ],
35 },
36 {
37 category: 'Security',
38 items: [
39 { name: 'Helmet', status: true },
40 { name: 'CORS', status: true },
41 { name: 'Rate limiting', status: true },
42 { name: 'ValidationPipe', status: true },
43 { name: 'Compression', status: true },
44 ],
45 },
46 {
47 category: 'Monitoring',
48 items: [
49 { name: 'Endpoint de health check', status: true },
50 { name: 'Logging structuré', status: true },
51 { name: 'Rotation des logs', status: true },
52 ],
53 },
54];
55
56checklist.forEach(cat => {
57 console.log(`\n${cat.category} :`);
58 cat.items.forEach(item => {
59 const icon = item.status ? 'DONE' : 'TODO';
60 console.log(` [${icon}] ${item.name}`);
61 });
62});
63
64const total = checklist.reduce((s, c) => s + c.items.length, 0);
65const done = checklist.reduce(
66 (s, c) => s + c.items.filter(i => i.status).length, 0
67);
68console.log(`\nStatut : ${done}/${total} éléments prêts`);
69console.log("\nLes routes de l'Empire sont prêtes - du code à la production !");
70Tu as repéré une erreur dans cette leçon ?