JavaRush /Курсы /Docker for Spring /Файл compose.yaml: ...

Файл compose.yaml: базовая структура

Docker for Spring
15 уровень , 1 лекция
Открыта

1. compose.yaml как описание окружения

Если до этого Docker у вас был как набор команд, то Compose добавляет ровно одну ключевую идею: «пусть окружение живёт в файле, а команды будут короткими». Важно не перепутать: Compose не заменяет Docker Engine и не создаёт “другой Docker”. Он просто даёт вам удобный способ описать контейнеры и их параметры так, чтобы не держать их в голове и не копировать по чатам.

Что делает Docker Compose по сути? Он читает YAML и превращает его в те же самые действия, которые вы бы сделали руками: собрать image (если нужно), создать сеть (если нужно), создать том (если нужно), запустить контейнер(ы) с нужными портами, переменными, mount’ами. То есть Compose — это не «вторая система», а «нормальная оболочка над тем, что вы уже делали руками».

Удобно представлять это так:

flowchart TD
  A["compose.yaml (описание)"] --> B["docker compose up"]
  B --> C["Docker Engine: build image (опционально)"]
  B --> D["Docker Engine: create network (опционально)"]
  B --> E["Docker Engine: create volumes (опционально)"]
  B --> F["Docker Engine: run containers"]
  F --> G["Вы проверяете API /actuator/health и логи"]

В рамках сегодняшнего дня мы сознательно держим окружение простым: один сервис app, без внешней БД и без «зоопарка зависимостей». Это поможет понять механику Compose на чистом примере, а не утонуть в том, что конкретно не стартует: приложение или база.

2. Канонические имена и команда Compose

Когда вы начинаете искать примеры в интернете, мир делится на два лагеря: где-то показывают файл docker-compose.yml, а где-то — compose.yaml. И ещё где-то встречается команда docker-compose, а не docker compose. Это не потому, что люди любят хаос (хотя иногда кажется, что любят). Это потому, что Compose исторически эволюционировал, и сегодня у нас есть канонический современный путь.

В этом курсе мы фиксируем baseline: файл называется compose.yaml, команда — docker compose (через пробел). Это соответствует современному Compose v2, который идёт как часть Docker CLI. С точки зрения вашего будущего «взрослого» опыта это выгодно: меньше путаницы, меньше “а у меня другая версия”, меньше “почему команда не найдена”.

Практически это означает три простых правила.

Первое правило: файл лежит в корне репозитория, рядом с Dockerfile, .dockerignore, README.md. Не прячьте его в docker/compose/compose.yaml, если вы не ведёте отдельный курс по поиску файлов.

Второе правило: вы запускаете Compose из директории проекта (или явно указываете файл). Самый частый сценарий в учебном проекте будет выглядеть так:

cd docker-java-catalog-service  # Переходим в директорию проекта (где лежит compose.yaml)
docker compose up               # Поднимаем окружение по описанию из compose.yaml

Третье правило: мы не превращаем курс в археологию, но контекст полезен. Старое имя docker-compose.yml и команда docker-compose — это legacy-путь, который вы можете встретить в чужих репозиториях. Уметь прочитать — полезно. Но как основной teaching-path мы его не используем, чтобы не размножать варианты.

3. YAML в Compose: отступы — это ваши новые “скобки”

Compose-файл — это YAML. А YAML — это такой формат, который выглядит дружелюбно… пока вы не сделали лишний пробел. Если в Java вы забыли закрыть фигурную скобку, компилятор скажет: «друг, ты не закрыл блок». В YAML роль “скобок” играют отступы. И да, это тот редкий случай, когда пробелы реально важны, а не просто «чтобы было красиво».

Давайте договоримся о минимальной YAML-грамматике, которая нужна именно для Compose.

Ключ-значение — это “map” (словарь). Пример:

services:        # Раздел с сервисами окружения
  app:           # Имя сервиса (логическое имя в Compose)
    build: .     # Собирать image из текущей директории (Dockerfile по умолчанию)

Здесь services — ключ верхнего уровня, а значение — вложенный объект. У services есть ключ app, а у app есть ключ build.

Список — это “array” (массив) с -. Пример с портами:

ports:           # Список пробросов портов
  - "8080:8080"  # host:container (слева порт на вашей машине, справа — в контейнере)

ports — список, где каждый элемент — строка. Почему строка в кавычках? Потому что так надёжнее и читабельнее. YAML, конечно, умный, но мы с ним не соревнуемся в IQ — мы хотим, чтобы файл читался одинаково на всех машинах и у всех студентов.

И вот важный практический совет: когда файл ломается отступами, вы обычно получаете ошибку вида mapping values are not allowed here или что-то столь же “поэтичное”. В такие моменты не нужно вспоминать все грехи последних двух недель. Нужно просто прочитать файл сверху вниз и проверить, что уровни вложенности логичны.

Минимальный скелет compose.yaml

Compose-файл почти всегда читается от раздела services. Это точка входа. Можно сказать, что services — это как public static void main в мире окружения: тут начинается “жизнь”.

Минимально возможный Compose-файл, который вообще имеет смысл, выглядит так:

services:      # Всегда начинаем с описания сервисов
  app:         # Имя сервиса (вы будете ссылаться на него в командах: logs, exec и т.д.)
    build: .   # Источник сборки образа (контекст)

Да, всё. Три строки — и уже есть смысл: “есть сервис app, он собирается из текущей директории”. И вот теперь docker compose up --build сделает то, что вы делали бы руками: docker build ... и docker run ...

Чаще всего в реальном проекте вы добавите ports, потому что иначе сервис запустится, но будет «виден только самому себе» (контейнеру). И получается такой базовый минимум:

services:
  app:
    build: .
    ports:
      - "8080:8080"  # Публикуем порт наружу: localhost:8080 -> контейнер:8080

Теперь появляется “мост наружу”: host порт 8080 проброшен в контейнер на 8080.

Обратите внимание на то, что Compose-файл — это не место, где вы «придумываете новый способ запуска приложения». Если у вас уже есть корректный ENTRYPOINT в Dockerfile, Compose не обязан его повторять. Compose должен сказать: какой image запустить и какие runtime-параметры добавить.

4. build и image: не путать

Одна из самых частых ранних путаниц: «а мне писать build или image?». Это не вкусовщина. Это два разных сценария.

build означает: “собери image из исходников”. Compose будет использовать build context и Dockerfile (по умолчанию Dockerfile в указанной директории). Простой пример:

services:
  app:
    build: .  # Собираем image локально из Dockerfile и контекста проекта

То есть мы говорим: “бери Dockerfile из текущего проекта и собирай”.

image означает: “возьми уже готовый образ”. Обычно либо вы уже собрали его ранее (docker build -t ...), либо он лежит в registry. Пример:

services:
  app:
    image: docker-java-catalog-service:latest  # Берём готовый image по имени:тегу

И это важно: image не “собирает”, он “использует”. Поэтому если image не существует локально и не может быть вытянут, запуск не состоится.

Есть и третий, очень практичный случай: использовать build и image вместе — например, build: . плюс image: docker-java-catalog-service. Тогда build отвечает за источник сборки, а image — за имя результата. Для локальной разработки это удобно: Compose сам собирает сервис и одновременно сохраняет результат под предсказуемым именем, которое не зависит от имени директории проекта.

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

Если хочется сделать build чуть более явным (и чуть менее магическим), можно записать так:

services:
  app:
    build:
      context: .           # Где лежит контекст сборки (что отправляем Docker-демону)
      dockerfile: Dockerfile  # Явно указываем имя Dockerfile

Выглядит длиннее, зато читается как “контракт”. Но на старте курса это не обязательно — иногда краткость полезнее, чем идеологическая строгость.

5. Настройки сервиса: порты, env, тома

Когда вы переходите с docker run на Compose, самый приятный момент — вы начинаете видеть параметры запуска как структуру. Там, где раньше был длинный one-liner, теперь есть YAML, в котором глазом видно: “порты вот тут, переменные вот тут, тома вот тут”.

Чтобы мозг быстрее привык, можно держать маленькую таблицу соответствий. Это не “полный справочник Compose”, а именно мост от уже знакомых флагов docker run к ключам YAML:

Что вы делали в docker run Как это выглядит в compose.yaml Смысл на человеческом
-p 8080:8080 ports: ["8080:8080"] “Открой порт на host и прокинь в контейнер”
-e SPRING_PROFILES_ACTIVE=standalone environment: { SPRING_PROFILES_ACTIVE: standalone } “Положи переменную окружения внутрь контейнера”
-v ./data/exports:/data/exports volumes: ["./data/exports:/data/exports"] “Смонтируй каталог host в контейнер”

Простейший сервис с портом и одним env var выглядит так:

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: standalone

Этого уже достаточно, чтобы увидеть структуру: ports отвечает за внешний вход в контейнер, environment — за env vars внутри него. Если сервису нужны bind mount’ы или дополнительные параметры, они просто добавляются в соседние разделы volumes и environment, а каркас файла остаётся тем же.

Здесь хорошо видно два важных момента.

Во-первых, “настройки окружения” разделены по смыслу: порты отдельно, переменные отдельно, тома отдельно. Вы не ловите глазами \ и не ищете в строке, где там -e, а где -v.

Во-вторых, Compose не требует от вас помнить порядок флагов. В docker run можно написать -p до -e или после — Docker поймёт. Но человек читает это тяжело. YAML же по природе структурный: вы группируете по разделам, а не по порядку.

6. Верхний уровень: networks и volumes

Даже если сегодня у нас один сервис, вы уже видите, что у Compose есть сущности “уровня окружения”, а не “уровня контейнера”. Это важная мысль, потому что Compose — не “запуск одного контейнера”, а “описание стенда”.

В файле есть разделы верхнего уровня, которые часто встречаются:

services — кто участвует в окружении (контейнеры);
networks — как они соединяются по сети;
volumes — как хранится состояние/данные через named volumes.

Сегодня нам достаточно понять именно структурный момент: эти разделы пишутся на одном уровне вложенности с services, а не внутри services.app. То есть вот так (правильно):

services:
  app:
    build: .

volumes:
  exports-data:  # Объявляем named volume на верхнем уровне (как сущность окружения)

А вот так (неправильно, и YAML/Compose будут не в восторге):

services:
  app:
    build: .
    volumes:
      exports-data:  # Ошибка: здесь ожидается список подключений, а не объявление volume

Внутри сервиса вы подключаете volume, а объявляете его снаружи. Почему так? Потому что объявление — это “мы заводим сущность окружения”, а подключение — это “мы используем сущность окружения в конкретном сервисе”. Разделение похоже на Java: объявили класс отдельно, а потом используете его в конкретных местах.

Ещё раз подчеркну: мы не углубляемся сегодня в сетевую модель и DNS. Мы просто фиксируем: если вы видите networks: и volumes: на верхнем уровне — это нормально, так и должно быть. Это не “лишняя бюрократия”, а структура, которая потом спасает проект от YAML-хаоса.

7. Минимальный цикл работы: up, logs, down

Файл сам по себе ничего не запускает. Он — как build.gradle.kts: пока вы не вызвали команду, это просто текст. Поэтому важно знать минимальный “рабочий цикл”, который заменяет вам ручные docker build + docker run.

Самый базовый сценарий выглядит так:

docker compose up --build  # Поднять окружение и при необходимости пересобрать image

Флаг --build полезен, когда вы используете build: . и хотите гарантировать, что образ будет пересобран, если вы поменяли код или Dockerfile. На старте обучения это делает поведение более предсказуемым: меньше шансов “случайно запустить старый образ”.

Когда стек поднялся, вы почти сразу захотите посмотреть логи:

docker compose logs app  # Показать логи сервиса app (имя берётся из compose.yaml)

app — это имя сервиса (не контейнера). В этом и есть кайф Compose: вы думаете “сервисами окружения”, а не “случайными контейнерами”.

Когда нужно остановить окружение, используйте:

docker compose down  # Остановить и удалить созданные контейнеры/сети (по умолчанию)

И вот здесь очень приятное ощущение: вы не ищете, как именно назывался контейнер, не вспоминаете docker stop ... и docker rm .... Compose сам знает, что он создавал, и сам это аккуратно убирает.

Если вы любите “держать окно терминала чистым”, можно запускать в фоне (detached):

docker compose up -d --build  # Запуск в фоне, чтобы терминал сразу освободился

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

8. Типичные ошибки при создании и чтении compose.yaml

Ошибка №1: “Я написал YAML, но Compose ругается непонятными словами”.
Обычно это отступы. YAML не прощает “слегка съехавший уровень”, потому что уровень вложенности и есть смысл. Самый рабочий способ лечения — не пытаться угадать, а открыть файл и прочитать: servicesapp → ключи сервиса. Если какой-то ключ внезапно оказался на другом уровне, вы это увидите. И да, иногда это буквально один пробел, который стоит вам 20 минут и пары философских вопросов к мирозданию.

Ошибка №2: путаница build и image — “я думал, оно само соберётся”.
Если вы написали image: docker-java-catalog-service, Compose не обязан строить этот image. Он обязан попытаться его использовать. Если образа нет — получите ошибку. Если вы хотите сборку из текущего репозитория, используйте build: .. И наоборот, если вы хотите запускать строго конкретный готовый артефакт (особенно в команде), image становится более честным и предсказуемым.

Ошибка №3: ожидание, что Compose “поймёт всё за меня”, даже если я не описал порты.
Compose не телепат. Если вы не указали ports, приложение может работать внутри контейнера идеально, но снаружи вы его не откроете через localhost:8080. Это не поломка Spring Boot, это просто отсутствие публикации порта. Compose не публикует порты “по умолчанию”, потому что это было бы небезопасно и неожиданно.

Ошибка №4: попытка сделать первый compose.yaml слишком большим.
Очень хочется сразу добавить разделы “на вырост”: networks, volumes, restart-policies, healthchecks, profiles, depends_on, и ещё пять вещей, которые вы видели в чужом репозитории. На практике это убивает обучаемость. Правильный первый файл читается одним взглядом и отвечает на вопрос: “как запустить наш сервис в том режиме, который нам нужен прямо сейчас”. Всё остальное — лишний шум, который потом трудно отличить от реально важного.

Ошибка №5: смешивание “настроек Compose” и “настроек приложения” в одну кашу.
Compose управляет тем, как запускается контейнер: порты, переменные окружения, mount’ы. Spring Boot управляет тем, как ведёт себя приложение внутри контейнера: профиль, порт, datasource и так далее. В Compose-файле вы можете передать значения внутрь контейнера через environment, но если вы начинаете пытаться “настроить Spring Boot ключами Compose” (или наоборот) — вы быстро перестаёте понимать, где именно живёт причина поведения.

1
Задача
Docker for Spring, 15 уровень, 1 лекция
Недоступна
Минимальный `compose.yaml` с `build`
Минимальный `compose.yaml` с `build`
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ