Passez en production : NODE_ENV, process manager, image Docker correcte et scaling multi-cœurs avec cluster.
Ouvrir cette leçon dans KodokonNODE_ENV n'est qu'une convention de bibliothèques : le cœur de Node l'ignore presque totalement. Mais Express, entre autres, s'en sert pour mettre les vues en cache et masquer les stack traces ; certains frameworks sont plusieurs fois plus rapides avec NODE_ENV=production. Côté dépendances, npm ci --omit=dev installe exactement le lockfile, sans les dépendances de développement - reproductible et plus rapide que npm install. Depuis Node 20.6, --env-file charge un fichier .env nativement, sans dépendance.
NODE_ENV=production node server.js
npm ci --omit=dev
node --env-file=.env server.jsUn processus Node finit toujours par mourir : dépassement mémoire, bug, exception. Le rôle du process manager est de le relancer. Deux écoles : sur une machine nue, pm2 (ou systemd) supervise, redémarre et recharge sans coupure ; dans un conteneur, c'est l'orchestrateur (Kubernetes, ECS) qui joue ce rôle - un seul processus par conteneur, et pm2 devient inutile, voire nuisible, car il masque les crashs à l'orchestrateur.
npm install -g pm2
pm2 start server.js -i max --name api
pm2 reload api
pm2 logs api --lines 100Deux pièges Docker spécifiques à Node. Un : l'image doit tourner avec l'utilisateur non privilégié node, fourni par les images officielles. Deux : Node n'est pas conçu pour être PID 1 - il ne moissonne pas les processus zombies ; lancez le conteneur avec docker run --init (ou init: true en Compose) pour insérer un init minimal. Enfin, écrivez CMD en forme exec : la forme shell intercale /bin/sh, qui ne transmet pas SIGTERM - votre arrêt propre de la leçon précédente ne se déclencherait jamais.
FROM node:20-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
FROM node:20-slim
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]Un processus Node n'exploite qu'un seul cœur. Le module cluster crée un processus worker par cœur : le primaire écoute le port et distribue les connexions aux workers en round-robin (comportement par défaut, sauf sous Windows). Chaque worker possède sa propre mémoire et son propre event loop : aucun état partagé - sessions et caches doivent vivre dans Redis ou équivalent. Pour du calcul pur, préférez worker_threads, qui partage la mémoire via SharedArrayBuffer.
import cluster from "node:cluster";
import { createServer } from "node:http";
import { availableParallelism } from "node:os";
if (cluster.isPrimary) {
const count = availableParallelism();
for (let i = 0; i < count; i += 1) cluster.fork();
cluster.on("exit", (worker) => {
console.log(`worker ${worker.process.pid} down`);
cluster.fork();
});
} else {
createServer((req, res) => {
res.end(`pid ${process.pid}`);
}).listen(3000);
}