Собираем оркестр контейнеров с docker-compose
Докер решает множество проблем, связанных с зависимостями и рабочими окружениями, но запуск контейнера сам по себе может стать сложностью. Иногда нам требуется поднимать сразу несколько контейнеров, а иногда помнить все переменные окружения, которые нужно скормить докеру.
Например, в моей первой статье про докер я передавал докеру флаг -p 3000:3000, чтобы указать, какой порт из контейнера опубликовать на моей машине. Если я отдам свой образ кому-то ещё, мне обязательно нужно сообщить, к какому из портов привязываться, а им — не забыть передать этот флажок. Docker-compose позволяет нам передавать свои контейнеры вместе со всеми настройками, которые им нужны для корректной работы.
Важно: Compose не поставляется вместе с докером, поэтому вам придется установить его отдельно.
Конфигурация контейнеров с docker-compose
Вся настройка конфигурации производится в yaml файле, который мы размещаем в корне нашего проекта, обычно он называется docker-compose.yml.
В нём обязательно должен лежать ключ version, в котором мы указываем используемую версию синтаксиса. Они довольно сильно отличаются, а мажорные версии не совместимы между собой, поэтому в проектах разного возраста могут лежать абсолютно разные по структуре файлы. Этого не стоит пугаться: концепт работы от этого не меняется. В статье я использую версию 3.
Для подавляющего большинства проектов достаточно будет всего одного дополнительного ключа: services. В нём мы описываем поднимаемые контейнеры и их настройки. Если мы вернемся к примеру из моей прошлой статьи и попытаемся собрать образ и поднять контейнер с опубликованным 3000 портом, то файл docker-compose.yml будет выглядеть так:
В объекте services лежит объект api, который и является описанием нашего сервиса. Название у него может быть каким угодно, главное, чтобы нам самим оно было понятно. В нём по ключу build мы указываем, как будет собираться наш образ: context отвечает за то, в какой папке будет производиться сборка, а dockerfile — название нашего докерфайла. В данном случае мы соберем докерфайл по пути api/prod.Dockerfile. Этот ключ не является обязательным, при его отсутствии мы бы собрали api/Dockerfile. Ниже, в поле ports мы указываем нужные нам привязки портов, так же, как если бы мы это делали через флаг -p .
Теперь я могу отправить свой проект кому-то вместе с этим файлом, потребуется лишь написать в терминале docker-compose up, а контейнер соберется и запустится с нужными настройками сам!
Поднимаем несколько контейнеров разом
Иногда для разработки фронта необходимо поднимать локально и бекенд, которому может потребоваться ещё и локальная база данных или какие-то дополнительные сервисы. Чтобы не следить за необходимым окружением на локальной машине, мы можем просто описать compose файл с необходимыми контейнерами! Давайте добавим к нашему примеру выше postgres как базу данных и посмотрим на получившийся конфиг:
Мы просто добавили новое поле описывающее сервис БД в services, и теперь docker-compose up поднимет сразу оба! Давайте разберемся с новыми полями, которые появились в конфиге:
depends_on
При запуске приложений из нескольких сервисов часто случаются ситуации, когда один из сервисов требует при запуске наличия другого. Например, бекенд не может встать без базы данных, как в нашем случае. Для таких ситуаций в описании сервиса можно дописать ключ depends_on и в значение передать все зависимости, старта которых он должен дождаться.
image
Если нужный нам образ уже собрали за нас и положили в Docker hub, мы можем просто указать его название в поле image вместо локальной сборки с build.
restart
В случае, когда контейнер падает, то докер не будет пытаться его поднимать снова. Поле restart позволяет переопределить это поведение: со значением always каждый раз, когда контейнер будет завершаться, докер будет пытаться его перезапустить. Подробнее о restart здесь.
environment
Если при запуске контейнера нам хочется передать ему переменные среды, мы можем сделать это при помощи объекта environment.
volumes
Вся файловая система внутри контейнера умирает вместе с ним. Если же мы хотим сохранять некоторые файлы или папки между запусками контейнера, то нам поможет ключ volumes. Грубо говоря, мы привязываем путь до двоеточия на нашей машине, к пути после двоеточия внутри контейнера. В нашем примере, всё, что контейнер положит по пути /var/lib/postgresql/data будет лежать в папке postgres-data нашего проекта. Подробнее про volumes в докере можно почитать здесь.
Рецепты
Вместо заключения покажу немного полезных команд docker-compose.
docker-compose up -d
Как и докер, compose поддерживает флаг -d, для того чтобы не подключаться в свежесозданные контейнеры, а запускать их в фоновом режиме.
docker-compose up <название сервиса>
Если нам нужно поднять не все, а лишь один из сервисов, описанных в compose файле, мы можем просто указать его название команде up, например, docker-compose up api.
docker-compose logs
Команда logs покажет логи из всех контейнеров сразу. В неё, как и в up, можно передать название сервиса, чтобы получить логи только из него. Отличие этой команды от обычного docker logs в том, что название не сгенерировано докером из двух случайных слов, а совпадает с названием нужного контейнера из compose файла.
docker-compose down
Команда-антипод up — останавливает все созданные контейнеры. В неё тоже можно передать название нужного сервиса, если мы хотим остановить только его.