Интеграция 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
- Скачайте и установите Docker Desktop for Windows. Во время установки выберите опцию с включением WSL 2 (Windows Subsystem for Linux). После установки включите опцию Use WSL 2 based engine в Settings > General.
- (При необходимости) Установите и обновите сам WSL 2 по инструкции Microsoft, затем в Docker Desktop включите интеграцию с выбранным дистрибутивом WSL (Settings > Resources > WSL Integration).
- Проверьте установку:
Сообщение Hello from Docker! означает, что всё работает.
docker --version docker compose version docker run hello-world
Если будете хранить исходники внутри файловой системы 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 занят:
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:
node_modules
dist
.git
.vscode
.DS_Store
*.log
.env*Dockerfile — собираем dev‑контейнер на Node 24 Alpine (в сборках вы практически всегда будете видеть Alpine, т.к. это один из самых легковесных Linux-образов, и размер финального контейнера с его использованием получится минимальным):
# Стейдж 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"]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 (дополненный):
# стейджи 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 80nginx/nginx.conf — конфиг Nginx для SPA:
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 сборки:
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)