Docker-контейнеризация Vite-приложения

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


Базовые принципы контейнеризации Vite

Vite-приложение в продакшене представляет собой статический набор файлов после команды сборки. Эти файлы не требуют выполнения Node.js в runtime, если они обслуживаются веб-сервером. Однако сам процесс сборки требует Node.js.

Типовой жизненный цикл выглядит следующим образом:

  1. Установка зависимостей
  2. Сборка проекта (vite build)
  3. Получение каталога dist
  4. Раздача статических файлов через веб-сервер

Контейнеризация может включать как этап сборки, так и этап раздачи.


Многоступенчатая сборка (multi-stage build)

Наиболее эффективный подход — использование multi-stage build в Docker.

Структура:

  • Stage 1: сборка приложения на Node.js
  • Stage 2: минимальный runtime для раздачи статики

Пример Dockerfile:

# Stage 1: build
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

RUN npm run build


# Stage 2: production server
FROM nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Разбор этапов сборки

Stage 1: Node.js окружение

Используется минимальный образ Node.js на базе Alpine Linux для уменьшения размера контейнера.

Ключевые операции:

  • npm ci обеспечивает детерминированную установку зависимостей
  • копирование исходного кода
  • выполнение vite build, который генерирует production-версию проекта

Vite в этом режиме выполняет:

  • bundling модулей
  • tree-shaking
  • оптимизацию ассетов
  • генерацию хешированных файлов для кеширования

Stage 2: runtime через Nginx

Для продакшена используется Nginx, так как он оптимизирован под раздачу статических файлов.

Основные причины выбора:

  • низкое потребление памяти
  • высокая производительность при отдаче статики
  • простота конфигурации маршрутизации SPA

Конфигурация Nginx для SPA

Vite-приложения часто являются SPA, поэтому требуется fallback на index.html.

Пример конфигурации:

server {
  listen 80;

  root /usr/share/nginx/html;

  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }

  gzip on;
  gzip_types text/css application/javascript application/json image/svg+xml;
}

Dockerfile с кастомным nginx-конфигом

FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

RUN npm run build


FROM nginx:alpine

COPY ./nginx.conf /etc/nginx/conf.d/default.conf

COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Dev-режим в Docker

Для разработки контейнер может запускать dev-server Vite.

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

EXPOSE 5173

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

Ключевой момент — --host, иначе сервер будет доступен только внутри контейнера.


Docker Compose для удобства разработки

version: "3.9"

services:
  vite-app:
    build: .
    ports:
      - "5173:5173"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true

Особенности:

  • volume монтирует код для hot reload
  • исключение node_modules предотвращает конфликты зависимостей
  • polling активируется для корректного отслеживания изменений в контейнере

Работа с переменными окружения

Vite использует .env файлы, которые подхватываются на этапе сборки.

В Docker важно понимать:

  • переменные в runtime не влияют на уже собранный bundle
  • изменения требуют пересборки контейнера

Пример:

VITE_API_URL=https://api.example.com

Docker build:

docker build --build-arg VITE_API_URL=https://api.example.com .

И Dockerfile:

ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL

Кеширование зависимостей

Оптимизация Docker-сборки критична для Vite-проектов.

Правильный порядок слоёв:

  1. package.json
  2. npm ci
  3. исходный код

Это позволяет Docker использовать кеш при изменении только исходников.


Уменьшение размера образа

Практики оптимизации:

  • использование alpine образов
  • multi-stage build
  • удаление devDependencies в production
  • минимизация слоёв

Пример:

RUN npm prune --production

Обслуживание SPA маршрутизации

Vite часто используется с клиентским роутингом (например, React Router). Без правильного fallback сервер вернёт 404 при прямом заходе на маршрут.

Решение реализуется через:

  • try_files в Nginx
  • либо fallback middleware в Node (для dev-сценариев)

Безопасность контейнера

Контейнеризация снижает поверхность атаки, но требует дополнительных мер:

  • запуск Nginx без root-прав (при кастомной конфигурации)
  • отсутствие лишних пакетов в runtime образе
  • фиксация версий базовых образов
  • запрет записи в filesystem контейнера

CI/CD сценарий с Docker

Типовой pipeline:

  1. checkout репозитория
  2. сборка Docker image
  3. прогон тестов (опционально внутри контейнера Node)
  4. публикация образа
  5. деплой на сервер

Пример тега:

docker build -t my-vite-app:1.0.0 .

Горячая замена модулей в контейнере

Hot Module Replacement в Vite требует проброса портов и файловой синхронизации.

Основные условия:

  • открытый порт 5173
  • --host 0.0.0.0
  • volume-монтирование проекта
  • polling watcher в Linux/Windows контейнерной среде

Типовые ошибки при контейнеризации

  • запуск production-сборки через dev server
  • отсутствие dist в nginx-образе
  • неправильный base path в Vite (base в config)
  • отсутствие SPA fallback
  • кеширование node_modules между разными ОС

Структура итогового production-проекта

project/
  Dockerfile
  nginx.conf
  package.json
  vite.config.js
  src/
  dist/ (генерируется)

Масштабирование подхода

Контейнеризированный Vite-проект легко интегрируется в:

  • Kubernetes
  • Docker Swarm
  • serverless контейнерные платформы
  • edge-развёртывание

Статическая природа сборки делает такие приложения предсказуемыми и легко масштабируемыми через CDN и reverse proxy слои.