The Jerusalem PostDespite spy warnings, Trump called Huang to urge him to stay at Nvidia before resignation - reportPunchUS Air Force captain arrested in JapanInquirerProtesters burn Trump effigy ahead of his Asean Summit arrivalZDF heuteEntdecken Sie das ZDF-NachrichtenstudioWirtualna PolskaMgła i silny wiatr dotrą do Polski. IMGW ostrzegaBBC NewsYoung people should have legal guarantee of job or training, Milburn tells BBCХабрПосильная помощь в бэклог приложения «Моя школа»RMF24Zniszczył flagi przed pomnikiem. Porozrzucał wieńce i zniczeRapplerLIVE UPDATES: 49th ASEAN Summit in Manila20 MinutenAlexi rutscht Hochzeitsdatum raus – sie ist nicht mal verlobtRFI 中文泽连斯基:普京并不支持停火,不会同意在宪法中确立中立地位ESPN🌹 Commanders QB Sam Hartman to star as 'The Bachelor' in show's 30th season
The Daily Newsstand · Free, Always
Saturday, November 14, 2026

Архитектура контейнеризации Next.js: оркестрация Docker Compose, супервизоры процессов и разделение окружений

Translate

Архитектурная эволюция: сопоставление PM2, автономного режима Standalone и контейнерных инициализаторов

Исторически развертывание приложений на базе Node.js на физических серверах или виртуальных машинах (VPS) опиралось на менеджеры процессов, среди которых стандартом де-факто стал PM2. В традиционных операционных системах PM2 решал фундаментальные задачи однопоточной природы Node.js: перезапуск при сбоях, автоматический запуск после перезагрузки ОС, ротацию журналов и вертикальное масштабирование на все доступные ядра центрального процессора посредством модуля cluster среды выполнения. Однако фундаментальный сдвиг в сторону микросервисной и контейнерной архитектуры видоизменил распределение ответственности между компонентами инфраструктуры.

В экосистеме Docker базовой парадигмой является изоляция одного процесса в рамках одного контейнера. Попытка интеграции PM2 внутрь Docker-контейнера создает избыточный архитектурный слой — супервизор над другим супервизором. Демон Docker вместе с оркестраторами (Docker Compose, Kubernetes, Nomad) самостоятельно отслеживает жизненный цикл контейнеров, реализует политики перезапуска (restart: unless-stopped) и собирает метрики. Если внутри контейнера запускается стандартный демон PM2, дочерний процесс Node.js может аварийно завершиться или зависнуть в цикле утечки памяти, но для внешнего оркестратора контейнер продолжит отображаться как работоспособный, поскольку процесс самого менеджера PM2 остается активным.

Начиная с версии 12, фреймворк Next.js предлагает встроенный режим сборки output: "standalone". Данный механизм задействует алгоритм трассировки файлов зависимостей (@vercel/nft), сканирует дерево импортов и генерирует минимальный рабочий каталог .next/standalone. В него помещаются исключительно используемые в продакшене модули и скомпилированный HTTP-сервер server.js. Это позволяет отказаться от громоздкой команды next start, требующей наличия всей исходной директории node_modules, и сократить размер итогового образа Docker с 1–1.5 ГБ до компактных 100–150 МБ.

Архитектурным барьером для использования PM2 в режиме кластера (exec_mode: 'cluster') внутри контейнера является внутренняя модель кэширования Next.js. Архитектура App Router глубоко полагается на инкрементную статическую регенерацию (ISR), серверный кэш запросов (fetch cache) и кэш компонентов. В стандартной конфигурации кэш размещается в оперативной памяти или локальной файловой системе конкретного процесса. При запуске нескольких инстансов под управлением PM2 каждый рабочий процесс формирует изолированный сегмент кэша. Когда входящий вебхук или серверное действие (Server Action) вызывает инвалидацию по тегу (revalidateTag), сброс данных происходит только в памяти одного воркера, в то время как остальные продолжают отдавать устаревшие данные. Масштабирование Next.js как внутри многопроцессного контейнера, так и между несколькими репликами требует выноса состояния во внешнее распределенное хранилище (например, Redis) с реализацией интерфейса cacheHandler и установкой параметра cacheMaxMemorySize: 0.

Еще одной технической проблемой прямого запуска Node.js в контейнерах является поведение процесса с идентификатором PID 1. Среда Node.js изначально не проектировалась для выполнения функций системного процесса инициализации: она не пересылает POSIX-сигналы (SIGTERM, SIGINT) дочерним процессам и не очищает таблицу процессов от завершившихся дочерних задач, что провоцирует накопление «зомби-процессов» (defunct) и приводит к десятисекундным задержкам остановки контейнера по таймауту SIGKILL.

Для решения проблемы PID 1 современная практика контейнеризации предписывает использовать не тяжеловесный менеджер PM2, а специализированные инициализаторы вроде Tini. Платформа Docker Compose предоставляет встроенную поддержку Tini через директиву init: true, что исключает необходимость добавления вспомогательных бинарников в дистрибутив контейнера. Если специфика проекта безальтернативно требует присутствия PM2, запуск в контейнере допустим исключительно с помощью утилиты pm2-runtime, удерживающей процесс на переднем плане (foreground), преимущественно в режиме fork для одного инстанса.

Характеристика

Standalone + Docker Compose (Рекомендуемый подход)

PM2 Runtime внутри Docker (pm2-runtime)

Классический сервер VPS с PM2

Управление жизненным циклом

Демон Docker (restart_policy, healthcheck)

pm2-runtime внутри контейнера

Системный демон PM2 и служба Systemd

Размер дискового пространства

Минимальный (100–150 МБ за счет трассировки файлов)

Средний (250–450 МБ из-за глобального PM2)

Большой (1–2 ГБ, полный каталог node_modules)

Обработка сигналов ядра (PID 1)

Аппаратная через init: true (интегрированный Tini)

Встроена в исполняемый файл pm2-runtime

Переложена на системный менеджер ОС (Systemd)

Горизонтальное масштабирование

Репликация контейнеров через scale и балансировщик

Запуск дочерних процессов (exec_mode: 'cluster')

Внутренний кластер PM2 на ядра CPU

Координация кэша ISR

Требует внешний Redis при числе реплик более одной

Требует внешний Redis даже внутри одного контейнера

Требует внешний Redis при кластеризации

Мониторинг доступности

Внешний опрос healthcheck через API оркестратора

Локальные модули PM2 или интерфейс PM2 Plus

Утилиты командной строки (pm2 monit, pm2 logs)

Структурная организация проекта и топология каталогов

Четкое разделение конфигураций инфраструктуры и исходного кода приложения обеспечивает предсказуемость сборки и устраняет риск утечки служебных файлов в продуктивную среду. Рекомендуется придерживаться модульной топологии, где декларации сред разработки и эксплуатации разнесены по специализированным файлам Compose и Dockerfile.

Структура проекта организуется следующим образом:

  • my-nextjs-app/ — корневая директория репозитория приложения.

    • .dockerignore — декларация исключений из контекста сборки Docker-образов.

    • .gitignore — фильтрация служебных файлов для системы контроля версий Git.

    • .env.example — эталонный шаблон переменных окружения без чувствительных данных.

    • .env.production.example — шаблон переменных производственной среды.

    • compose.dev.yaml — оркестрация локального окружения с поддержкой горячей перезагрузки.

    • compose.prod.yaml — конфигурация оркестрации продуктивного контура с изоляцией сетей.

    • Dockerfile — многоэтапный сборочный манифест для формирования минимального production-образа.

    • Dockerfile.dev — конфигурация контейнера разработки с полным набором инструментов отладки.

    • ecosystem.config.js — конфигурационный манифест PM2 (применяется при регламентном требовании запуска через супервизор).

    • next.config.ts — конфигурация фреймворка с активацией автономного вывода standalone.

    • package.json и package-lock.json — манифесты зависимостей и фиксации версий пакетов.

    • public/ — статически обслуживаемые ресурсы фронтенда (изображения, шрифты, фавиконы).

    • src/ — основной каталог исходного кода.

      • app/ — маршрутизация и страницы App Router.

        • api/health/route.ts — контроллер проверки жизнеспособности сервиса.

        • layout.tsx — корневой макет интерфейса.

        • page.tsx — главная страница приложения.

    • deploy/ — инфраструктурные скрипты и файлы конфигураций серверов.

      • nginx/conf.d/default.conf — параметры обратного прокси с оптимизацией под потоковый рендеринг Next.js.

    • tsconfig.json — конфигурация компилятора TypeScript.

Подобная архитектура четко разграничивает среду быстрой локальной итерации разработчика и жестко зафиксированный продуктивный контур, предотвращая случайную подмену боевых образов конфигурациями с активным HMR-сервером.

Политика изоляции артефактов: .gitignore и .dockerignore

Корректное составление файлов фильтрации исключает попадание в репозиторий конфиденциальных параметров, а также блокирует отправку лишних мегабайт данных демону сборки BuildKit, оптимизируя время создания слоев.

Критическая особенность компилятора Next.js заключается в обработке переменных среды при сборке standalone: если файлы .env присутствуют в контексте билда, утилита next build запекает их содержимое в артефакты каталога .next/standalone, после чего секреты беспрепятственно утекают в публикуемый образ. По этой причине локальные конфигурации окружения должны быть категорически изолированы от сборочного контекста Docker.

Ниже представлена конфигурация .gitignore:

# Зависимости среды выполнения
node_modules/
.pnp
.pnp.js

# Артефакты сборки и кэши Next.js
.next/
out/
build/
dist/

# Отладочные журналы
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# Конфиденциальные переменные окружения
.env
.env*.local
!.env.example
!.env.production.example

# Кэш сборочных конвейеров
.turbo/
coverage/

# Системные метаданные
.DS_Store
*.pem

Файл .dockerignore строится на базе принципа жесткого отсечения локальных модулей, систем контроля версий и конфигурационных файлов хоста, чтобы гарантировать установку нативных бинарников (например, модуля манипуляции изображениями sharp) строго под целевую архитектуру Linux-контейнера:

# Локальные модули и артефакты (создаются изолированно внутри контейнера)
node_modules
.next
out
build
dist
.turbo

# Система контроля версий и метаданные
.git
.gitignore
.github
.gitlab
README.md

# Локальные файлы окружения (секреты передаются на этапе запуска)
.env
.env*.local
.env.development
.env.test

# Файлы декларации контейнеров
Dockerfile*
compose*.yaml
docker-compose*.yml
.dockerignore

# Временные системные файлы
*.log
.npm
.yarn

Проектирование сборочных образов: Dockerfile для Development и Production

Для локальной разработки критически важна скорость внесения правок и доступность всех инструментов сборки, тогда как для рабочей среды приоритетом становятся неизменяемость, защищенность и компактность.

Среда разработки: Dockerfile.dev

Манифест Dockerfile.dev ориентирован на установку полного дерева зависимостей (включая devDependencies) и поддержание соединения горячей замены модулей (HMR). Системная библиотека libc6-compat подключается для корректной интеграции низкоуровневых бинарных модулей в дистрибутивах на базе Alpine Linux.

Dockerfile

FROM node:22-alpine

WORKDIR /app

# Подключение базовых библиотек совместимости C-рантайма
RUN apk add --no-cache libc6-compat

# Кэширование слоя зависимостей
COPY package.json package-lock.json* ./

RUN npm install

# Копирование исходного кода (при монтировании томов перекрывается хостом)
COPY . .

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
ENV NODE_ENV=development

EXPOSE 3000

CMD ["npm", "run", "dev"]

Среда эксплуатации: Production Dockerfile (Multi-Stage)

Многоэтапная сборка изолирует компиляцию приложения от финального рантайма. В первом этапе (dependencies) выполняется чистая установка пакетов с использованием кэширования BuildKit (--mount=type=cache).

Во втором этапе (builder) компилируется проект.

Финальный этап (runner) содержит только необходимые файлы.

Важнейшая деталь автономного режима Next.js состоит в том, что статический каталог .next/static и общедоступная папка public/ намеренно не упаковываются в .next/standalone/, поскольку авторы фреймворка рассчитывают на их выгрузку в CDN. При автономном запуске контейнера эти каталоги необходимо скопировать вручную по строго определенным путям, иначе браузер получит HTML-разметку без стилей и изображений.

Кроме того, по соображениям информационной безопасности запуск процесса осуществляется от лица непривилегированного пользователя nextjs с фиксированным идентификатором UID 1001.

Dockerfile

ARG NODE_VERSION=22-alpine
FROM node:${NODE_VERSION} AS base
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Этап 1: Разрешение и изоляция зависимостей
FROM base AS dependencies
COPY package.json package-lock.json* ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci --no-audit --no-fund

# Этап 2: Компиляция приложения
FROM base AS builder
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

# Сборка standalone-артефактов с кэшированием сборщика
RUN --mount=type=cache,target=/app/.next/cache \
    npm run build

# Этап 3: Минимальный продакшен-образ
FROM node:${NODE_VERSION} AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Ограничение привилегий: запуск из-под системного пользователя
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nextjs

# Утилита проверки жизнеспособности контейнера
RUN apk add --no-cache curl

# Копирование статических ресурсов фронтенда
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

# Перенос сгенерированного автономного сервера
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./

# Перенос клиентских хэшированных чанков в ожидаемую структуру каталогов
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Предоставление прав на директорию динамического кэша
RUN mkdir -p .next && chown -R nextjs:nodejs .next

USER nextjs

EXPOSE 3000

# Запуск автономного сервера напрямую через среду Node.js
CMD ["node", "server.js"]

Декларативная оркестрация: Docker Compose для Dev и Prod

Для управления конфигурациями контейнеров используются независимые манифесты Compose, отражающие специфику каждой среды.

Манифест разработки: compose.dev.yaml

В среде разработки критично исключить перезапись каталога /app/node_modules контейнера модулями хост-машины. Для этого применяется теневой анонимный том (anonymous volume).

Переменная WATCHPACK_POLLING=true активирует опрос файловой системы механизмом отслеживания изменений, что устраняет проблемы передачи событий через виртуализированные файловые дескрипторы под Windows (WSL2) и macOS.

services:
  web-dev:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: nextjs_dev_container
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - WATCHPACK_POLLING=true
      - NEXT_TELEMETRY_DISABLED=1
    volumes:
      # Проброс исходного кода в реальном времени
      - .:/app
      # Изоляция зависимостей контейнера от хоста
      - /app/node_modules
      # Изоляция локальных кэшей компилятора
      - /app/.next
    networks:
      - dev_network

networks:
  dev_network:
    driver: bridge

Запуск осуществляется вызовом:

docker compose -f compose.dev.yaml up --build

Продакшен-оркестрация: compose.prod.yaml

В боевом окружении сервис Next.js закрывается обратным прокси-сервером Nginx. Ключевой настройкой является директива init: true, которая делегирует Tini обработку системных сигналов ядра, гарантируя корректное завершение открытых сетевых соединений при обновлении версий.

Директива healthcheck выполняет активный мониторинг состояния через внутренний эндпоинт, предотвращая подачу пользовательского трафика на неинициализированный узел.

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: nextjs-standalone-app:latest
    container_name: nextjs_app_prod
    restart: unless-stopped
    # Инициализация Tini в качестве PID 1 для обработки сигналов и зомби
    init: true
    stop_grace_period: 30s
    environment:
      - NODE_ENV=production
      - PORT=3000
      - HOSTNAME=0.0.0.0
      - DATABASE_URL=${DATABASE_URL}
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2048M
        reservations:
          cpus: "0.5"
          memory: 512M
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://127.0.0.1:3000/api/health || exit 1"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 20s
    networks:
      - app_network

  nginx:
    image: nginx:1.27-alpine
    container_name: nextjs_proxy_prod
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./deploy/nginx/conf.d:/etc/nginx/conf.d:ro
    depends_on:
      web:
        condition: service_healthy
    networks:
      - app_network

networks:
  app_network:
    driver: bridge

Запуск продуктивного комплекса выполняется следующей командой:

docker compose -f compose.prod.yaml up -d --build

Сопутствующие компоненты производственного контура

Конфигурация фреймворка: next.config.ts

Для активации генерации самодостаточного сервера в конфигурацию Next.js добавляется параметр output: "standalone". Дополнительно отключается заголовок идентификации платформы x-powered-by для снижения информативности потенциальных векторов атак.

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
  reactStrictMode: true,
  poweredByHeader: false,
  compress: true,
};

export default nextConfig;

Реализация контроллера проверки состояния: src/app/api/health/route.ts

Вспомогательный маршрут проверки работоспособности информирует оркестратор о текущем состоянии экземпляра приложения. Объявление маршрута как force-dynamic исключает его попадание в статический кэш.

import { NextResponse } from "next/server";

export const dynamic = "force-dynamic";

export async function GET() {
  try {
    const healthData = {
      status: "healthy",
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
      memoryRssBytes: process.memoryUsage().rss,
    };

    return NextResponse.json(healthData, { status: 200 });
  } catch (error) {
    return NextResponse.json(
      { status: "unhealthy", message: (error as Error).message },
      { status: 503 }
    );
  }
}

Альтернативная интеграция с PM2: ecosystem.config.js

Если внутренние стандарты предприятия предписывают обязательное внедрение PM2, в сборочный образ стадии runner устанавливается утилита PM2 (RUN npm install -g pm2), а запуск транслируется на команду CMD ["pm2-runtime", "start", "ecosystem.config.js"].

Файл ecosystem.config.js в корневой директории направляется непосредственно на автономный входной файл server.js:

module.exports = {
  apps: [
    {
      name: "nextjs-app",
      script: "./server.js",
      // Принудительный режим fork во избежание рассинхронизации локального ISR-кэша
      instances: 1,
      exec_mode: "fork",
      max_memory_restart: "1500M",
      env: {
        NODE_ENV: "production",
        PORT: 3000,
      },
    },
  ],
};

Конфигурация Nginx с поддержкой потоковой передачи данных

Современный стек Next.js (начиная с версий 13–15+) в режиме App Router опирается на компонентную модель React Server Components, где интерфейс передается потоково через чанкирование (Transfer-Encoding: chunked).

Стандартная конфигурация прокси-буферизации Nginx удерживает эти пакеты до полного формирования страницы, полностью разрушая пользовательский опыт от использования компонентов loading.tsx и границ Suspense.

В файле deploy/nginx/conf.d/default.conf буферизация принудительно отключается директивами proxy_buffering off; и proxy_set_header X-Accel-Buffering no;:

upstream nextjs_backend {
    server web:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name localhost;

    server_tokens off;
    client_max_body_size 25M;

    gzip on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

    location / {
        proxy_pass http://nextjs\_backend;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Критическая секция: обеспечение работоспособности Streaming SSR в App Router
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_set_header X-Accel-Buffering no;

        proxy_read_timeout 60s;
        proxy_connect_timeout 60s;
    }

    # Долгосрочное кэширование неизменяемых хэшированных файлов Next.js
    location /_next/static/ {
        proxy_pass http://nextjs\_backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
}

Синтез архитектурных решений

Переход от монолитных серверов с локальными демонами PM2 к контейнеризированным системам меняет баланс ответственности: управление процессами полностью делегируется уровню оркестрации.

Внедрение легковесного инициализатора Tini через init: true в Docker Compose закрывает риски зависания сигналов и накопления зомби-процессов без раздувания кодовой базы сторонними библиотеками управления потоками.

Использование автономного режима output: "standalone" обеспечивает минимальный размер образов и гарантирует изоляцию сборочных секретов при условии строгой настройки файлов .dockerignore.

Масштабирование системы осуществляется горизонтальным размножением контейнеров с балансировкой через Nginx, где обязательное отключение прокси-буферизации сохраняет реактивность потокового рендеринга современного Next.js. При расширении кластера сверх одного контейнера обязательным условием консистентности системы становится перевод внутреннего кэша фреймворка на единое внешнее хранилище типа Redis.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.