Перейти к содержимому
Интеграция Docker

Интеграция Docker

Docker позволяет упаковать приложение вместе со всеми зависимостями в образ, а запускать — в контейнере. Контейнер — это изолированный процесс, использующий ядро ОС хоста. В отличие от виртуальной машины, здесь не используется отдельное гостевое ядро, поэтому контейнеры запускаются быстро и занимают меньше ресурсов.

Но наше приложение не всегда будет состоять из одного контейнера. В дальнейшем рядом с фронтендом нам понадобятся база данных и прокси-сервер. В этом случае используется Docker Compose — инструмент, который описывает мультиконтейнерное приложение в одном YAML‑файле (сервисы, сети, тома) и позволяет поднять всё одной командой docker compose up. Сейчас используется Compose v2 (встроен в Docker CLI как плагин docker compose).

Раньше использовалась отдельная утилита docker-compose (Compose v1), сейчас дефис уже не нужен. А version: "3", которую можно встретить в старых YAML-файлах, — это версия формата файла, а не самого Compose.

Установка Docker

Формально есть несколько способов поставить Docker на каждую из платформ, но здесь рассмотрим только самые классические и удобные.

Windows 10/11: Docker Desktop + WSL 2

  1. Скачайте и установите Docker Desktop for Windows. Во время установки выберите опцию с включением WSL 2 (Windows Subsystem for Linux). После установки включите опцию Use WSL 2 based engine в Settings > General.
  2. (При необходимости) Установите и обновите сам WSL 2 по инструкции Microsoft, затем в Docker Desktop включите интеграцию с выбранным дистрибутивом WSL (Settings > Resources > WSL Integration).
  3. Проверьте установку:
    docker --version
    docker compose version
    docker run hello-world
    Сообщение Hello from Docker! означает, что всё работает.

Если будете хранить исходники внутри файловой системы WSL 2 (например, /home/<user>/projects/...) — это немного ускорит bind‑mount (процесс монтирования) при старте. У Docker Desktop есть официальные рекомендации по производительности WSL 2. ([Docker Documentation][6])

Базовые команды Docker и Compose

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

  • Сборка образа — docker build -t myapp:dev . собирает образ по Dockerfile. По умолчанию используется BuildKit/Buildx.
  • Запуск контейнера — docker run --rm -p 8080:80 myapp:dev запускает контейнер, пробрасывая порт. По этому порту потом можно обратиться к содержимому контейнера из основной хост-системы.
  • Список контейнеров — docker ps (только запущенные) или docker ps -a (все).
  • Логи и доступ внутрь — docker logs <container> и docker exec -it <container> sh позволяют посмотреть вывод и попасть в shell конкретного контейнера.

Но, как мы с вами уже сказали, чаще всего наше взаимодействие будет не напрямую с Docker, а с его плагином, который позволит объединить несколько контейнеров в единую сеть, чтобы они могли выступать в качестве компонентов / ресурсов нашей единой информационной системы.

С Compose v2 вам, скорее всего, понадобится следующий набор основных команд:

  • Поднять приложение — docker compose up, в фоне -d, форс‑сборка --build.
  • Остановить и убрать всё созданное — docker compose down.
  • Состояние, логи, выполнение команды — docker compose ps, docker compose logs -f, docker compose exec.

Раньше в compose-файлах ещё использовался ключ version, но после перехода на Compose v2 он был помечен как deprecated.

Также несколько файлов конфигурации можно совмещать через механизм override, который описан в документации Docker Compose. (Docker Documentation). Пример с его использованием мы рассмотрим чуть позже.

Dockerfile

Прежде чем мы что-то сможем запустить, нам нужно написать инструкции, каким образом наш проект в принципе должен собираться и стартовать. Для этого используется Dockerfile — текстовый рецепт сборки образа. Строится послойно, выполняя инструкции сверху вниз. Файл должен начинаться с FROM (кроме глобальных ARG/директив парсера).

Минимальный набор инструкций, с которым вам стоит познакомиться:

Инструкция Назначение / пример
FROM <image>[:tag] Базовый образ. FROM node:24-alpine
WORKDIR <path> Рабочий каталог для последующих команд. WORKDIR /app
COPY <src> <dest> Копирование файлов в образ. COPY package*.json .
ADD Как COPY, но с распаковкой архивов/URL; используйте COPY по умолчанию.
RUN <cmd> Команда на этапе сборки. RUN npm ci
ENV k v / ARG k=v Переменные окружения и аргументы сборки.
EXPOSE <port> Документация порта (для людей/инструментов).
CMD ["..."] Команда по умолчанию при запуске контейнера.
ENTRYPOINT ["..."] Исполняемый процесс PID 1, сочетается с CMD. Чаще всего используется для запуска кастомных скриптов при старте контейнера
HEALTHCHECK ... Проверка здоровья контейнера (опционально).
LABEL Метаданные образа.

Но кроме самого Dockerfile при сборке почти всегда необходим ещё один файл — .dockerignore (похож на уже знакомый вам .gitignore). Он исключает папки/файлы из контекста сборки — ускоряет и снижает размер образа. Для Node‑проекта обычно игнорируют node_modules, dist, .git, *.log, .env* и т. п. (Docker Documentation)

Dockerfile для нашего проекта

В dev‑режиме Vite слушает порт 5173 и по умолчанию биндится на localhost (это вы могли заметить, когда после написания npm run dev вам необходимо перейти в браузере по маршруту http://localhost:5173, чтобы открыть и просмотреть базовую страницу). В контейнере кроме этого нам нужно будет явно включить прослушивание на всех интерфейсах (--host 0.0.0.0) и зафиксировать порт в самом Vite strictPort: true, чтобы при возникновении конфликта Vite не попробовал переключиться на соседние порты.

Примерная структура того, что мы с вами добавим:

    • …
  • Dockerfile
  • docker-compose.dev.yml
  • .dockerignore
  • vite.config.ts
  • package.json

Порт зафиксируем опцией strictPort, чтобы Vite не переключался на другой, если 5173 занят:

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),  // оставляем alias из прошлой лабы
    },
  },
  server: {
    host: true,        // эквивалент "0.0.0.0"
    port: 5173,
    strictPort: true,  // порт прокинут в compose как 5173:5173, автосмена сломала бы проброс
    // В Docker на Windows-хосте события ФС не долетают в контейнер — включаем polling флагом из compose
    watch: { usePolling: !!process.env.CHOKIDAR_USEPOLLING }
  }
})

Подробнее изучить опции server.host, –host и –strictPort можете в официальных руководствах Vite (Vite).

.dockerignore:

.dockerignore
node_modules
dist
.git
.vscode
.DS_Store
*.log
.env*

Dockerfile — собираем dev‑контейнер на Node 24 Alpine (в сборках вы практически всегда будете видеть Alpine, т.к. это один из самых легковесных Linux-образов, и размер финального контейнера с его использованием получится минимальным):

Dockerfile
# Стейдж deps, общие зависимости
FROM node:24-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Стейдж dev, Vite с HMR
FROM node:24-alpine AS dev
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 5173
CMD ["npm","run","dev"]
docker-compose.dev.yml
services:
  frontend:
    build:
      context: .
      dockerfile: Dockerfile
      target: dev
    ports:
      - "5173:5173"            # хост:контейнер
    volumes:
      - .:/app                 # bind-mount кода, чтобы HMR видел правки без пересборки образа
      - /app/node_modules      # анонимный том, чтобы не затирать node_modules
    environment:
      # флаг читается в vite.config через server.watch
      - CHOKIDAR_USEPOLLING=true

Запуск dev‑окружения:

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

При сборке проекта под Windows можете попробовать сохранить проект внутри WSL (например, \\wsl.localhost\Ubuntu\home\..., то есть /home/... внутри WSL) — это ускоряет горячую перезагрузку и работу файловой системы с bind‑mount.

Собираем в prod

В продакшне нам нужен минимальный образ с уже собранными статическими файлами. Для этого используем multi‑stage build: отдельный этап «builder» собирает dist/, а финальный — на nginx:alpine — только раздаёт статику.

Dockerfile (дополненный):

Dockerfile
# стейджи deps и dev из блока выше
# ...

# Стейдж build, собираем статическую дистрибуцию
FROM node:24-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Стейдж prod, nginx раздаёт статику
FROM nginx:1.29-alpine AS prod
RUN rm -f /etc/nginx/conf.d/default.conf
COPY ./nginx/nginx.conf /etc/nginx/conf.d/app.conf
COPY --from=build /app/dist/ /usr/share/nginx/html/
EXPOSE 80

nginx/nginx.conf — конфиг Nginx для SPA:

nginx/nginx.conf
server {
  listen       80;
  server_name  _;

  root /usr/share/nginx/html;
  index index.html;

  # index.html не кэшируем, чтобы клиент всегда получал свежие хэшированные имена бандлов
  location = /index.html { expires -1; }

  # Vite хеширует имена ассетов — их можно кэшировать надолго, имя меняется при каждой сборке
  location ~* \.(?:js|css|svg|png|jpg|jpeg|gif|ico|woff2?)$ {
    expires 7d;
    add_header Cache-Control "public, max-age=604800, immutable";
    try_files $uri =404;
  }

  # SPA fallback, неизвестные пути отдаём index.html, чтобы клиентский роутер обработал маршрут сам
  location / {
    try_files $uri $uri/ /index.html;
  }
}

Также не забудем создать и свой docker-compose.yml файл для prod сборки:

docker-compose.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
      target: prod
    ports:
      - "80:80"

Запуск теперь выполняется командой docker compose -f docker-compose.yml up -d.

Как можно улучшить приведённую сверху конфигурацию? Ранее мы уже упоминали про механизм override (перезаписи), его можно применить тут, чтобы задать одну базовую конфигурацию и перезаписывать её в зависимости от активного режима (dev/prod). Создадим файл compose.base.yaml:

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile

А теперь создадим его вариации, которые будут запускаться в зависимости от активного режима.

# compose.dev.yaml
services:
  web:
    build:
      target: dev
    ports:
      - "5173:5173"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true

И prod вариант:

# compose.prod.yaml
services:
  web:
    build:
      target: prod
    ports:
      - "80:80"

Теперь запуск тестовой сборки можно производить при помощи команды docker compose -f compose.base.yaml -f compose.dev.yaml up -d --build, а в prod-режиме имя второго файла изменится на compose.prod.yaml. Основное преимущество такого подхода в том, что мы получаем возможность централизованно менять базовую конфигурацию в едином источнике правды, а не в каждом из файлов по отдельности.

Что дальше

В этой лабораторной мы собрали Docker-окружение для одного фронтенда. В следующей лабораторной мы напишем настоящий backend на Fastify и научим фронт ходить за реальными данными — а пока с моками. Заодно слегка перетряхнём структуру: проект из текущего корня room-assets/ переедет в подпапку frontend/, рядом появится backend/, и compose-файлы вместо docker-compose.*.yml мы перепишем под современный формат compose.*.yaml с механизмом override (тот самый, который мы только что обсудили в callout выше).

Ещё стоит посмотреть

  • Что такое контейнеры и Docker — Docker Overview / Concepts. (Docker Documentation)
  • Compose: обзор, спецификация, установка — Compose overview, file reference, install. (Docker Documentation)
  • Установка — Docker Desktop (Windows/macOS), WSL 2 backend и best practices, convenience script для Linux. (Docker Documentation)
  • Dockerfile reference, .dockerignore, multi‑stage builds — официальные референсы. (Docker Documentation)
  • Vite server options (порт/хост) — официальные опции server.port, server.host. (vitejs)
  • Nginx — статика/try_files, reverse‑proxy/proxy_pass. (NGINX Documentation)