Docker и JavaScript: контейнеризация Node.js приложений (продвинутый уровень)

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: от простого к сложному в мире контейнеризации

На начальном уровне работа с Docker и JavaScript (Node.js) часто сводится к созданию простого Dockerfile и запуску контейнера. Однако в реальных продакшн-системах этого недостаточно. Продвинутый уровень подразумевает глубокое понимание механизмов сборки, оптимизации размера образов, управления зависимостями, сетевой изоляции и безопасности.

В этой статье мы разберём ключевые аспекты профессиональной контейнеризации Node.js-приложений. Вы узнаете, как использовать многостадийную сборку (multi-stage builds), настраивать Docker Compose для микросервисной архитектуры, управлять переменными окружения и минимизировать поверхность атаки. Материал ориентирован на разработчиков, уже знакомых с основами Docker и JavaScript.

1. Многостадийная сборка (Multi-stage builds) для Node.js

Одна из главных проблем при контейнеризации Node.js-приложений — избыточный размер финального образа. Стандартный подход с одним Dockerfile включает в себя инструменты сборки (TypeScript, Webpack, Babel), которые не нужны в рантайме. Многостадийная сборка позволяет разделить процесс на этапы: сборка и выполнение.

1.1. Пример Dockerfile с двумя стадиями

# Этап сборки (build)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build  # Предполагается, что build-скрипт собирает проект

# Этап выполнения (production)
FROM node:20-alpine AS production
WORKDIR /app
# Копируем только node_modules и собранные файлы
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./

EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]

Ключевые моменты:

  • node:20-alpine — легковесный образ на основе Alpine Linux (около 30 МБ).
  • npm ci — чистая установка зависимостей строго по package-lock.json.
  • --only=production — исключаем dev-зависимости (TypeScript, тесты).
  • USER node — отказ от root-пользователя для безопасности.

В результате финальный образ содержит только необходимые для запуска файлы, а размер сокращается с 300-400 МБ до 80-120 МБ.

1.2. Оптимизация кэширования слоёв

Docker кэширует каждый слой (команду). Чтобы ускорить повторные сборки, копируйте package*.json до копирования всего исходного кода. Если зависимости не изменились, слой с npm ci будет взят из кэша.

COPY package*.json ./
RUN npm ci --only=production
COPY . .  # Копируется только если исходники изменились

2. Docker Compose для микросервисной архитектуры

В продакшне Node.js-приложение редко работает изолированно. Обычно требуется база данных (PostgreSQL, MongoDB), кэш (Redis) или очередь сообщений (RabbitMQ). Docker Compose позволяет описать всю инфраструктуру в одном YAML-файле.

2.1. Пример docker-compose.yml для Node.js + Redis

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.prod
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - REDIS_URL=redis://redis:6379
    depends_on:
      - redis
    networks:
      - backend

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - backend

volumes:
  redis_data:

networks:
  backend:
    driver: bridge

Важные аспекты:

  • Сетевые изоляция: сервисы общаются через внутреннюю сеть backend. Redis не доступен извне (если не пробросить порт).
  • Именованные тома: redis_data сохраняет данные при перезапуске контейнера.
  • Переменные окружения: REDIS_URL использует имя сервиса (redis) как DNS-имя.
  • Зависимости: depends_on гарантирует порядок запуска (но не готовность — используйте healthcheck).

2.2. Healthcheck для Node.js

Чтобы Compose ждал, пока приложение действительно запустится, добавьте healthcheck в Dockerfile или сам сервис:

# В Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

Или в docker-compose.yml:

healthcheck:
  test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
  interval: 30s
  timeout: 10s
  retries: 3

3. Безопасность и ограничение ресурсов

Контейнеры не являются изолированной средой по умолчанию. Продвинутая контейнеризация требует настройки прав и лимитов.

3.1. Запуск от непривилегированного пользователя

Node.js-образы по умолчанию работают от root. Это опасно: если злоумышленник получит доступ к контейнеру, он получит root-права на хосте (при маппинге томов). Всегда переключайте пользователя:

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

3.2. Read-only файловая система

В продакшне приложение не должно писать в файловую систему контейнера (используйте тома или S3). Запустите контейнер с флагом --read-only:

docker run --read-only -v /tmp:/tmp my-node-app

В docker-compose.yml:

                

Блоги

Рекомендаций по книгам