Docker Compose

Введение

Docker Compose - это инструмент для определения и запуска многоконтейнерных приложений Docker.

Он использует файлы YAML для настройки служб приложения и выполняет процесс создания и запуска всех контейнеров с помощью одной команды.

Утилита docker-compose CLI позволяет пользователям выполнять команды на нескольких контейнерах одновременно, например, создавать образы, масштабировать контейнеры, запускать контейнеры, которые были остановлены, и многое другое.

Команды, связанные с манипулированием образами или пользовательскими интерактивными опциями, не актуальны в Docker Compose, поскольку они адресуются одному контейнеру.

Файл docker-compose.yml используется для определения служб приложения и включает в себя различные параметры конфигурации.

Например, параметр build определяет параметры конфигурации, такие как путь к файлу Dockerfile , параметр command позволяет переопределять команды Docker по умолчанию и многое другое.

Первая публичная бета-версия Docker Compose (версия 0.0.1) была выпущена 21 декабря 2013 года. Первая готовая к производству версия (1.0) была доступна 16 октября 2014 года.

Параллельно существуют две версии: с дефисом и без.

Более старая запускается как

docker-compose

Новая

docker compose

В системе могут быть одновременно установлены обе версии compose

Первый вариант этой статьи был написан во времена docker-compose поэтому могут попадаться как старая так и новая версия.

Установка

Подробно про установку docker compose читайте здесь

В Debian , Ubuntu и подобных

sudo apt-get -y update
sudo apt-get install -y docker-compose-plugin

В CentOS , Red Hat , Rocky и подобных

sudo yum -y update
sudo yum install -y docker-compose-plugin

Проверить версию

docker compose version

Docker Compose version v2.28.1

Или

docker-compose -v

docker-compose version 1.27.4, build 40524192

Проверить статус

Чтобы проверить состояние запущенных контейнеров нужно выполнить

docker compose ps --all

Если конейнеры запущены из нестандартоного файла

docker compose -f ./docker-compose-custom.yaml ps --all

Если установлен старый docker-compose (через дефис)

docker-compose ps

Изображение баннера

Простейшая структура проекта

Рассмотрим следующую структуру

lesson0 ├── docker-compose.yml ├── ubuntu-pytest │ └── Dockerfile └── ubuntu-web └── Dockerfile

Два докерфайла со стандартным названием Dockerfile лежат каждый в своей директории.

docker-compose.yml

По умолчанию файл с настройками проекта для compose называется docker-compose.yml

Команда

docker compose

требует наличие такого файла.

Если файл с настройками назвается по-другому, например docker-compose-custom.yml , то нужно указать путь до него с помощью опции -f

docker compose -f ./docker-compose-custom.yml

Пример простейшего compose файла который поднимет контейнер с Alpine Linux

services: client: image: alpine restart: always command: tail -F /dev/null

docker compose up -d
docker compose ps -a

NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS alp-client-1 alpine "tail -F /dev/null" client 13 seconds ago Up 2 seconds

При работе с compose нужно чётко различать SERVICE и NAME.

client - это имя сервиса.

alp-client-1 - это имя контейнера.

В этом примере оно выдано автоматически и не совпадает с именем сервиса, но иногда может возникнуть следующая ситуация.

services: alpine_client: image: alpine container_name: alpine_client restart: always command: tail -F /dev/null

NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS alpine_client alpine "tail -F /dev/null" alpine_client 13 seconds ago Up 2 seconds

Никаких осложнений я в этой связи не встречал, но если вы планируете кому-то объяснять структуру, для простоты рекомендую иметь разные названия.

build

build это команда, которой compose создаёт контейнеры по рецептам непосредственно из docker-compose.yml или из указанных в нём докерфайлов.

Также build - это парамет в docker-compose.yml который показывает на директорию с докерфайлом.

Чтобы показать compose где находится задуманный для данного сервиса докерфайл нужно с помощью параметра build указать директорию в которой этот докерфайл лежит

services: pytester: build: ubuntu-pytest web: build: ubuntu-web

Пример докерфайла для ubuntu-pytest.

Он просто создаёт контейнер с Ubuntu , обновляет её и устанавливает несколько утилит.

# getting base image ubuntu FROM ubuntu:22.04 LABEL maintainer="andreyolegovich.ru" CMD ["echo", "Ubuntu-pytest image is creating"] RUN apt-get -y update \ && apt-get install -y dialog \ && apt-get install -y apt-utils \ && apt-get install -y tree vim \ && apt-get install -y curl gcc g++ make \ && apt-get -y update \ && apt-get -y upgrade

Сбилдить контейнеры можно командой

docker-compose build

Если это первый раз, то придётся подождать пока закончатся все операции

up

Когда контейры созданы, их нужно запустить

Запустить все контейнеры в проекте

docker compose up

Чтобы запустить в фоновом режие (daemon) используется опция -d

docker compose up -d

Чтобы запустить из нестандартного файла

docker compose -f ./docker-compose-custom.yml up -d

В старой версии:

docker-compose up

Creating network "lesson0_default" with the default driver Creating lesson0_pytester_1 ... done Creating lesson0_web_1 ... done Attaching to lesson0_pytester_1, lesson0_web_1 pytester_1 | Ubuntu-pytest image is creating web_1 | Ubuntu-pytest image is creating lesson0_pytester_1 exited with code 0 lesson0_web_1 exited with code 0

Автоматически создаётся сеть lesson0_default, в которой наши контейнеры могут обмениваться данными.

Разберём имя lesson0_pytester_1

lesson0 - это имя проекта, полученное из имени корневой директории.

pytester - это имя контейнера

1 - это номер экземпляра контейнера.

Для запуска в режиме демона используйте опцию -d

docker-compose up -d

Creating network "lesson0_default" with the default driver Creating lesson0_web_1 ... done Creating lesson0_pytester_1 ... done

Эти контейнеры видны после выполнения обычной команды docker ps -a

docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4dcaba2ace93 lesson0_pytester "echo 'Ubuntu-pytest…" 3 minutes ago Exited (0) 3 minutes ago lesson0_pytester_1 2339db49d582 lesson0_web "echo 'Ubuntu-pytest…" 3 minutes ago Exited (0) 3 minutes ago lesson0_web_1

Изучить статус можно также одной из команд:

docker compose ps --all

docker compose -f ./docker-compose-cusom.yml ps --all

docker-compose ps

Name Command State Ports -------------------------------------------------------------------- lesson0_pytester_1 echo Ubuntu-pytest image i ... Exit 0 lesson0_web_1 echo Ubuntu-web image is c ... Exit 0

down

Остановить все контейнеры проекта можно командой

docker compose down

Если запуск был из нестандартного файла:

docker compose -f ./docker-compose-custom.yml down

В старой версии:

docker-compose down

Removing lesson0_pytester_1 ... done Removing lesson0_web_1 ... done

Индивидуальная работа с сервисами

В compose возможно работать с каждым сервисом по отдельности.

Указывать нужно имя сервиса в таком виде, в каком оно задано в docker-compose.yml а не имя контейнера (иногда они совпадают но это не обязательно)

Пересобрать только один контейнер

Если вы внесли изменения только в один Dockerfile нет смысла выключать все контейнеры и собирать всё вместе.

Можно указать имя нужного сервиса и воспользоваться командой up с опциями --detach и --build

Допустим пересобрать нужно сервис postgres

# docker-compose.yml services: postgres: …

docker-compose up --detach --build postgres

Чтобы остановить и удалить только один контейнер а также удалить все связанные с ним безымянные образы нужно выполнить

docker-compose rm -s -v service_name

В новом compose не нужно -

docker compose up --detach --build service_name

Если нужно использовать build, это можно сделать как из кэша так и без него.

docker compose build postgres

docker compose build --no-cache postgres

Перезапустить только один сервис

Для перезапуска одного сервиса достаточно указать его имя после команды restart

docker compose restart worker

Можно задать таймаут с помощью -t

docker compose restart -t 30 worker

.env файлы

Часто бывает удобно вынести некоторые значения в .env файл, специально предназначенный для хранения переменных окружения.

Типичный пример - логины и пароли, которые нежелательно коммитить в репозиторий, поэтому проще всего подтянуть из внешнего файла.

Пример из официальной документации : можно задать версию webapp прямо в компоуз файле

services: web: image: "webapp:v.15"

Но если это не удобно её можно вынести в .env

project ├── docker-compose.yml └── .env

cat .env

TAG=v1.5

vi docker-compose.yml

services: web: image: "webapp:${TAG}"

Компоуз автоматически проверяет наличие файла .env поэтому команда для запуска не меняется

docker compose up -d

Или для кастомного компоуз файла

docker compose -f docker-compose-custom.yml up -d

Если файлов вида .env несколько - с помощью флага --env-file можно указать на нужные.

Допустим, в следующем проекте нам не нужен .env.prod:

project ├── docker-compose-andrei.yml ├── .env ├── .env.prod └── .env.secrets

docker compose --env-file .env --env-file .env.secrets -f docker-compose-andrei.yml up -d

В случае совпадающих переменных используются переменные из последнего указанного файла.

Желательно указывать файлы с переменными окружения и в командах down, ps -a и т.д.

docker compose --env-file .env --env-file .env.secrets -f docker-compose-andrei.yml down

docker compose --env-file .env --env-file .env.secrets -f docker-compose-andrei.yml ps -a

Пример проекта

qa-demo-project ├── docker-compose.yml ├── dockerfiles │ ├── Dockerfile.ubuntu │ ├── Dockerfile.ubuntu.js │ ├── Dockerfile.ubuntu.playwright │ └── Dockerfile.ubuntu.pytest ├── flask-service │ ├── Dockerfile │ ├── requirements.txt │ ├── src │ │ └── app.py │ └── venv │ ├── GNUmakefile ├── logs ├── README.md └── src ├── dev │ ├── app │ │ ├── __init__.py │ │ ├── file_helper.py │ │ ├── main.py │ │ ├── prod.py │ │ ├── psum.py │ │ ├── quadratic.py │ │ └── test_example.py │ └── __init__.py ├── GNUmakefile └── tests ├── js │ └── src ├── pytest │ ├── requirements │ └── src └── robot ├── requirements └── src

docker-compose.yml

version: '3.7' services: ubuntu: # аналог docker run --name container_name: pytest_ubuntu image: pytest_ubuntu build: context: ./dockerfiles dockerfile: Dockerfile.ubuntu args: buildversion: 1 ports: - "3000:3000" volumes: - ./src:/opt tty: true command: tail -F /dev/null flaskservice: build: flask-service ports: - "5000:5000"

flask-service Dockerfile

# set base image (host OS) FROM python # set the working directory in the container WORKDIR /code # copy the dependencies file to the working directory COPY requirements.txt . # install dependencies RUN pip install -r requirements.txt # copy the content of the local src directory to the working directory COPY src/ . # command to run on container start CMD [ "python", "./app.py" ]

docker-compose build docker-compose up -d docker exec -it 7e3d2711b4ab bash python -m pytest -v tests/pytest/src/tests/app

# docker-compose.yml version: '3.8' services: pytest_ubuntu: container_name: pytest_ubuntu image: pytest_ubuntu # priveleged: true # user: root build: context: ./dockerfiles dockerfile: Dockerfile.ubuntu.pytest args: buildversion: 1 ports: - "3000:3000" networks: net1: ipv4_address: 10.5.0.2 extra_hosts: - flask.andrei.com:10.5.0.3 - jenkins.andrei.com:10.7.0.6 volumes: - ./src:/opt tty: true command: tail -F /dev/null flask_service: build: flask_service ports: - "5000:5000" networks: net1: ipv4_address: 10.5.0.3 extra_hosts: - jenkins.andrei.com:10.7.0.6 web: build: nginx ports: - "80:80" networks: net1: ipv4_address: 10.5.0.4 extra_hosts: - flask.andrei.com:10.5.0.3 - jenkins.andrei.com:10.7.0.6 robot_ubuntu: container_name: robot_ubuntu image: robot_ubuntu build: context: ./dockerfiles dockerfile: Dockerfile.ubuntu.robotfw args: buildversion: 1 ports: - "3001:3001" networks: net1: ipv4_address: 10.5.0.5 extra_hosts: - flask.andrei.com:10.5.0.3 - jenkins.andrei.com:10.7.0.6 volumes: - ./src:/opt tty: true command: tail -F /dev/null jenkins: # image specified in Dockerfile # image: jenkins/jenkins:lts restart: always privileged: true user: root build: context: ./dockerfiles dockerfile: Dockerfile.jenkins args: buildversion: 1 ports: - "8080:8080" - "50000:50000" container_name: jenkins networks: net1: ipv4_address: 10.5.0.6 jenkins: ipv4_address: 10.7.0.6 extra_hosts: - flask.andrei.com:10.5.0.3 volumes: - /var/run/docker.sock:/var/run/docker.sock - ./jenkins:/home networks: net1: driver: bridge ipam: config: - subnet: 10.5.0.0/16 gateway: 10.5.0.1 jenkins: driver: bridge ipam: config: - subnet: 10.7.0.0/16 gateway: 10.7.0.1

Автор статьи: Андрей Олегович