---
title: 'Переносим Next.js проект из PM2 в Docker: защита от критических уязвимостей | Zomro'
description: ''
image: 'https://cdn.zomro.com/uploads/blog/yaroslav.baidatskyi%40zomro.org/2026/01/23/pasted-image-20260123140356_q50.jpg'
---

![opened image]()

  * [Zomro](https://zomro.com/rus/)
  * [FAQ](https://zomro.com/rus/blog/faq/)
  * Переносим Next.js проект из PM2 в Docker: защита от критических уязвимостей 



# Переносим Next.js проект из PM2 в Docker: защита от критических уязвимостей

24-01-2026

## Что случилось и почему это важно

В декабре 2025 года мир Next.js и React потрясла серия критических уязвимостей. Если Вы используете Next.js - эта статья для Вас.

### CVE-2025-55182 (React2Shell)

**Уровень опасности:** CVSS 10.0 из 10.0 (максимальный)

Это уязвимость в React Server Components. Злоумышленник может Выполнить любой код на Вашем сервере, просто отправив специально сформированный HTTP-запрос. Без пароля, без авторизации - просто запрос, и Ваш сервер взломан.

**Что затронуто:**

  * react-server-dom-* версий 19.0.0, 19.1.0, 19.1.1, 19.2.0
  * Next.js 15.x и 16.x (App Router)
  * Next.js начиная с версии 14.3.0-canary.77



**Исправлено в:**

  * React: 19.0.1, 19.1.2, 19.2.1
  * Next.js: 14.x stable, 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7



### CVE-2025-66478

Это та же уязвимость, но специфичная для Next.js. По сути — дубликат CVE-2025-55182, но для понимания: если вы на Next.js с App Router - Вы были уязвимы.

### CVE-2025-55183 (Source Code Exposure)

**Уровень опасности:** Средний

Позволяет получить исходный код Ваших Server Actions. То есть злоумышленник может увидеть Вашу бизнес-логику. Если Вы случайно захардкодили пароль в коде - его тоже увидят.

**Исправлено в:** Next.js 15.0.7+, 15.1.11+, 15.2.8+, 15.3.8+, 15.4.10+, 15.5.9+, 16.0.10+

> Версии 14.x и 13.x **не получили** исправление этой уязвимости.

### CVE-2025-55184 (Denial of Service)

**Уровень опасности:** Высокий

Denial of Service (DoS) - специальный запрос вешает Ваш сервер, и он перестаёт отвечать на любые запросы. Сайт просто падает.

**Исправлено в:** Next.js 13.3+, 14.2.35+, 15.0.7+, 15.1.11+, 15.2.8+, 15.3.8+, 15.4.10+, 15.5.9+, 16.0.10+

## Зачем переносить в Docker?

Вы спросите: "Я обновил Next.js, уязвимости закрыты. Зачем мне Docker?"

Отличный вопрос. Вот зачем:

  1. **Изоляция** \- если завтра найдут новую уязвимость и Ваш сервер взломают, злоумышленник окажется внутри контейнера, а не на Вашем хосте. Это как комната внутри комнаты.
  2. **Минимум лишнего -** в контейнере только то, что нужно для работы приложения. Меньше софта = меньше потенциальных дыр.
  3. **Быстрое обновление** \- нашли уязвимость? Пересобрали контейнер, перезапустили. Всё. Не нужно SSH на сервер и ручные танцы с бубном.
  4. **Одинаковое окружение** \- "у меня локально работает" больше не аргумент. В контейнере всё одинаково: и у Вас, и на сервере.



## Как сейчас выглядит Ваш проект (с PM2)

Допустим, у Вас типичная ситуация:

![Переносим Next.js проект из PM2 в Docker защита от критических уязвимостей 1](https://cdn.zomro.com/uploads/blog/yaroslav.baidatskyi%40zomro.org/2026/01/23/pasted-image-20260123140356_q50.jpg)

  
И Вы запускаете это так:
    
    
    npm run build
    pm2 start ecosystem.config.js
    

Ваш ``ecosystem.config.js`` выглядит примерно так:
    
    
    module.exports = {
      apps: [{
        name: 'my-app',
        script: 'node_modules/next/dist/bin/next',
        args: 'start',
        instances: 2,
        exec_mode: 'cluster'
      }]
    }

Сейчас мы шаг за шагом перенесём это в Docker.

## Шаг 1: Обновите Next.js до безопасной версии

Прежде чем что-то контейнеризировать, убедитесь что у Вас безопасная версия:

Проверяем текущую версию
    
    
    npm list next

  
Обновляем до последней безопасной версии
    
    
    npm install next@latest react@latest react-dom@latest

**Безопасные версии (исправлены все уязвимости):**

Ветка | Минимальная безопасная версия  
---|---  
16.0.x | 16.0.10  
15.5.x | 15.5.9  
15.4.x | 15.4.10  
15.3.x | 15.3.8  
15.2.x | 15.2.8  
15.1.x | 15.1.11  
15.0.x | 15.0.7  
14.x | 14.2.35 (только DoS-фикс)  
  
**React (react-server-dom-*):** 19.0.1, 19.1.2, 19.2.1

> **Важно:** Версии 14.x получили только исправление DoS (CVE-2025-55184). Уязвимость утечки исходного кода (CVE-2025-55183) в них не исправлена. Рекомендуется обновиться до 15.x.

## Шаг 2: Настройте next.config.js для Docker

Откройте Ваш ``next.config.js`` и добавьте одну важную строку: ``output: 'standalone',``, пример next.config.js:

![Переносим Next.js проект из PM2 в Docker защита от критических уязвимостей 2](https://cdn.zomro.com/uploads/blog/yaroslav.baidatskyi%40zomro.org/2026/01/23/pasted-image-20260123141327_q50.jpg)

**Что делает` output: 'standalone'`?**

Когда Вы запускаете ``npm run build``, Next.js создаст папку ``.next/standalone`` с минимальным набором файлов для запуска. Это уменьшит размер Docker-образа в 3-5 раз.

После изменения пересоберите проект:
    
    
    npm run build

Проверьте, что появилась папка ``.next/standalone``:
    
    
    ls -la .next/standalone/

Вы должны увидеть файл ``server.js`` \- это и есть Ваше приложение в одном файле.

## Шаг 3: Создайте файл .dockerignore

Создайте файл `.dockerignore` в корне проекта. Он говорит Docker, что НЕ копировать в образ:
    
    
    node_modules
    
    .env
    .env.local
    .env.development
    .env.production.local
    
    .git
    .gitignore
    
    .vscode
    .idea
    
    ecosystem.config.js
    .pm2
    
    *.log
    npm-debug.log*
    
    __tests__
    *.test.js
    *.test.ts
    coverage
    
    .next
    
    .DS_Store

Разберем указаные директории, файлы:  
``node_modules`` \- зависимости, которые установим внутри контейнера  
`.env*` \- локальные env-файлы с паролями не должны попасть внутрь образа  
`.git`, `.gitignore`, `.vscode`, `.idea`, `.DS_Store` \- git, редакторы, MacOS  
`ecosystem.config.js`, `.pm2` \- pm2 нам больше не нужен  
`*.log`, `npm-debug.log*` \- логи  
`__tests__`, `*.test.js`, `*.test.ts`, `coverage` \- тесты  
`.next` \- результаты сборки (соберем внутри контейнера)

**Почему это важно?**

Если не создать `.dockerignore`, в Ваш образ попадут все файлы, включая `.env` с паролями. Любой, кто получит доступ к образу, увидит Ваши секреты.

## Шаг 4: Создайте Dockerfile

Создайте файл `Dockerfile` (без расширения) в корне проекта:
    
    
    FROM node:20-alpine AS deps
    
    WORKDIR /app
    COPY package.json package-lock.json* ./
    RUN npm ci --only=production
    
    
    FROM node:20-alpine AS builder
    
    WORKDIR /app
    COPY --from=deps /app/node_modules ./node_modules
    COPY . .
    RUN npm ci
    
    ENV NEXT_TELEMETRY_DISABLED=1
    RUN npm run build
    
    
    FROM node:20-alpine AS runner
    
    WORKDIR /app
    ENV NODE_ENV=production
    ENV NEXT_TELEMETRY_DISABLED=1
    
    RUN apk add --no-cache dumb-init
    
    RUN addgroup --system --gid 1001 nodejs
    RUN adduser --system --uid 1001 nextjs
    
    COPY --from=builder /app/public ./public
    COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
    COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
    
    USER nextjs
    EXPOSE 3000
    ENV PORT=3000
    ENV HOSTNAME="0.0.0.0"
    
    CMD ["dumb-init", "node", "server.js"]

**Что тут происходит?**

Мы используем **multi-stage build** \- три этапа сборки:

  1. **deps** \- только устанавливаем зависимости
  2. **builder** \- собираем приложение
  3. **runner** \- финальный образ только с тем, что нужно для запуска



В финальный образ НЕ попадают:

  * Исходный код (только скомпилированный)
  * devDependencies
  * Файлы сборки



Это и безопаснее, и образ получается маленький (примерно 150-200 МБ вместо 1+ ГБ).

Также в этом Dockerfile мы используем Alpine образы, которые являються минималистичным Linux, поскольку чем меньше размер изначального образа тем меньше уязвимостей. Также устанавливаем dumb-init, поскольку Node.js плохо работает как главный процесс (PID 1) в контейнере, а dumb-init решает эту проблему. Используем отдельного пользователя чтобы наше приложение не работало от root пользователя.

## Шаг 5: Создайте docker-compose.yml

Docker Compose упрощает запуск. Создайте файл `docker-compose.yml`:
    
    
    services:
      app:
        build:
          context: .
          dockerfile: Dockerfile
        container_name: my-nextjs-app
        restart: unless-stopped
        ports:
          - "3000:3000"
        deploy:
          resources:
            limits:
              cpus: '1.0'
              memory: 512M
        environment:
          - NODE_ENV=production
        security_opt:
          - no-new-privileges:true
        healthcheck:
          test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
          interval: 30s
          timeout: 10s
          retries: 3
          start_period: 40s

> Раздел лимитов (`deploy: resources: limits:`) желательно изменить под мощность Вашего сервера.

В этом `docker-compose.yml` мы собираем образ из текущей директории, указываем порт доступа к проекта - 3000 (при необходимости можна изменить, досттаочно просто изменить первую цифру 3000), также ограничиваем ресурсы (это не обязательно, но рекомендуеться), запрещаем повышение привилегий и делаем проверку здоровя приложения. 

## Шаг 6: Собираем и запускаем

Теперь у Вас должна быть такая структура:

![Переносим Next.js проект из PM2 в Docker защита от критических уязвимостей 3](https://cdn.zomro.com/uploads/blog/yaroslav.baidatskyi%40zomro.org/2026/01/23/pasted-image-20260123143431_q50.jpg)

> `ecosystem.config.js` можно удалить, поскольку он больше не нужен. 

Собираем образ:
    
    
    docker compose build

Первая сборка займёт несколько минут. Последующие будут быстрее благодаря кэшированию.

Запускаем:
    
    
    docker compose up -d
    

Флаг `-d` запускает в фоновом режиме (как pm2).

Проверяем что работает:  
Смотрим статус:
    
    
    docker compose ps

Смотрим логи:
    
    
    docker compose logs -f

Проверяем в браузере:
    
    
    curl http://localhost:3000

Пример корректной работы:

![Переносим Next.js проект из PM2 в Docker защита от критических уязвимостей 4](https://cdn.zomro.com/uploads/blog/yaroslav.baidatskyi%40zomro.org/2026/01/23/pasted-image-20260123154438_q50.jpg)

## Шаг 7: Основные команды (вместо PM2)

Вот шпаргалка. Слева - как было с PM2, справа - как теперь с Docker:

Действие | PM2 | Docker Compose  
---|---|---  
Запустить | pm2 start ecosystem.config.js | docker compose up -d  
Остановить | pm2 stop all | docker compose down  
Перезапустить | pm2 restart all | docker compose restart  
Посмотреть логи | pm2 logs | docker compose logs -f  
Статус | pm2 status | docker compose ps  
Пересобрать после изменений | npm run build && pm2 restart all | docker compose up -d --build  
  
## Шаг 8: Переменные окружения (секреты)

**Важно:** Никогда не храните пароли в Dockerfile или docker-compose.yml!

Создайте файл `.env.production` (добавьте его в .gitignore!):
    
    
    DATABASE_URL=postgresql://user:password@host:5432/db
    API_SECRET=your-secret-key

Обновите `docker-compose.yml`:
    
    
    services:
      app:
        # ... остальное как было ...
        
        env_file:
          - .env.production

## Шаг 9: Запуск на сервере

На Вашем сервере:

1\. Установите Docker (Ubuntu/Debian):
    
    
    apt-get update && apt-get install ca-certificates curl && \
    install -m 0755 -d /etc/apt/keyrings && \
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc && \
    chmod a+r /etc/apt/keyrings/docker.asc && \
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \
    apt-get update && \
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

2\. Скопируйте проект на сервер (без node_modules и .next):
    
    
    rsync -avz --exclude 'node_modules' --exclude '.next' --exclude '.git' \
      ./ user@server:/path/to/app/

3\. Скопируйте `.env.production` отдельно:
    
    
    scp .env.production user@server:/path/to/app/

4\. На сервере:
    
    
    cd /path/to/app
    docker compose up -d --build
    

5\. Удалите PM2 (если был установлен глобально):
    
    
    pm2 kill
    pm2 unstartup
    npm uninstall -g pm2

## Что делать при обновлении приложения

  1. Внесите изменения в код
  2. Пересоберите и перезапустите:


    
    
    docker compose up -d --build

Docker пересоберёт только изменившиеся слои, это быстро.

## Что делать при выходе новых уязвимостей

1\. Обновите зависимости:
    
    
    npm update next react react-dom

2\. Пересоберите образ:
    
    
    docker compose build --no-cache
    docker compose up -d

Флаг `--no-cache` заставляет пересобрать всё с нуля, включая базовый образ node:20-alpine.

## Частые проблемы и решения

### "Приложение не стартует"

Смотрите логи:
    
    
    docker compose logs app

### "Не могу подключиться к базе данных"

Если база данных на том же сервере, используйте `host.docker.internal` вместо `localhost`:
    
    
    DATABASE_URL=postgresql://user:pass@host.docker.internal:5432/db

Или добавьте в docker-compose.yml:
    
    
    services:
      app:
        extra_hosts:
          - "host.docker.internal:host-gateway"

### "Образ слишком большой"

Проверьте что `.dockerignore` создан и содержит `node_modules`.

Посмотреть размер образа
    
    
    docker images | grep nextjs

Нормальный размер: 150-300 МБ. Если больше 500 МБ - что-то не так.

### "После пересборки изменения не применились"

Используйте флаг `--build`:
    
    
    docker compose up -d --build

## Если у Вас уже есть Nginx

Если на сервере уже крутится Nginx как reverse proxy (например, для SSL), **не открывайте порт 3000 наружу**.

Измените в `docker-compose.yml` секцию `ports`:
    
    
    services:
      app:
        # ... остальное как было ...
        
        # БЫЛО (доступно всем):
        # ports:
        #   - "3000:3000"
        
        # СТАЛО (только для localhost):
        ports:
          - "127.0.0.1:3000:3000"
    

Теперь контейнер доступен только с этого же сервера (localhost), а не из интернета.

В конфиге Nginx добавьте proxy_pass:
    
    
    server {
        listen 80;
        server_name your-domain.com;
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_http_version 1.1;
            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;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_cache_bypass $http_upgrade;
        }
    }
    

> Необходимо изменить домен на Ваш.

Перезапустите Nginx:
    
    
    nginx -t && systemctl reload nginx

## Заключение

Вы перенесли проект из PM2 в Docker. Теперь у Вас:

  * Изолированное окружение
  * Приложение работает от непривилегированного пользователя
  * Минимальный образ без лишнего софта
  * Простое обновление одной командой
  * Одинаковое поведение локально и на сервере



При появлении новых уязвимостей вроде CVE-2025-55182 Вам достаточно обновить зависимости и пересобрать образ. Даже если злоумышленник найдёт новую 0-day уязвимость, он окажется внутри контейнера(где будет минимум доступных команд для закрепления и експлуатации сервера), а не на Вашем хосте.

Похожие статьи

[Как установить Nginx в Docker-Compose](https://zomro.com/rus/blog/faq/317-kak-ustanovit-nginx-v-docker-compose) [Установка Yii 2.0](https://zomro.com/rus/blog/faq/208-ustanovka-yii-20) [ISP: Как включить модуль Lets Encrypt и выдать сертификат](https://zomro.com/rus/blog/faq/83-isp-kak-vklyuchit-modul-lets-encrypt-i-vydat-sertifikat) [VestaCP: Как отключить или изменить количество резервных копий (бэкапов)](https://zomro.com/rus/blog/faq/71-vestacp-kak-otklyuchit-ili-izmenit-kolichestvo-rezervnyh-kopiy-bekapov) [Docker против файервола: как закрыть уязвимости портов](https://zomro.com/rus/blog/faq/646-docker-and-linux-network-security-working-with-iptables-ufw-and-nftables)

[ Выделенные серверы от €88.80/мес ](/rus/dedicated-servers)
