Перейти к содержанию

Dockerfile для Node.js API

В прошлом уроке подготовили API и выбрали семейство Node.js. Теперь опишем, как получить образ с этим приложением. Результат — полный Dockerfile и понятный порядок сборки. Первый вариант специально оставим простым: оптимизацию кеша сделаем после знакомства с запуском, чтобы причина перестановки инструкций была видна на нашем собственном проекте.

Dockerfile не является сценарием развёртывания всего сайта. Он описывает создание одного image и его настройки по умолчанию. Публикация порта, подключение постоянного хранилища и запуск нескольких сервисов появятся отдельно. Если поместить все ожидания в один файл, будет трудно выяснить, почему образ собрался, но браузер ничего не показывает.

Поместим Dockerfile рядом с API

Создайте api/Dockerfile без расширения. Ниже полный файл этого состояния. package.json, package-lock.json, src и data уже должны существовать после предыдущего урока.

FROM node:24-bookworm-slim
WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev
USER node
EXPOSE 4100
CMD ["node", "src/server.mjs"]

FROM выбирает начальный образ. WORKDIR задаёт рабочий каталог для следующих инструкций и для запуска приложения. COPY переносит файлы из контекста сборки в этот каталог. Скопированные материалы уже не являются живой ссылкой на папку компьютера: последующая правка исходника сама по себе не меняет image.

Флаг chown назначает владельца копируемым файлам. Официальный Node image предоставляет пользователя node, а USER делает его пользователем основного процесса. В нашем API ему нужно читать исходники и данные; в последующих упражнениях он также будет писать в подготовленную папку данных. Это уменьшает полномочия процесса относительно root, но не является полной моделью защиты приложения или хоста.

Сборка и выполнение происходят в разное время

RUN npm ci --omit=dev выполняется при сборке. У команды есть package-lock.json, и её задача — установить зафиксированный набор runtime-зависимостей. У текущего API он пустой; команда всё равно иллюстрирует договор манифеста и lockfile. Мы не заменяем отсутствующий lockfile незаметным npm install внутри сборки.

CMD определяет команду по умолчанию для запуска контейнера. Она не стартует сервер во время docker build. При создании контейнера команда станет основным процессом, если читатель явно не переопределит её. Форму JSON-массива выбрали для прямого запуска Node без промежуточного shell. Это важно для сигналов и предсказуемого определения аргументов.

EXPOSE отмечает предполагаемый порт приложения. Инструкция не публикует его на компьютере и не заменяет настройку ports или publish. Можно получить корректный image с EXPOSE, запустить контейнер и всё ещё не иметь доступа к нему по адресу localhost компьютера. Не смешивайте эту метку с сетевым правилом запуска; назначение инструкций описано в справочнике Dockerfile.

Инструкция Когда влияет Что проверяем в нашем примере
FROM Подготовка сборки Выбрано семейство Node.js 24
COPY Сборка Файлы API есть в контексте
RUN Сборка Манифест и lockfile согласованы
USER Последующие шаги и запуск Процесс API работает от node
CMD Запуск контейнера Основной процесс — server.mjs
EXPOSE Метаданные образа Указан внутренний порт 4100

Выберем правильный контекст

Из корня professorweb-container-lab читатель выполняет следующую команду. Она приведена для самостоятельного повторения; автор материалов её не выполнял.

docker build --tag professorweb-container-lab-api:lab-04 ./api

Последний аргумент ./api задаёт контекст сборки. Dockerfile по умолчанию ищется в нём, а источник COPY . . означает содержимое именно этого контекста. Точка внутри Dockerfile не указывает на любую доступную папку компьютера. Она также не означает каталог, из которого пользователь запустил команду, если контекст выбран иначе.

Для сравнения можно находиться внутри api и передать контекст точкой. При тех же файлах это описывает ту же границу входа. Однако в курсе все команды будем привязывать к корню проекта, чтобы API и будущий frontend не путались. Подмена контекста всем репозиторием может неожиданно включить ненужные файлы и усложнить пути COPY.

Имя professorweb-container-lab-api и тег lab-04 — наши обозначения учебного результата. Они не резервируют repository в registry и не загружают образ в интернет. В локальном Engine после успешной сборки должна появиться соответствующая запись. Её ID, размер и время создания будут реальными наблюдениями читателя, а не заранее заданными числами из статьи.

Прочитаем результат сборки

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

В этом Dockerfile COPY . . переносит доступное содержимое контекста, поэтому отсутствие конкретного package-lock.json обнаружится позднее на npm ci, а отсутствие server.mjs — при запуске Node. Для COPY с явно указанным именем отсутствующий вход может вызвать ошибку уже на копировании. Проверяйте состояние файлов и контекст по фактической фазе отказа. Если не удаётся получить базовый образ, причина может относиться к сети, registry или архитектуре, а не к JavaScript API. Ошибка npm ci требует сверить манифест и lockfile. Каждое сообщение относится к определённой фазе; повторный запуск контейнера не исправляет незавершённую сборку.

При описании image иногда говорят, что каждая строка Dockerfile создаёт новый файловый слой. Это слишком грубая модель: инструкции настроек также изменяют конфигурацию образа, а filesystem-изменения и метаданные не одно и то же. Для этого урока достаточно видеть последовательность операций и её входы. В теме кеша вернёмся к тому, какие изменения заставляют выполнить дорогие шаги повторно.

Почему простая копия ещё требует доработки

COPY . . удобен для небольшого каталога, но без ограничений он может переносить локальные модули, файлы окружения и журналы. Это проблема входных материалов сборки, а не поведения маршрута /api/courses. Следующий урок добавит .dockerignore, чтобы состав контекста был понятнее. Не кладите реальные секреты рядом с учебными файлами в надежде, что Docker сам отличит их от нужных исходников.

Уже собранный образ не меняется от редактирования Dockerfile. Если исправили команду запуска или владельца файлов, нужна новая сборка и новый контейнер из её результата. Старый экземпляр сохраняет свою конфигурацию. Такое поведение станет особенно заметно, когда в следующей части запустим API и будем отличать start существующего контейнера от run нового.

Полные исходные start/after этого урока есть в архиве серии. Сборка и запуск остаются not_run. После самостоятельного выполнения читатель сможет назвать выбранный контекст, объяснить разницу RUN и CMD и показать image с собственным тегом — это конкретный ожидаемый результат, который понадобится дальше.

Оглавление серии