«У меня работает» — фраза, которую Docker должен уничтожить. Для фронтендера это не про Kubernetes, а про две вещи: воспроизводимую сборку и окружение, которое новый человек в команде запускает одной командой.
Многоэтапный Dockerfile
Первый этап собирает приложение на образе с Node. Второй берёт только готовую папку dist и отдаёт её через nginx. Итоговый образ не содержит ни Node, ни node_modules — весит десятки мегабайт вместо сотен.
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
Порядок строк важен. Сначала копируем только package*.json и ставим зависимости — Docker закеширует этот слой, и изменение самого кода не заставит всё переустанавливать.
.dockerignore
Без него в образ попадёт локальный node_modules, сборка будет дольше и может отличаться от той, что на CI.
node_modules
dist
.git
.env*
Compose для локальной работы
Для разработки мы не собираем образ на каждое изменение. Монтируем код как том и запускаем dev-сервер Vite в контейнере. Второй том, /app/node_modules, не даёт зависимостям из контейнера смешиваться с теми, что на вашем диске.
services:
web:
image: node:22-alpine
working_dir: /app
command: sh -c "npm ci && npm run dev -- --host"
ports:
- "5173:5173"
volumes:
- .:/app
- /app/node_modules
Три команды на каждый день
| Команда | Что делает |
|---|---|
docker compose up | запускает окружение разработки |
docker build -t app . | собирает продакшн-образ |
docker run -p 8080:80 app | отдаёт сборку на localhost:8080 |
Осторожно с SPA Стандартный nginx вернёт 404 для маршрутов роутера после обновления страницы. Добавьте свой конфиг с try_files $uri /index.html;.
Вот и всё. С этими двумя файлами сборка на CI, у вас и у коллеги с новым ноутбуком даёт одинаковый результат.
