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

Каталог сервисов в Docker Compose

Запуск API теперь содержит имя image, порт, окружение, mount и проверки. Если каждый раз набирать их вручную, легко подключить не тот каталог или забыть важный параметр. В этом уроке опишем сервис в compose.yaml. Результат — полный самостоятельный Compose-файл с API и постоянными данными, который затем станет основой для отдельного веб-клиента.

Compose не заменяет Dockerfile и не упаковывает volume внутрь image. Он описывает модель запуска связанных ресурсов: сервисов, сети и хранилищ. Используем текущую команду docker compose и Compose v2. Команды читатель выполняет самостоятельно; в материалах их результаты остаются ожидаемыми, а execution — not_run.

Создадим compose.yaml в корне проекта

Файл должен лежать рядом с папкой api, внутри professorweb-container-lab. Ниже его полный вариант для этого урока. Здесь пока один сервис; дополнительный frontend появится после объяснения сети и режима разработки.

name: professorweb-container-lab
services:
  api:
    build:
      context: ./api
    image: professorweb-container-lab-api:lab-10
    environment:
      PORT: "4100"
      CATALOG_FILE: /app/data/catalog.json
    ports:
      - "127.0.0.1:4100:4100"
    volumes:
      - catalog-data:/app/data
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:4100/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 3s
volumes:
  catalog-data:

Поле name задаёт имя Compose-проекта. Service api указывает контекст ./api и имя image после сборки. Environment передаёт PORT и путь каталога процессу. Ports оставляет доступ только через loopback Docker-хоста, а volume подключает постоянные данные к подготовленной папке /app/data.

Строки version в этом файле нет. В актуальной Compose Specification такое поле является устаревшим информационным элементом и не выбирает установленную версию Compose. Номер программы читатель записывает через docker compose version, как делал в паспорте окружения. Назначение верхних полей описано в справочнике Compose.

Имя проекта определяет область ресурсов

Compose использует имя проекта при создании своих объектов. В частности, именованное хранилище catalog-data по умолчанию получает имя с префиксом проекта. Это позволяет двум разным проектам иметь одинаковое логическое название volume и разные фактические данные.

Standalone volume pw-catalog-data из предыдущего урока не становится автоматически volume этого проекта. Мы намеренно начинаем Compose-сценарий с отдельными данными. Если требуется перенести изменения, понадобится осознанное копирование или явное external-хранилище; в этом занятии такую миграцию не изображаем выполненной. У читателя может уже существовать Compose-volume от предыдущего повторения, и тогда он также сохранит собственное содержимое.

Не задаём container_name. Собственное имя проекта и имя сервиса достаточно определяют учебную область, а жёсткое имя контейнера способно создавать дополнительные конфликты при повторении. Для чтения журнала и выполнения команды будем обращаться к сервису через Compose, сохраняя ту же модель при изменении фактического имени экземпляра.

Элемент Что описывает Что остаётся отдельно
build.context Вход сборки API Уже работающий контейнер не обновляется сам
image Имя результата сборки Registry-публикация не выполняется
ports Маршрут с хоста Внутренний listen задаёт приложение
volumes Подключение данных Резервная копия не создаётся автоматически
healthcheck Узкая HTTP-проверка процесса Корректность каталога не подтверждается

Освободим порт и запустим свой проект

Перед запуском остановите предыдущий собственный контейнер, который занимает host-порт 4100. Если его имя или состояние отличается, сначала прочитайте список ресурсов. Не останавливайте чужой процесс только ради совпадения команды с учебником.

docker stop pw-catalog-data
docker compose config
docker compose up --detach --build
docker compose ps
docker compose logs api

Config показывает разрешённую модель Compose и помогает найти ошибку пути или синтаксиса до запуска. Не включайте в вывод настоящие секреты: конфигурация может раскрывать подставленные значения. В нашем файле секретов нет. Up с build явно просит подготовить image перед созданием нужных экземпляров; это понятный договор вместо ожидания, что любое изменение Dockerfile автоматически соберётся от любого up.

Ожидается работающий сервис api с публикацией 127.0.0.1:4100 и подключённым проектным volume. Самостоятельный GET /api/courses должен вернуть четыре начальных записи, если volume новый. Если данные отличаются, проверьте историю хранилища, а не объявляйте образ неправильным по одному видимому заголовку.

Healthcheck проверяет указанную функцию

Healthcheck запускает Node-команду внутри контейнера и обращается к /health через его собственный loopback. Успешный HTTP-ответ даёт успешный код завершения проверки. Timeout и retries определяют, как Compose/Engine оценивают последовательность попыток; это не дополнительный маршрут браузера и не запись в каталог.

Наш /health не читает JSON. При повреждённом catalog.json контейнер может оставаться healthy, а /api/courses возвращать 503. Это согласованный учебный договор, который не стоит называть полной готовностью сайта. Для реального сервиса проверка готовности данных и зависимостей должна быть разработана отдельно, с учётом стоимости и частоты запросов.

Позже frontend будет ожидать service_healthy перед первоначальным стартом. Такое ожидание помогает упорядочить запуск, но не гарантирует вечную доступность API после него. Клиент всё равно должен обработать ошибку запроса. Возможности depends_on и healthcheck описаны в документации порядка запуска Compose.

Различим сеть контейнеров и сеть браузера

Compose создаёт для проекта сеть, где сервис может обращаться к другому по имени api и внутреннему порту 4100. Host-публикация для такого соединения не является обязательной. Когда появится веб-сервер, он сможет отправить запрос к http://api:4100 внутри этой сети.

Браузер на компьютере обычно не знает Compose-имени api. Адрес http://api:4100 в браузерном JavaScript не становится доступным из-за объявления service. Поэтому будущий клиент будет обращаться по относительному /api/courses, а dev-сервер или nginx перенаправит запрос внутри контейнерной сети. Это сохранит понятное происхождение адреса и не потребует открывать API на все интерфейсы хоста.

Контейнерный IP не стоит вписывать в исходники как постоянный адрес: при пересоздании объекта он может измениться. Имя сервиса является более подходящей частью договора сети. Детали жизненного цикла соединений и повторного разрешения DNS всё равно зависят от конкретного клиентского процесса; позже у простого nginx-примера явно обозначим эту границу.

Остановим проект с сохранением данных

docker compose stop
docker compose start
docker compose down

Stop и start работают с существующими экземплярами. Down удаляет контейнеры и сеть проекта по своей модели, а обычный named volume остаётся. Удаление volumes дополнительным параметром является другой, разрушительной операцией; не добавляйте её к обычному переходу к следующему уроку без решения о данных. Точные правила приведены в справочнике compose down.

После самостоятельной записи утилитой через docker compose exec api и повторного up ожидается сохранение заголовка в прежнем volume. Исходные snapshots архива серии этого runtime-содержимого не включают. В следующем уроке подготовим клиент Vite и отдельно опишем полный dev-файл и полный файл раздачи собранной статики, чтобы режимы не смешивались незаметно.

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