Несколько месяцев назад я писал о том, как я создал игру Snake на основе React. В игру можно играть на http://sean-snake.netlify.com/.

Вы могли заметить, что я полагаюсь на Netlify, чтобы справиться с тяжелой работой по размещению игры и сделать ее доступной для всех, у кого есть доступ в Интернет. Все, что мне нужно было сделать, это связать репозиторий проекта GitHub с Netlify, и все заработало в мгновение ока.

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

Первый вариант, который пришел в голову, — создать виртуальную машину с помощью AWS, GCP или чего-то подобного. Затем изнутри виртуальной машины я мог клонировать репозиторий, создать приложение и запустить его оттуда. Я делал это раньше, когда мне нужно было разместить бэкэнд-скрипт моего Discord-бота (подробнее об этом можно прочитать здесь). Хотя не было ничего плохого в том, чтобы развернуть экземпляр виртуальной машины и запустить веб-приложение из этого экземпляра, на этот раз я хотел попробовать что-то другое.

На этот раз я решил докеризировать свое приложение!

Напишите Dockerfile

Во-первых, мне нужно было придумать Dockerfile, из которого будет собираться Docker-образ. Dockerfile должен будет сделать следующее:

  1. Вытяните образ узла
  2. Скопируйте весь исходный код в рабочий каталог внутри изображения.
  3. Установите зависимости на основе содержимого package.json
  4. Запустите приложение так, как я обычно запускаю его в своей локальной среде.

Ниже приведено содержимое файла Dockerfile.

# Fetching the latest node image on alpine linux
FROM node:alpine AS development
# Declaring env
ENV NODE_ENV development
# Setting up the work directory
WORKDIR /react-app
# Installing dependencies
COPY ./package.json /react-app
RUN npm install
# Copying all the files in our project
COPY . .
# Starting our application
CMD npm run dev

Все выглядело хорошо, когда я построил образ

docker build -f Dockerfile -t seanluong/snake:v1 .

Поиск неисправностей

Это было почти правильно. Когда я использовал docker run ниже, чтобы запустить приложение:

docker run -d -p 8888:8888 seanluong/snake:v1

и перешел к localhost:8888, игра Snake не была запущена.

Причина, по которой это не сработало, заключалась в том, что приложение React было создано с использованием Vite, что означает:

  • По умолчанию работает не на порту 8888, а на порту 5173.
  • Порт не отображается, если при запуске приложения не установлен правильный флаг.

Обновите порт в конфигурации Vite

Я обновил vite.config.ts, чтобы установить порт по умолчанию как 8888.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    port: 8888,
  },
})

Обновите Dockerfile, чтобы открыть порт

Затем я обновил Dockerfile, чтобы команда запуска приложения включала необходимый флаг для предоставления доступа к порту.

# <same content as before except the last line>
# Starting our application. The "-- --host" helps exposing the port
CMD npm run dev -- --host

И вуаля!

Все упомянутые здесь изменения можно найти в этом коммите.

Улучшение

Одна из проблем с этим способом докеризации приложения заключается в том, что он не очень эффективен с точки зрения использования дискового пространства. Беглый взгляд на размер изображения сказал мне, что его размер составляет 465 МБ.

% docker images
REPOSITORY        TAG       IMAGE ID       CREATED        SIZE
seanluong/snake   v1       7e297431ed1a   23 hours ago   465MB

Это не должно вызывать удивления. Если мы посмотрим на содержимое Dockerfile, то увидим, что здесь нет шага сборки. Исходный код просто копируется и запускается так же, как он обычно запускается в локальной среде без Docker.

Один из способов уменьшить размер изображения — выполнить этап сборки, чтобы весь код React (как TypeScript, так и CSS) был объединен в артефакты сборки, которые будут использоваться как статические ресурсы с веб-сервера (например, nginx). Это будет темой для другого поста.