Выпусти проект в продакшен: NODE_ENV, менеджер процессов, правильный Docker-образ и масштабирование на все ядра через cluster.
Открыть этот урок в KodokonNODE_ENV - это всего лишь соглашение библиотек: ядро Node почти полностью его игнорирует. Но Express и другие используют его, чтобы кешировать шаблоны и скрывать стеки вызовов; некоторые фреймворки в несколько раз быстрее с NODE_ENV=production. Со стороны зависимостей npm ci --omit=dev устанавливает ровно то, что в lock-файле, без dev-зависимостей - воспроизводимо и быстрее, чем npm install. Начиная с Node 20.6 --env-file загружает файл .env штатно, без дополнительных зависимостей.
NODE_ENV=production node server.js
npm ci --omit=dev
node --env-file=.env server.jsПроцесс Node рано или поздно умирает: перерасход памяти, баг, исключение. Роль менеджера процессов - перезапустить его. Две школы: на голой машине pm2 (или systemd) следит, перезапускает и перезагружает без простоя; в контейнере эту роль играет оркестратор (Kubernetes, ECS) - один процесс на контейнер, и pm2 становится бесполезен, даже вреден, потому что скрывает падения от оркестратора.
npm install -g pm2
pm2 start server.js -i max --name api
pm2 reload api
pm2 logs api --lines 100Две ловушки Docker, специфичные для Node. Первая: образ должен работать от непривилегированного пользователя node, который есть в официальных образах. Вторая: Node не рассчитан на роль PID 1 - он не подбирает процессы-зомби; запускай контейнер с docker run --init (или init: true в Compose), чтобы вставить минимальный init. Наконец, пиши CMD в exec-форме: shell-форма вставляет /bin/sh, который не пробрасывает SIGTERM - корректная остановка из предыдущего урока просто никогда не сработает.
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"]Один процесс Node использует только одно ядро. Модуль cluster создаёт по процессу-воркеру на ядро: главный процесс слушает порт и раздаёт соединения воркерам по кругу (поведение по умолчанию, кроме Windows). У каждого воркера своя память и свой цикл событий: никакого общего состояния - сессии и кеши должны жить в Redis или его аналоге. Для чистых вычислений предпочитай worker_threads, которые разделяют память через 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);
}