Каталог сервисов в 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-файл и полный файл раздачи собранной статики, чтобы режимы не смешивались незаметно.