Пошук уроків, статей та іншого контенту
Створіть production-контейнер для Node.js із правильним запуском процесу, користувачем і налаштуванням середовища.
Production-контейнер для Node.js має:
запускати застосунок у foreground;
використовувати команду в exec-формі;
не запускати процес від імені root;
встановлювати лише production-залежності;
отримувати налаштування через змінні середовища;
містити тільки файли, необхідні для роботи застосунку.
Контейнер не повинен завершуватися одразу після запуску. Основний процес Node.js має залишатися активним, щоб Docker міг відстежувати його стан і передавати сигнали завершення.
Створимо HTTP-сервер, який читає порт зі змінної середовища PORT.
server.jsconst http = require("node:http");
const port = Number(process.env.PORT) || 3000;
const server = http.createServer((request, response) => {
response.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
response.end("Node.js застосунок працює в Docker\n");
});
server.listen(port, "0.0.0.0", () => {
console.log(`Сервер слухає порт ${port}`);
});
const shutdown = (signal) => {
console.log(`Отримано ${signal}. Завершення роботи...`);
server.close(() => {
console.log("HTTP-сервер зупинено");
process.exit(0);
});
};
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));Адреса 0.0.0.0 важлива для контейнера. Якщо сервер слухатиме лише localhost, він може бути недоступним через проброшений порт Docker.
package.json{
"name": "node-docker-example",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node server.js"
}
}Створіть файл блокування залежностей:
npm installУ результаті з’явиться package-lock.json. Команда npm ci, яку ми використаємо в Dockerfile, потребує цього файлу.
.dockerignoreФайл .dockerignore визначає, які файли не потрібно надсилати в контекст складання образу.
node_modules
npm-debug.log
.git
.gitignore
.env
Dockerfile
.dockerignoreПапку node_modules не варто копіювати з локального комп’ютера. Залежності повинні встановлюватися всередині контейнера для відповідного середовища.
Файл .env також не слід додавати до образу. Секрети й налаштування передаються контейнеру під час запуску.
Створіть файл Dockerfile:
FROM node:22-bookworm-slim AS dependencies
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
FROM node:22-bookworm-slim
ENV NODE_ENV=production
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY package.json package-lock.json ./
COPY server.js ./
USER node
EXPOSE 3000
CMD ["node", "server.js"]FROM node:22-bookworm-slimЦе офіційний образ Node.js на основі Debian із мінімальним набором пакетів. Варіант slim зазвичай менший за повний образ.
У Dockerfile використано два етапи:
dependencies — встановлення залежностей;
фінальний етап — запуск застосунку.
Такий підхід дає змогу чітко відокремити підготовку залежностей від фінального образу.
WORKDIR /appУсі наступні команди виконуються в директорії /app. Якщо директорії ще немає, Docker створить її.
COPY package.json package-lock.json ./
RUN npm ci --omit=devСпочатку копіюються лише файли опису залежностей. Це допомагає Docker використовувати кеш: якщо код змінився, але залежності залишилися такими самими, цей шар не потрібно створювати повторно.
Команда npm ci:
використовує точні версії з package-lock.json;
перед встановленням очищає наявну папку node_modules;
підходить для автоматизованих і production-збірок.
Прапорець --omit=dev не встановлює залежності з devDependencies, які потрібні лише для розробки або тестування.
ENV NODE_ENV=productionЦя змінна повідомляє застосунку та бібліотекам, що контейнер працює в production-режимі.
COPY --from=dependencies /app/node_modules ./node_modules
COPY package.json package-lock.json ./
COPY server.js ./З етапу dependencies копіюється готова папка node_modules. До фінального образу додаються лише потрібні файли застосунку.
Для більших проєктів список файлів залежатиме від структури застосунку. Наприклад, може знадобитися скопіювати папку dist після складання.
USER nodeОфіційний Node.js-образ містить користувача node. Після цієї інструкції застосунок запускається не від імені root.
Запуск від імені непривілейованого користувача зменшує наслідки можливої вразливості в застосунку або його залежностях.
EXPOSE 3000EXPOSE документує порт, який використовує застосунок. Ця інструкція сама по собі не робить порт доступним з операційної системи. Для цього під час запуску потрібно використати параметр -p.
CMD ["node", "server.js"]Це exec-форма команди. Вона запускає Node.js без додаткової оболонки.
Не використовуйте для основного процесу таку форму без необхідності:
CMD node server.jsУ shell-формі Docker запускає команду через оболонку. Це може ускладнити коректну передачу сигналів процесу. exec-форма є явною та краще підходить для основного процесу контейнера.
Виконайте команду в директорії, де розташовані Dockerfile, package.json, package-lock.json і server.js:
docker build -t node-docker-example .Крапка в кінці означає поточну директорію як контекст складання.
Перевірити створений образ можна так:
docker images node-docker-exampleЗапустіть контейнер і зв’яжіть порт контейнера з портом локального комп’ютера:
docker run --rm -p 3000:3000 node-docker-exampleТепер застосунок доступний за адресою:
http://localhost:3000Параметри команди:
--rm автоматично видаляє контейнер після його зупинки;
-p 3000:3000 передає локальний порт 3000 на порт 3000 у контейнері;
node-docker-example — назва образу.
Зупинити контейнер можна комбінацією Ctrl+C. Docker передасть процесу сигнал завершення, а обробник у server.js коректно закриє HTTP-сервер.
Порт не повинен бути жорстко прив’язаний до одного значення. Передайте інший порт під час запуску:
docker run --rm -e PORT=8080 -p 8080:8080 node-docker-exampleУ цьому випадку:
Node.js слухає порт 8080 усередині контейнера;
Docker передає порт 8080 на локальний порт 8080;
значення EXPOSE 3000 не змінює фактичний порт процесу.
Змінні середовища можна передавати й окремо для різних середовищ:
docker run --rm \
-e NODE_ENV=production \
-e PORT=8080 \
-p 8080:8080 \
node-docker-exampleНе зберігайте паролі, токени та інші секрети в Dockerfile. Значення, записані через ENV у Dockerfile, можуть стати частиною метаданих образу.
Перевірити користувача всередині контейнера можна командою:
docker run --rm node-docker-example idОчікується, що процес працює від імені користувача node, а не root.
Переглянути запущені контейнери:
docker psПереглянути журнали:
docker logs <container_id>Для production-контейнера журнали застосунку мають виводитися в стандартний потік stdout або stderr. Тоді Docker та інші системи запуску можуть їх збирати.
node_modulesCOPY . .Така команда може скопіювати локальні залежності, якщо вони не додані до .dockerignore. Вони можуть бути несумісними з операційною системою або архітектурою контейнера.
Використовуйте .dockerignore і встановлюйте залежності всередині образу.
package-lock.jsonЯкщо Dockerfile містить:
RUN npm ciале package-lock.json відсутній, складання завершиться помилкою.
Спочатку створіть файл блокування:
npm installrootЯкщо в Dockerfile немає USER node, процес зазвичай запускається від імені root.
Додайте після копіювання файлів:
USER nodeУсі файли, потрібні застосунку для читання, мають бути доступними цьому користувачу.
localhostТакий код може бути недоступним ззовні контейнера:
server.listen(port, "localhost");Для контейнера використовуйте:
server.listen(port, "0.0.0.0");Параметри -p мають відповідати порту, на якому реально слухає застосунок:
docker run --rm -p 8080:3000 node-docker-exampleТут локальний порт 8080 спрямовується на порт 3000 у контейнері. Відкрити застосунок потрібно за адресою http://localhost:8080.
Команда:
RUN npm installможе встановити залежності для розробки, якщо не задано production-режим.
Для production-вмісту використовуйте:
RUN npm ci --omit=devCMDДля основного процесу контейнера краще використовувати масив аргументів:
CMD ["node", "server.js"]Це дозволяє Docker безпосередньо запускати Node.js і правильно передавати йому сигнали.
Production-контейнер Node.js:
встановлює залежності через npm ci;
не містить devDependencies;
задає NODE_ENV=production;
запускає Node.js у foreground;
використовує CMD в exec-формі;
працює від імені непривілейованого користувача node;
слухає 0.0.0.0;
отримує порт та інші налаштування через змінні середовища;
не містить локальних залежностей і секретів завдяки .dockerignore.