Автор статьи, перевод которой мы публикуем сегодня, хочет рассказать о том, как упаковывать в контейнеры Docker веб-приложения, основанные на React, Express и MongoDB. Здесь будут рассмотрены особенности формирования структуры файлов и папок таких проектов, создание файлов
Ради простоты изложения я предполагаю, что у вас уже имеется работающее приложение, представленное клиентской и серверной частями, подключённое к базе данных.
Лучше всего, если клиентский и серверный код будет расположен в одной и той же папке. Код может располагаться в одном репозитории, но он может храниться и в разных репозиториях. В таком случае проекты стоит скомбинировать в одной папке с использованием команды
Дерево файлов родительского репозитория
Здесь я использовал проект, созданный с помощью Create React App и настроенный на поддержку TypeScript. Это — простой блог, содержащий несколько визуальных элементов.
Первым делом создадим файл
Откроем файл и введём в него команды, представленные ниже. Как уже было сказано, я пользуюсь в своём приложении TypeScript, поэтому мне сначала нужно собрать его. Затем нужно взять то, что получилось, и развернуть это всё в формате статических ресурсов. Для того чтобы этого достичь, я пользуюсь двухступенчатым процессом сборки образа Docker.
Первый шаг работы заключается в использовании Node.js для сборки приложения. Я использую, в качестве базового образа, образ Alpine. Это — весьма компактный образ, что благотворно скажется на размере контейнера.
Так начинается наш
Теперь надо организовать хостинг для только что созданной сборки. Для того чтобы это сделать, воспользуемся NGINX. И, опять же, это будет Alpine-версия системы. Делаем мы это, как и прежде, ради экономии места.
Здесь в папку
Это — всё, что нужно для докеризации клиентской части приложения. Итоговый
Наш Express-API тоже довольно прост. Тут, для организации конечных точек, используется технология RESTful. Конечные точки применяются для создания публикаций, для поддержки авторизации и для решения других задач. Начнём работу с создания
В ходе разработки серверной части приложения я пользовался возможностями ES6. Поэтому мне, чтобы запустить код, нужно его скомпилировать. Я решил обработать код с помощью Babel. Как вы уже, возможно, догадались, тут снова будет использован многоступенчатый процесс сборки.
Здесь всё очень похоже на тот
Я, перед сохранением паролей в базе данных, хэширую их с помощью bcrypt. Это — весьма популярный пакет, но при использовании его в образах, основанных на Alpine, наблюдаются некоторые проблемы. Тут можно столкнуться с примерно такими сообщениями об ошибках:
Это — широко известная проблема. Её решение заключается в установке дополнительных пакетов и Python перед установкой npm-пакетов.
Следующий этап сборки образа, как и в случае с клиентом, заключается в том, чтобы взять то, что было сформировано на предыдущем этапе, и запустить это с помощью Node.js.
Здесь есть ещё одна особенность, которая заключается в установке только тех пакетов, которые предназначены для работы проекта в продакшне. Babel нам больше не нужен — ведь всё уже было скомпилировано на первом шаге сборки. Далее, мы открываем порт
Вот итоговый
Последний этап нашей работы заключается в объединении контейнеров
Создадим файл
Теперь структура файлов проекта должна выглядеть так, как показано ниже.
Итоговая структура файлов проекта
Теперь внесём в
Тут всё устроено очень просто. У нас имеется три сервиса:
В разделах
И — вот ещё одна мелочь, имеющая отношение к MongoDB. В кодовой базе бэкенда нужно обновить строку подключения к базе данных. Обычно она указывает на
Но, применяя технологию Docker Compose, мы должны сделать так, чтобы она указывала бы на имя контейнера:
Финальный шаг нашей работы заключается в том, чтобы всё это запустить, выполнив в корневой папке проекта (там, где находится файл
Мы рассмотрели несложную методику контейнеризации приложений, основанных на React, Node.js и MongoDB. Полагаем, если вам это понадобится, вы сможете легко адаптировать её для своих проектов.
P.S. Мы запустили маркетплейс на сайте RUVDS. Имеющийся там образ Docker устанавливается в один клик. Проверить работу контейнеров можно на VPS. Новым клиентам бесплатно предоставляются 3 дня для тестов.
Уважаемые читатели! Пользуетесь ли вы Docker Compose?
Dockerfile
и использование технологии Docker Compose.Начало работы
Ради простоты изложения я предполагаю, что у вас уже имеется работающее приложение, представленное клиентской и серверной частями, подключённое к базе данных.
Лучше всего, если клиентский и серверный код будет расположен в одной и той же папке. Код может располагаться в одном репозитории, но он может храниться и в разных репозиториях. В таком случае проекты стоит скомбинировать в одной папке с использованием команды
git submodule
. Я поступил именно так.Дерево файлов родительского репозитория
React-приложение
Здесь я использовал проект, созданный с помощью Create React App и настроенный на поддержку TypeScript. Это — простой блог, содержащий несколько визуальных элементов.
Первым делом создадим файл
Dockerfile
в корневой директории client
. Для того чтобы это сделать, достаточно выполнить такую команду:$ touch Dockerfile
Откроем файл и введём в него команды, представленные ниже. Как уже было сказано, я пользуюсь в своём приложении TypeScript, поэтому мне сначала нужно собрать его. Затем нужно взять то, что получилось, и развернуть это всё в формате статических ресурсов. Для того чтобы этого достичь, я пользуюсь двухступенчатым процессом сборки образа Docker.
Первый шаг работы заключается в использовании Node.js для сборки приложения. Я использую, в качестве базового образа, образ Alpine. Это — весьма компактный образ, что благотворно скажется на размере контейнера.
FROM node:12-alpine as builder
WORKDIR /app
COPY package.json /app/package.json
RUN npm install --only=prod
COPY . /app
RUN npm run build
Так начинается наш
Dockerfile
. Сначала идёт команда node:12-alpine as builder
. Затем мы задаём рабочую директорию — в нашем случае это /app
. Благодаря этому в контейнере будет создана новая папка. В эту папку контейнера копируем package.json
и устанавливаем зависимости. Затем в /app
мы копируем всё из папки /services/client
. Работа завершается сборкой проекта.Теперь надо организовать хостинг для только что созданной сборки. Для того чтобы это сделать, воспользуемся NGINX. И, опять же, это будет Alpine-версия системы. Делаем мы это, как и прежде, ради экономии места.
FROM nginx:1.16.0-alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Здесь в папку
nginx
копируются результаты сборки проекта, полученные на предыдущем шаге. Затем открываем порт 80
. Именно на этом порте контейнер будет ожидать подключений. Последняя строка файла используется для запуска NGINX.Это — всё, что нужно для докеризации клиентской части приложения. Итоговый
Dockerfile
будет выглядеть так:FROM node:12-alpine as build
WORKDIR /app
COPY package.json /app/package.json
RUN npm install --only=prod
COPY . /app
RUN npm run build
FROM nginx:1.16.0-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Express-API
Наш Express-API тоже довольно прост. Тут, для организации конечных точек, используется технология RESTful. Конечные точки применяются для создания публикаций, для поддержки авторизации и для решения других задач. Начнём работу с создания
Dockerfile
в корневой директории api
. Действовать будем так же, как и прежде.В ходе разработки серверной части приложения я пользовался возможностями ES6. Поэтому мне, чтобы запустить код, нужно его скомпилировать. Я решил обработать код с помощью Babel. Как вы уже, возможно, догадались, тут снова будет использован многоступенчатый процесс сборки.
FROM node:12-alpine as builder
WORKDIR /app
COPY package.json /app/package.json
RUN apk --no-cache add --virtual builds-deps build-base python
RUN npm install
COPY . /app
RUN npm run build
Здесь всё очень похоже на тот
Dockerfile
, который мы использовали для клиентской части проекта, поэтому в подробности вдаваться мы не будем. Однако здесь есть одна особенность:RUN apk --no-cache add --virtual builds-deps build-base python
Я, перед сохранением паролей в базе данных, хэширую их с помощью bcrypt. Это — весьма популярный пакет, но при использовании его в образах, основанных на Alpine, наблюдаются некоторые проблемы. Тут можно столкнуться с примерно такими сообщениями об ошибках:
node-pre-gyp WARN Pre-built binaries not found for bcrypt@3.0.8 and node@12.16.1 (node-v72 ABI, musl) (falling back to source compile with node-gyp)
npm ERR! Failed at the bcrypt@3.0.8 install script.
Это — широко известная проблема. Её решение заключается в установке дополнительных пакетов и Python перед установкой npm-пакетов.
Следующий этап сборки образа, как и в случае с клиентом, заключается в том, чтобы взять то, что было сформировано на предыдущем этапе, и запустить это с помощью Node.js.
FROM node:12-alpine
WORKDIR /app
COPY --from=builder /app/dist /app
COPY package.json /app/package.json
RUN apk --no-cache add --virtual builds-deps build-base python
RUN npm install --only=prod
EXPOSE 8080
USER node
CMD ["node", "index.js"]
Здесь есть ещё одна особенность, которая заключается в установке только тех пакетов, которые предназначены для работы проекта в продакшне. Babel нам больше не нужен — ведь всё уже было скомпилировано на первом шаге сборки. Далее, мы открываем порт
8080
, на котором серверная часть приложения будет ожидать поступления запросов, и запускаем Node.js.Вот итоговый
Dockerfile
:FROM node:12-alpine as builder
WORKDIR /app
COPY package.json /app/package.json
RUN apk --no-cache add --virtual builds-deps build-base python
RUN npm install
COPY . /app
RUN npm run build
FROM node:12-alpine
WORKDIR /app
COPY --from=builder /app/dist /app
COPY package.json /app/package.json
RUN apk --no-cache add --virtual builds-deps build-base python
RUN npm install --only=prod
EXPOSE 8080
USER node
CMD ["node", "index.js"]
Docker Compose
Последний этап нашей работы заключается в объединении контейнеров
api
и client
с контейнером, содержащим MongoDB. Для того чтобы это сделать, воспользуемся файлом docker-compose.yml
, размещённым в корневой директории родительского репозитория. Это делается так из-за того, что из этого места есть доступ к файлам Dockerfile
для клиентской и серверной частей проекта.Создадим файл
docker-compose.yml
:$ touch docker-compose.yml
Теперь структура файлов проекта должна выглядеть так, как показано ниже.
Итоговая структура файлов проекта
Теперь внесём в
docker-compose.yml
следующие команды:version: "3"
services:
api:
build: ./services/api
ports:
- "8080:8080"
depends_on:
- db
container_name: blog-api
client:
build: ./services/client
ports:
- "80:80"
container_name: blog-client
db:
image: mongo
ports:
- "27017:27017"
container_name: blog-db
Тут всё устроено очень просто. У нас имеется три сервиса:
client
, api
и db
. Для MongoDB нет выделенного Dockerfile
— Docker загрузит соответствующий образ со своего хаба и создаст из него контейнер. Это означает, что наша база данных будет пустой, но нас, для начала, это устроит.В разделах
api
и client
имеется ключ build
, значение которого содержит путь к файлам Dockerfile
соответствующих сервисов (к корневым директориям api
и client
). Порты контейнеров, назначенные в файлах Dockerfile
, будут открыты в сети, организуемой Docker Compose. Это позволит приложениям взаимодействовать. При настройке сервиса api
, кроме того, используется ключ depends_on
. Он сообщает Docker о том, что, прежде чем запускать этот сервис, нужно дождаться полного запуска контейнера db
. Благодаря этому мы сможем предотвратить возникновение ошибок в контейнере api
.И — вот ещё одна мелочь, имеющая отношение к MongoDB. В кодовой базе бэкенда нужно обновить строку подключения к базе данных. Обычно она указывает на
localhost
:mongodb://localhost:27017/blog
Но, применяя технологию Docker Compose, мы должны сделать так, чтобы она указывала бы на имя контейнера:
mongodb://blog-db:27017/blog
Финальный шаг нашей работы заключается в том, чтобы всё это запустить, выполнив в корневой папке проекта (там, где находится файл
docker-compose.yml
) следующую команду:$ docker-compose up
Итоги
Мы рассмотрели несложную методику контейнеризации приложений, основанных на React, Node.js и MongoDB. Полагаем, если вам это понадобится, вы сможете легко адаптировать её для своих проектов.
P.S. Мы запустили маркетплейс на сайте RUVDS. Имеющийся там образ Docker устанавливается в один клик. Проверить работу контейнеров можно на VPS. Новым клиентам бесплатно предоставляются 3 дня для тестов.
Уважаемые читатели! Пользуетесь ли вы Docker Compose?