В этой статье рассмотрим, как использовать Docker Compose для управления многоконтейнерными приложениями. Разберем, зачем нужен Docker Compose и как он помогает решать задачи разработки, тестирования и развертывания. Узнаем, как создавать и настраивать контейнеры для приложений с помощью файла docker-compose.yml. Расскажем, какие команды будут полезны в работе с контейнерами и как настраивать пользовательские сети для взаимодействия компонентов внутри проекта.
Зачем нужен Docker Compose
Docker Compose — это инструмент управления многоконтейнерными приложениями. Он упрощает разработку, развертывание и тестирование сложных проектов, где используются несколько сервисов. Ими могут быть веб-серверы, базы данных, приложения и другие компоненты.
В отличие от Docker, который работает с отдельными контейнерами, Docker Compose ориентирован на управление несколькими контейнерами одновременно. Он описывает их конфигурацию в одном файле — docker-compose.yml. Это делает его незаменимым инструментом для разработчиков и DevOps-инженеров, которые хотят автоматизировать сборку, запуск и настройку сети между контейнерами.
Сравнивать Docker Compose с Docker корректно, но важно понимать их различия. Docker — это инструмент для создания и управления отдельными контейнерами. Docker Compose же предназначен для работы с группами контейнеров, объединенных в сервисы.
Например, с помощью Docker Compose можно одной командой запустить весь проект, включая веб-сервер, базу данных и приложение, а также настроить сеть между ними.
docker-compose up
Это значительно ускоряет разработку и упрощает управление зависимостями между компонентами. За это Docker Compose и ценят в современных IT-проектах.
Что такое YAML
YAML — это человекочитаемый формат данных, который часто используется для создания конфигурационных файлов, например docker-compose.yml. Формат прост и имеет понятный синтаксис. Это делает его идеальным для описания конфигураций сервисов, сетей, портов и других параметров в Docker Compose.
В YAML используются отступы для обозначения структуры, что позволяет легко описывать сложные зависимости и настройки. Например, из файла docker-compose.yml можно указать, какие образы использовать, как настроить сеть между контейнерами, какие порты открыть на хосте и как управлять переменными окружения. Благодаря своей гибкости и удобству, YAML стал стандартом для работы с Docker Compose и другими инструментами в сфере разработки и DevOps.
Создание проекта для запуска в Docker Compose
Запуск проекта с использованием Docker Compose предполагает создание и настройку нескольких контейнеров, которые работают вместе. В этом разделе мы рассмотрим, как создать контейнеры для Nginx и PHP, чтобы они могли взаимодействовать друг с другом. Это типичный сценарий для веб-приложений, где Nginx выступает в роли веб-сервера, а PHP обрабатывает backend-логику.
Создание контейнера для Nginx
Nginx — высокопроизводительный веб-сервер, который часто используется для обслуживания статических файлов и проксирования запросов к backend-сервисам. Для создания контейнера с Nginx необходимо:
- создать Dockerfile,
- настроить конфигурацию Nginx,
- описать сервис в docker-compose.yml.
Пример Dockerfile для Nginx:
FROM nginx:latest
COPY nginx.conf /etc/nginx/nginx.conf
COPY ./html /usr/share/nginx/html
Пример минимальной конфигурации для проксирования запросов к PHP (nginx.conf):
server {
listen 80;
server_name localhost;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000; # Связь с PHP-контейнером
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Описание сервиса в docker-compose.yml:
services:
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./html:/var/www/html
depends_on:
- php
networks:
- app_network
Создание контейнера для PHP
PHP-контейнер будет обрабатывать backend-логику приложения. Для его создания необходимо:
- использовать официальный образ PHP с поддержкой FPM (FastCGI Process Manager),
- настроить PHP для работы с Nginx,
- описать сервис в docker-compose.yml.
Пример Dockerfile для PHP:
FROM php:8.2-fpm
RUN docker-php-ext-install pdo_mysql
Описание сервиса в docker-compose.yml:
services:
php:
image: php:8.2-fpm
container_name: php
volumes:
- ./php:/var/www/html # Проброс директории с PHP-кодом
networks:
- app_network
Создание файла docker-compose.yml и настройка проекта
Чтобы Nginx мог передавать запросы PHP-FPM, необходимо:
- убедиться, что оба контейнера находятся в одной сети (app_network),
- указать в конфигурации Nginx правильный адрес PHP-контейнера (в нашем примере это php:9000, где php — имя сервиса в docker-compose.yml).
Итоговый docker-compose.yml:
version: '3.8'
services:
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/html:/var/www/html
depends_on:
- php
networks:
- app_network
php:
image: php:8.2-fpm
container_name: php
volumes:
- ./php:/var/www/html
networks:
- app_network
networks:
app_network:
driver: bridge
Создайте файл index.php в директории php/:
<?php
echo "Hello from PHP!";
?>
Запустите проект командой:
docker-compose up -d
Откройте браузер и перейдите по адресу http://localhost. Вы увидите сообщение «Hello from PHP!». Оно означает успешную работу связки Nginx и PHP.
Пример структуры проекта:
project/
├── docker-compose.yml
├── nginx/
│ ├── nginx.conf
│ └── html/
├── php/
│ └── index.php
Работа с контейнерами в Docker Compose
Docker Compose предоставляет удобный способ управления контейнерами, начиная от их сборки и запуска, заканчивая остановкой и удалением.
Сборка контейнеров в Docker Compose
Используйте команду:
docker-compose build
Она позволяет создать образы на основе инструкций, указанных в docker-compose.yml.
Запуск всех сервисов
docker-compose up
Запуск контейнеров в фоновом режиме
Для этой задачи можно добавить в команду флаг -d:
docker-compose up -d
Это особенно полезно для развертывания приложений на сервере.
Остановка контейнеров без их удаления
Это делается командой:
docker-compose stop
Она приостанавливает работу всех сервисов, описанных в docker-compose.yml, но сохраняет их состояние.
Остановка и полное удаление контейнеров, сети и тома
Выполните команду:
docker-compose down
Удаление образов
Чтобы удалить образы, необходим дополнительный флаг —rmi all:
docker-compose down --rmi all
Удаление томов
А для удаления томов добавьте флаг —volumes:
docker-compose down --volumes
Выполнение команд внутри запущенного контейнера
Для выполнения команд внутри запущенного контейнера используется команда:
docker-compose exec <service_name> <command>
Здесь <service_name> — это имя сервиса, а <command> — команда, которую нужно выполнить. Например, чтобы открыть терминал в контейнере с PHP, выполните:
docker-compose exec php bash
Зависимости между сервисами
Зависимости между сервисами задаются в docker-compose.yml с помощью опции depends_on. Например, чтобы база данных запускалась перед приложением:
services:
app:
depends_on:
- db
Для запуска контейнера от имени определенного пользователя, нужно настроить опцию user в конфигурации:
services:
app:
user: "<user_id_or_user_name>:<group_id_or_group_name>"
Проброс папок между хостом и контейнером настраивается через volumes. Например:
services:
app:
volumes:
- ./app:/var/www/html
Переменные окружения задаются с помощью environment в docker-compose.yml:
services:
db:
environment:
MYSQL_ROOT_PASSWORD: password
Для просмотра логов конкретного сервиса применяется команда:
docker-compose logs <service_name>
Например, если нужно, чтобы nginx показал логи веб-сервера, нужно запустить команду:
docker-compose logs nginx
Проброс портов настраивается через ports. Например, чтобы связать порт 80 на хосте с портом 8080 в контейнере, выполните:
services:
web:
ports:
- "80:8080"
Лимиты на использование CPU и памяти задаются через deploy.resources.limits:
services:
app:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
Метки добавляются для удобства управления контейнерами. Например:
services:
web:
labels:
com.example.description: "Web service"
Docker Compose делает процесс работы с контейнерами простым и эффективным, автоматизируя множество рутинных задач.
Работа с сетью
Создание пользовательских сетей
Docker Compose позволяет создавать пользовательские сети для улучшения управления коммуникацией между контейнерами. По умолчанию Compose создает одну сеть для всех сервисов, но можно определить дополнительные сети в файле docker-compose.yml. Например, чтобы создать сеть с именем app_network, используется следующий код:
networks:
app_network:
driver: bridge
Чтобы подключить к этой сети нужные сервисы, нужно указать их в разделе networks каждого сервиса. Это полезно для сложных проектов, где требуется разделение сетевых ресурсов.
Изоляция сетей
Изоляция сетей позволяет ограничить взаимодействие между контейнерами, что повышает безопасность и упрощает управление. В Docker Compose можно создать несколько сетей и подключить к ним только определенные сервисы. Например, база данных может быть изолирована в отдельной сети, доступной только для backend-сервисов. Это делается через конфигурацию networks в docker-compose.yml:
services:
db:
networks:
- db_network
backend:
networks:
- db_network
- app_network
Здесь сервис db изолирован в сети db_network, а backend имеет доступ как к db_network, так и к app_network.
Коммуникация между контейнерами
Docker Compose упрощает коммуникацию между контейнерами, автоматически настраивая DNS-имена для каждого сервиса. Это позволяет обращаться к контейнерам по имени сервиса, указанному в docker-compose.yml. Например, если у вас есть сервис db, вы можете подключиться к нему из другого контейнера, используя имя db как хост. Пример:
services:
db:
image: postgres
app:
image: my_app
environment:
DB_HOST: db
В этом случае сервис app сможет подключиться к базе данных, используя db как хост. Docker Compose автоматически настроит сеть и DNS, чтобы обеспечить корректную коммуникацию между контейнерами.
Читайте также:
Эта публикация — текстовый вариант и сценарий для видео на YouTube (оно удобно разбито на эпизоды).
-
Плейлист на YouTube
-
Docker для новичков — #2 Все инструкции Dockerfile
-
Docker для новичков — #4 Оптимизация Dockerfile
Привет, сегодня я расскажу о том что такое Docker compose файл, из чего он состоит и как его написать.
Docker compose — команда Docker, которая позволяет запустить несколько контейнеров в Docker. Благодаря Compose-файлам можно описать взаимодействие контейнеров, правила их запуска и работы, сделать отдельный файл, который позволит запускать мультиконтейнерные приложения в помощью одной команды.
У compose есть официальная спецификация, которая детально рассказывает о том, как ее реализация должна работать. Ссылку я оставлю в описании. Ее не то чтобы стоит читать, так как там очень много текста, который вам не поможет так хорошо как эта статья или документация с сайта Docker, но знайте, что спецификация есть и к ней можно обратиться в случае каких-то проблем.
Чтобы воспользоваться Compose, вам необходимо выполнить лишь три шага:
-
Создайте Dockerfile для вашего приложения, либо же воспользуйтесь docker image с какого-нибудь репозитория, например, DockerHub
-
Создайте Docker compose файл, в котором опишите сервисы, которые должны работать вместе в изолированной среде.
-
Запустите docker compose up и Docker запустит все ваше приложение
Compose application model
Спецификация Compose позволяет определить платформонезависимое контейнерное приложение. Такое приложение является набором контейнеров, которые должны работать совместно с адекватным разделением ресурсов и каналом коммуникации.
В Docker используется термин mount, который используется для обозначения привязки какого-то ресурса, например папки с файлами, к контейнеру. Я не нашел красивого русского обозначения, поэтому буду употреблять слово “связан”. То есть это не абстрактная связь, а конкретная связь контейнера и каких-то данных вне контейнера. Учитывайте это по ходу видео.
Компоненты приложения называются сервисами.
service
— абстракция и по сути просто конфигурация того, какой конкретный image и с какими настройками должен быть запущен. То есть запуская сервис много раз вы должны получить один и тот же результат.
Сервисы взаимодействуют между собой через сеть (network
). В Compose network — это еще одна абстракция, которая позволяет устанавливать IP соединения между контейнерами, внутри которого сервисы могут коммуницировать друг с другом.
Сервисы хранят и разделяют между собой данные в volumes
. Мы говорили немного о volumes в прошлой статье о Dockerfile. Если не смотрели его, то посмотрите, там много интересного и подробного. Спецификация compose описывает то какие данные и где должны храниться.
Некоторые сервисы требуют конфигурационные данные для работы, поэтому есть отдельный концепт — config
. С точки зрения контейнера configs похожи на volumes — так как это тоже файлы, которые могут быть связаны с контейнером и читаться им.
secret
— тип конфигурационных данных для чувствительных данных. Чувствительные данные — это пароли, ключи и все то, что не должно попасть в чужие руки. Секреты так же могут быть связаны с контейнером.
project
— это индивидуальное размещение (деплой) приложения на конкретной платформе. То есть тот docker compose файл, который вы напишите — это отдельный проект. Имя проекта используется для того, чтобы группировать ресурсы вместе и изолировать их от других приложений или других таких же запущенных проектов.
Давайте рассмотрим пример приложения. Сначала он может показаться сложным, однако к концу статьи вы с легкостью сможете его прочитать и разобраться.
Это пример приложения с фронтендом и бэкендом.
frontend
сконфигурирован в рантайме при помощи HTTP Configuration файла, а также HTTPS сертификатом, который встроен в secret store приложения — то место, где хранятся секреты.
backend
хранит данные в постоянном хранилище. Оба сервиса взаимодействуют между собой при помощи изолированной backend сети, фронтенд также состоит во фронтенд сети, открывая https порт 443 наружу.
Это приложение состоит из следующих частей:
-
2 сервиса —
frontend
с imagewebapp
иbackend
с imagedatabase
-
1 секрета — HTTPS-сертификата, включенных во
frontend
-
1 конфигурации, включенной во
frontend
-
1 постоянного хранилища — volume, связанного с
backend
-
2 сетей —
frontend
иbackend
сети
Так описан compose-файл
services:
frontend:
image: example/webapp
ports:
- "443:8043"
networks:
- front-tier
- back-tier
configs:
- httpd-config
secrets:
- server-certificate
backend:
image: example/database
volumes:
- db-data:/etc/data
networks:
- back-tier
volumes:
db-data:
driver: flocker
driver_opts:
size: "10GiB"
configs:
httpd-config:
external: true
secrets:
server-certificate:
external: true
networks:
front-tier: {}
back-tier: {}
В этом примере показаны различия между volume
, secret
и config
:
-
они все связаны с контейнером, однако только
volume
может изменять данные -
secrets
иconfigs
readonly
Compose file
Compose файл — это .yaml файл, который определяет конфигурацию приложения, а именно версию файла, сервисы — контейнеры, которые должны быть запущены, сети, volumes, конфигурации и secrets.
По умолчанию используется имя compose.yaml или docker-compose.yaml, однако предпочтительно использовать первый вариант compose.yaml
Несколько compose файлов могут быть объединены вместе. Такое объединение перезаписывает или дополняет другой compose файл, собирая несколько файлов в один.
Вы можете использовать фрагменты, расширения или include-команду для работы с несколькими Compose файлами.
YAML формат стал популярным относительно недавно и он считается самым удобным для восприятия человеком. Данные в этом файле представляются как ключ: значение
, табуляция позволяет понять уровень вложенности.
В нем поддерживаются списки, ассоциативные массивы или мапы. Вы можете ознакомиться с синтаксисом на сайте yaml.org. Однако я верю, что на приведенных в этой статье примерах вы сможете понять как писать и читать файлы с таким форматом данных.
Будем называть верхним уровнем те ключи и значения, которые не имеют родительских. То есть они начинаются с начала строки и могут включать или не включать вложенные поля, однако сами не включены ни в какое другое поле.
На предыдущем примере на верхнем уровне находятся элементы services
, networks
, secrets
, configs
, volumes
.
Версия Docker compose файла
В начале docker compose файла на верхнем уровне можно указать версию compose — она опциональна. Она используется для обратной совместимости и только предоставляет информацию для разработчиков.
Версия не указывает Docker на то каким образом необходимо парсить конфигурацию из этого файла для создания приложения. Docker предпочтет использовать более новую версию. Docker пытается прочитать весь Compose файл и если некоторые поля не знакомы ему, потому что они появились в новой версии файла, то вы получите предупреждение.
Имя Docker compose файла
Имя указывает на то, как должно называться приложение, которое создается при помощи Compose файла. Если вы не укажите его явно, то оно будет сгенерировано. Как только имя определено, к нему можно обратиться по ключу COMPOSE_PROJECT_NAME
services:
foo:
image: busybox
command: echo "I'm running ${COMPOSE_PROJECT_NAME}"
Service
Сервис — это абстрактное определение какого-то ресурса, который используется для работы всего приложения. Ваше приложение состоит из нескольких ресурсов — например, бэкэнд, фронтенд и база данных. Вот каждый этот ресурс это отдельный сервис.
Сервисы представляют собой контейнеры, то есть запущенные image, и набор аргументов, поясняющих то, как этот контейнер должен работать.
В Compose файле должен быть верхнеуровневый элемент services
, который состоит из пар ключ: значение
, где ключ — это название сервиса и значение — его атрибуты.
Вы можете использовать как существующий image, который будет пулиться из какого-нибудь registry, так и собирать image при помощи Dockerfile, указав к нему путь.
Оригинальная документация состоит из описания вообще всех аргументов. В этой статье я расскажу лишь о части из них, которые используются повсеместно.
Атрибут build
— описывает путь к Dockerfile из которого будет собран image, а также параметры для этого Dockerfile, например, контекст.
services:
frontend:
image: example/webapp
build: ./webapp
backend:
image: example/database
build:
context: backend
dockerfile: ../backend.Dockerfile
В этом примере сервисы frontend
и backend
собираются из Dockerfile, который находится соответственно в ./webapp
и ../backend.Dockerfile
command
— переопределяет инструкцию CMD
, которая указана в Dockerfile этого сервиса, таким образом именно эта команда будет использоваться при запуске контейнера, а не та, что была указана в Dockerfile. Этот атрибут принимает как shell, так и exec форму команды, как и инструкция CMD
.
configs
— позволяет сервису использовать конфигурации, про которые мы говорили раньше в этом видео.
services:
redis:
image: redis:latest
configs:
- my_config
- my_other_config
configs:
my_config:
file: ./my_config.txt
my_other_config:
external: true
container_name
определяет имя для контейнера, который представлен этим сервисом. Этот атрибут принимает строку — название контейнера.
depends_on
говорит о том, в каком порядке должны быть запущены контейнеры сервисов.
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
Здесь сервис web
зависит от базы даных и от Redis, контейнер web
будет запущен только после того, как будут запущены контейнеры Redis и базы данных. Это нужно для того, чтобы ваше приложение не упало, попытавшись подключиться к еще не поднятому контейнеру, от которого оно зависит. Как и в локальной разработке, если вы пользуетесь базой, то она должна быть запущена до того, как вы запустите приложение.
entrypoint
переопределяет ENTRYPOINT
, который будет вызван при запуске контейнера. В прошлой статье мы узнали, что в Dockerfile выполняется только последняя инструкция ENTRYPOINT
, но здесь мы ее переопределяем и можем задать новое поведение при запуске контейнера.
entrypoint:
- php
- -d
- zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
- -d
- memory_limit=-1
- vendor/bin/phpunit
env_file
указывает путь к файлу с переменными, которые будут использоваться для работы контейнера. Чтобы не указывать все переменные, вы можете поместить их в один файл и сослаться на него. Вы можете передать как путь к отдельному файлу, так и список таких путей. По умолчанию это файл .env
.
env_file:
- ./a.env
- ./b.env
environment
определяет пары ключ: значение
для переменных, которые нужны при работе контейнера. Вы можете использовать два разных синтаксиса — map
и list
.
environment:
RACK_ENV: development
SHOW: "true"
USER_INPUT:
environment:
- RACK_ENV=development
- SHOW=true
- USER_INPUT
Если вы указали и env_file
и env
переменные, тогда переменные из env
имеют приоритет и будут перезаписывать переменные с таким же названием из env
файла.
image
определяет image, который будет запущен в контейнере. Вы можете указать просто название, но можете и дополнить его тегом или дайджестом. В сервисе должен быть указан либо image
либо build
атрибут, для того, чтобы Docker смог понять что должно быть запущено.
image: redis
image: redis:5
image: redis@sha256:0ed5d5928d4737458944eb604cc8509e245c3e19d02ad83935398bc4b991aac7
image: library/redis
image: docker.io/library/redis
image: my_private.registry:5000/redis
networks
— указывает список network
, к которым принадлежит сервис.
services:
some-service:
networks:
- some-network
- other-network
В этом примере сервис some-service
принадлежит к двум сетям, которые были описаны в Compose файле.
ports
определяет порты, которые отображаются наружу из контейнера. Мы говорили в прошлой статье, что EXPOSE
в Dockerfile не открывает порты, а лишь говорит разработчикам о том, что этот порт нужен для работы приложения. А вот в Compose файле ports
как раз и открывает порты наружу.
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "8000-9000:80"
- "6060:6060/udp"
Таким образом обратившись к localhost:3000 вы попадете внутрь контейнера на порт 3000. Вы можете поменять порт и обращаться к другому локальному порту, который в свою очередь будет отображен в контейнер. Порты на локальной машине должны быть доступны, иначе Compose файл не будет запущен.
pull_policy
говорит о том, должен ли image пулиться из registry при каждом запуске контейнера, чтобы был запущен именно актуальный image.
Возможные значения:
-
always
— image всегда пулятся -
never
— не пулятся никогда и должны быть на локальной машине, чтобы Compose файл мог ими воспользоваться -
missing
— Docker пулит только недостающие image -
build
— Docker будет создавать image из Dockerfile каждый раз
restart
— определяет правила, по которым контейнер будет перезапускаться, когда он упал.
Есть несколько опций:
-
no
— по умолчанию. Docker не перезапускает контейнер если он упал -
always
— Docker всегда перезапускает контейнер, пока он не будет удален -
on-failure
— Docker перезапускает контейнер, если он упал из-за ошибки, а не был остановлен правильно -
unless-stopped
— Docker перезапускает контейнер, пока сервис не будет остановлен или удален
restart: "no"
restart: always
restart: on-failure
restart: unless-stopped
secrets
— позволяет сервису обратиться к секретам, которые были указаны в текущем Compose файле.
services:
frontend:
image: example/webapp
secrets:
- server-certificate
secrets:
server-certificate:
file: ./server.cert
volumes
определяет путь хранения volume из контейнера на локальном хосте. Вы можете создать volume в какой-нибудь директории локально или же сослаться на описанный volume из текущего Compose файла.
services:
backend:
image: example/backend
volumes:
- type: volume
source: db-data
target: /data
volume:
nocopy: true
- type: bind
source: /var/run/postgres/postgres.sock
target: /var/run.postgres/postgres.sock
volumes:
db-data:
Network
Networks или сети позволяют сервисам общаться между собой. Поместив несколько сервисов в один network, они могут свободно обращаться друг к другу по названию сервиса. Это удобно и быстро конфигурируется, что делает деплой приложения в Docker простым.
В сервисе мы рассмотрели атрибут networks
, в котором указывается список сетей, к которым принадлежит сервис.
Сейчас на примерах мы рассмотрим то, как настроить сеть и какие атрибуты у нее есть.
services:
frontend:
image: example/webapp
networks:
- front-tier
- back-tier
networks:
front-tier:
back-tier:
В этом простом примере создается сервис и две сети, к обеим из которых он принадлежит. Когда сервис один, то ему сети и не нужны, так как ему не с кем общаться.
services:
proxy:
build: ./proxy
networks:
- frontend
app:
build: ./app
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend
networks:
frontend:
driver: custom-driver-1
backend:
driver: custom-driver-2
driver_opts:
foo: "1"
bar: "2"
В этом более сложном примере есть три сервиса — прокси, приложение и база данных. Этот пример напоминает тот, что мы рассмотрели в начале видео.
Прокси сервис изолирован от базы, так как они находятся в разных сетях. Но прокси может обратиться к сервису приложения.
Приложение может обратиться как к прокси, так и к базе данных, потому что принадлежит к обеим сетям, в которых находятся эти сервисы.
Также у этих сетей есть свои настройки — драйверы и их опции. Эти атрибуты необязательны, однако помогают настроить сеть.
Атрибут driver
указывает на то, какой драйвер для работы сети следует использовать. Будет выброшена ошибка, если драйвер недоступен для использования при запуске Compose файла.
Есть два типа драйверов, которые поддерживаются Compose: host
и none
. host
использует возможности хоста, то есть того компьютера, на котором запущен Compose файл, для коммуникации между сервисами, none
отключает коммуникацию.
networks:
db-data:
driver: overlay
driver_opts
определяет список опций для драйвера. Про них можно узнать из документации конкретного драйвера.
networks:
db-data:
driver_opts:
foo: "bar"
baz: 1
Если атрибут attachable
установлен в true
, тогда контейнеры вне этого Compose файла могут подключиться к сети и общаться с сервисами внутри этой сети.
networks:
mynet1:
driver: overlay
attachable: true
Если атрибут external
установлен в true
, тогда Docker ожидает, что эта сеть уже создана и будет обращаться к ней, иначе будет выброшена ошибка при запуске Compose файла. Все остальные атрибуты не будут учитываться, кроме атрибута имени, а если они отличаются от настоящих, то будет выброшена ошибка.
services:
proxy:
image: example/proxy
networks:
- outside
- default
app:
image: example/app
networks:
- default
networks:
outside:
external: true
В данном примере Compose будет использовать уже существующую сеть outside
, а не пытаться создать новую.
Если атрибут internal
установлен в true
, тогда вы запретите внешнее подключение к этой сети снаружи Compose файла, так как по умолчанию Compose предоставляет возможность внешнего подключения.
Атрибут name
устанавливает имя для сети, как и с volume, вы можете использовать переменные для подстановки значений при запуске Compose файла.
networks:
network1:
external: true
name: "${NETWORK_ID}"
Если вы используете внешний network, тогда вы можете указать настоящее имя сети, а внутри текущего Compose файла обращаться к сети с другим именем.
Volume
Volumes — это постоянные хранилища данных вашего контейнера.
Когда Docker контейнер перезапускается, все его данные, которые не хранятся в volume очищаются. Так как контейнер изолирован от окружающей среды, логично, что он будет очищен при перезапуске. Для того, чтобы сохранить важные данные, например, состояние базы данных из контейнера, необходимо создать volume, который будет хранить эти данные на хосте, на котором работает контейнер.
Верхнеуровневая конфигурация volumes
позволяет описать volumes, которые будут использованы сервисами. Как мы заметили раньше, в сервисе нужно явно указывать то, какие данные будут сохраняться и куда.
Давайте рассмотрим несколько примеров конфигурации volumes.
services:
backend:
image: example/database
volumes:
- db-data:/etc/data
backup:
image: backup-service
volumes:
- db-data:/var/lib/backup/data
volumes:
db-data:
На этом примере мы видим два сервиса, один из которых база данных, а второй — бэкап сервис, делающий бэкапы базы данных. Оба этих сервиса имеют доступ к данным из базы данных благодаря volume.
Сервис базы данных сохраняет данные в volume db-data
, а сервис бэкапа сохраняет эти данные в своем контейнере по пути /etc/data
. Таким образом данные из одного контейнера попадают во второй.
У конфигурации volume есть несколько атрибутов, но они не являются обязательными.
driver
указывает какой драйвер для volume должен быть использован. Некоторые могут быть платформозависимыми. Если драйвер недоступен, то Docker выбросит ошибку при попытке запуске Compose файла.
volumes:
db-data:
driver: foobar
driver_opts
определяет опции volume, пар ключ: значение
, они тоже платформозависимые, то есть на Windows и на Linux эти опции могут отличаться.
volumes:
example:
driver_opts:
type: "nfs"
o: "addr=10.40.0.199,nolock,soft,rw"
device: ":/docker/example"
Если атрибут external
установлен в true
, тогда Docker ожидает, что этот volume уже существует и управляется из другого Compose файла. Если такого volume не будет, то выбросится ошибка при запуске Compose файла. Также в этом случае все атрибуты кроме имени не будут учитываться, раз volume был создан и управляется из другого места. Но если эти атрибуты отличаются от оригинальных, то будет выброшена ошибка. Таким образом определяя external как true
, вы можете указать только имя volume для этого Compose файла.
services:
backend:
image: example/database
volumes:
- db-data:/etc/data
volumes:
db-data:
external: true
На этом примере volume не создается, а ищется уже существующий.
name
устанавливает имя для volume. Вы можете воспользоваться подстановкой переменных при запуске Compose файла. В данном случае имя volume будет получено из переменной DATABASE_VOLUME
из .env
файла.
volumes:
db-data:
name: ${DATABASE_VOLUME}
volumes:
db-data:
external:
name: actual-name-of-volume
В этом примере внешний volume имеет имя actual-name-of-volume
, а внутри текущего Compose файла вы можете обратиться по имени db-data
.
Config
Config позволяет сервисам изменять их поведение без необходимости изменения используемых image.
Сервисы могут обратиться к конфигам только если имеют атрибут configs
.
Как и volume, конфиги связаны с файловой системой контейнера. По умолчанию они находятся в корне файловой системы контейнера.
В конфиге указывается информация, которую могут использовать сервисы в Compose приложении. Источником конфига может быть файл или внешний источник.
-
file
— создается конфиг со всем содержимым файла -
environment
— создается конфиг со значением переменной -
content
— создается конфиг со значением, которое было передано в этом атрибуте
external
— Если указано как true
, Docker ожидает, что конфиг был создан, он не пытается его создать заново и выбрасывает ошибку, если такого конфига не существует.
name
— имя конфига, которое Docker ищет.
configs:
http_config:
file: ./httpd.conf
В этом примере создается config, значением которого будет содержимое файла. Имя для него будет <project_name>_http_config
.
configs:
app_config:
content: |
debug=${DEBUG}
spring.application.admin.enabled=${DEBUG}
spring.application.name=${COMPOSE_PROJECT_NAME}
В этом случае создается конфиг, содержимым которого является указанный текст.
Secret
Секреты используются для использования чувствительных данных. Сервисы могут обратиться к секретам только если имеют атрибут secrets
.
На верхнем уровне элемент secrets
определяет или ссылается на те самые чувствительные данные, источником которых служит либо file
либо environment
.
-
Если источник
file
, тогда создается секрет с содержимым этого файла. -
Если источник
environment
, тогда создается секрет со значением этой переменной.
external
обозначает то, что секрет уже был создан и Docker не пытается его создать. Если он не был создан, тогда вы получите ошибку при выполнении Compose файла.
name
— название секрета в Docker
Давайте рассмотрим несколько примеров.
secrets:
server-certificate:
file: ./server.cert
В этом примере создается секрет с названием <project_name>_server-certificate
в момент запуска Compose файла, а значение секрета берется из файла server.cert
.
secrets:
token:
environment: "OAUTH_TOKEN"
В этом случае секрет называется как <project_name>_token
и создается во время запуска Compose файла, значением является значение переменной.
secrets:
server-certificate:
external: true
В таком случае Docker пытается найти уже созданный секрет и использовать его.
Fragments
В Compose файле можно использовать встроенные возможности языка YAML, которые сделают Compose файл более аккуратным и эффективным.
Вы можете создать фрагменты для того, чтобы переиспользовать некоторые части файла. Например, если у вас есть несколько сервисов с одинаковой конфигурацией, вы можете объединить эту конфигурацию в одну и не дублировать ее повторно.
Фрагменты создаются при помощи символы амперсанд &
и имени фрагмента. Они устанавливаются на ключи YAML файла, раз этот формат — это ключ-значение, то подстановки происходят после двоеточия. С помощью *
и имени можно обратиться к этому фрагменту и получить его значение.
Фрагменты подставляются до подстановки переменных, поэтому здесь их нельзя использовать для создания названий.
volumes:
db-data: &default-volume
driver: default
metrics: *default-volume
В данном примере создается фрагмент default-volume
, который подставит все свойства volume db-data
в volume metrics
, то есть metrics
также будет иметь свойство driver: default
.
services:
first:
image: my-image:latest
environment: &env
- CONFIG_KEY
- EXAMPLE_KEY
- DEMO_VAR
second:
image: another-image:latest
environment: *env
В этом примере переменные одного сервиса помечаются якорем, чтобы быть переиспользоваными во втором сервисе.
Иногда вы можете хотеть переопределить значения фрагмента только частично, при помощи YAML merge вы можете это сделать как в данном примере.
services:
backend:
image: example/database
volumes:
- db-data
- metrics
volumes:
db-data: &default-volume
driver: default
name: "data"
metrics:
<<: *default-volume
name: "metrics"
В данном примере default-volume
подставит только driver
, а name
подставлен не будет, так как мы его явно указали.
Вы также можете дополнить значения фрагмента дополнительными элементами как в этом примере.
services:
first:
image: my-image:latest
environment: &env
FOO: BAR
ZOT: QUIX
second:
image: another-image:latest
environment:
<<: *env
YET_ANOTHER: VARIABLE
Но в таком случае вы должны указывать переменные в формате Ключ: Значение
, а не - Ключ=Значение
.
Если вы хотите использовать фрагменты в нескольких файлах, тогда воспользуйтесь расширениями Extension.
Extensions
Как и фрагменты, Extensions могут быть использованы для того, чтобы сделать Compose файл более эффективным и поддерживаемым.
Для этого на верхнем уровне нужно определить элемент, который начинается с x-
. Так Docker поймет, что это расширение и будет знать о нем.
Давайте рассмотрим несколько примеров:
x-env: &env
environment:
- CONFIG_KEY
- EXAMPLE_KEY
services:
first:
<<: *env
image: my-image:latest
second:
<<: *env
image: another-image:latest
В первом примере определен extension x-custom
и x-foo
, мы можем добавить x-foo
в сервис и Docker его обработает, но ничего не сделает, однако значение x-foo
можно получить в самом приложении webapp
.
Во втором примере env не принадлежит ни одному из сервисов. Такой extension определяет новую ноду, в которой содержится поле environment
. А якорь используется для того, чтобы обратиться к этому полю в двух сервисах.
x-environment: &default-environment
FOO: BAR
ZOT: QUIX
x-keys: &keys
KEY: VALUE
services:
frontend:
image: example/webapp
environment:
<< : [*default-environment, *keys]
YET_ANOTHER: VARIABLE
В этом примере можно использовать объединение из нескольких расширений, тогда все ключи из обоих расширений будут подставлены. Как и в случае с фрагментами, вы должны указывать переменные в формате Ключ: Значение
, а не - Ключ=Значение
.
Interpolation
Как и в Dockerfile, мы можем воспользоваться wildcard для подстановки значений переменных из конфигурационного файла.
Значение переменной можно получить написав ${VARIABLE}
или $VARIABLE
. Можно обратиться к вложенным переменным, например как здесь.
${VARIABLE:-${FOO}}
${VARIABLE?$FOO}
${VARIABLE:-${FOO:-default}}
Есть несколько видов подстановки.
${VARIABLE:-default} - подставит default, если переменная VARIABLE не задана или пустая
${VARIABLE-default} - подставит default только если переменная VARIABLE не задана
${VARIABLE:?err} - завершит программу с сообщением об ошибке err если переменная VARIABLE не задана или пустая
${VARIABLE?err} - завершит программу с сообщением об ошибке err только если переменная VARIABLE не задана
Если Docker не может найти переменную и нет значения по умолчанию, тогда выводится предупреждение и используется пустая строка. Стоит помнить об этом.
Если используется слияние нескольких Compose файлов, тогда подстановка происходит до слияния, чтобы не допустить ошибок.
Подстановки работают только для значений, а не ключей, поэтому такой пример не будет работать.
services:
foo:
labels:
"$VAR_NOT_INTERPOLATED_BY_COMPOSE": "BAR"
Merge
Вы можете использовать несколько compose файлов и объединить их в один, чтобы собрать свое приложение.
Давайте рассмотрим правила, которые соблюдает merge.
Если мержутся мапы, то есть пары ключ-значение, то недостающие ключи добавляются, а существующие перезаписываются.
#первый файл
services:
foo:
key1: value1
key2: value2
#второй файл
services:
foo:
key2: VALUE
key3: value3
#результат
services:
foo:
key1: value1
key2: VALUE
key3: value3
Если мержутся последовательности, тогда они дополняются.
#первый файл
services:
foo:
DNS:
- 1.1.1.1
#второй файл
services:
foo:
DNS:
- 8.8.8.8
#результат
services:
foo:
DNS:
- 1.1.1.1
- 8.8.8.8
COMMAND
, ENTRYPOINT
, HEALTHCHECK
перезаписываются, так как мы помним, что они не могут дублироваться в Dockerfile, то и здесь они не могут использоваться совместно, а только последняя инструкция будет применена.
#первый файл
services:
foo:
command: ["echo", "foo"]
#второй файл
services:
foo:
command: ["echo", "bar"]
#результат
services:
foo:
command: ["echo", "bar"]
В случае с уникальными ресурсами, такими как порты, volume, секреты и конфигурации если уникальные значения конфликтуют, тогда они будут перезаписаны.
#первый файл
services:
foo:
volumes:
- foo:/work
#второй файл
services:
foo:
volumes:
- bar:/work
#результат
services:
foo:
volumes:
- bar:/work
В качестве уникальных ресурсов выступают:
Include
Вы можете сослаться на другой Compose файл если вы хотите:
-
Переиспользовать Compose файлы,
-
Вы хотите вынести общие части в отдельные compose файлы, которые должны управляться независимо друг от друга
-
Вам нужно, чтобы Compose файл был не большого размера или включал в себя ресурсы определенной части большого приложения
Для того, чтобы подключить другой файл, вы должны указать на верхнем уровне секцию include
. Как только compose файлы из этой секции загрузились, они копируются в данный проект. Вы увидите предупреждение, если файлы конфликтуют между собой и Docker не будет сам мержить их.
Вы можете ссылаться на ресурсы из других Compose файлов, как будто эти ресурсы есть в вашем файле. Как на этом примере.
include:
- my-compose-include.yaml #здесь описан serviceB
services:
serviceA:
build: .
depends_on:
- serviceB #используем этот сервис как будто он описан прямо в этом файле
Вы также можете использовать переменные в include
для указания относительного пути папок, например для сборки приложения для разработки и production.
include:
- ${INCLUDE_PATH:?FOO}/compose.yaml
Можно использовать длинный синтаксис и более детально сконфигурировать include
.
include:
- path: ../commons/compose.yaml
project_directory: ..
env_file: ../another/.env
В данном случае path
— обязательный элемент и он определяет расположение compose файла относительно текущего. Это может быть как одна строка, так и список строк, если нужно подключить несколько Compose файлов.
project_directory
определяет путь, относительно которого относительные пути из path будут применены. По умолчанию это папка с текущим Compose файлом.
env_file
указывает путь к файлу с переменными, которые будут использоваться в указанных Compose файлах. По умолчанию это .env
файл в директории project_directory
. Тут так же можно указать как строку так и список строк. Переменные в локальной директории, то есть в той, в которой находится текущий Compose файл имеют преимущество перед импортируемыми и могут их переписать.
An open-source platform called Docker makes designing, shipping, and deploying applications simple. It runs an application in an isolated environment by compiling its dependencies into a so-called container. for additional information on Docker. In a normal case, several services, such as a database and load balancing, are required to support an application.
We’ll look at Docker Compose’s assistance with setting up many services in this article. Also, we will see a demonstration of installing and utilizing Docker Compose. Let’s try to understand docker-compose simply.
Docker Compose will execute a YAML-based multi-container application. The YAML file consists of all configurations needed to deploy containers Docker Compose, which is integrated with Docker Swarm, and provides directions for building and deploying containers. With Docker Compose, each container is constructed to run on a single host.
Table of Content
- Key Concepts in Docker Compose
- Install Docker Compose
- Install Docker Compose on Ubuntu — A Step-By-Step Guide
- Docker Container
- Why Docker Compose?
- How to Use Docker Compose?
- Docker-compose.yaml File
- Run the application stack with Docker Compose
- Important Docker Compose Commands
- Best Practices of Docker Compose
- Features of Docker Compose
Key Concepts in Docker Compose
Docker Compose is a powerful tool for managing multi-container applications, and mastering its key components—like services, networks, volumes, and environment variables—can greatly enhance its usage. Let’s break down these concepts and how they work within a Docker Compose file.
Docker Compose File (YAML Format)
Docker Compose configurations are mainly stored in a file named docker-compose.yml
, which uses YAML format to define an application’s environment. This file includes all the necessary details to set up and run your application, such as services, networks, and volumes. To use Docker Compose in an effective way you have to know the structure of this file.
Key Elements of the YAML Configuration
- Version: It defines the format of the Compose file, by ensuring compatibility with specific Docker Compose features and syntax.
- Services: The
services
section lists each containerized service required for the application. Each service can have its configuration options, such as which image to use, environment variables, and resource limits. - Networks: In network section, you can define custom networks that enable communication between containers. Additionally, it allows you to specify network drivers and custom settings for organizing container interactions.
- Volumes: Volumes allow for data persistence across container restarts and can be shared between containers if needed. They enable you to store data outside the container’s lifecycle, making it useful for shared storage or preserving application state.
Example docker-compose.yml
Here’s a sample Compose file that defines two services, a shared network, and a volume:
version: '3.8'services:
web:
image: nginx:latest
ports:
- "80:80"
networks:
- frontend
volumes:
- shared-volume:/usr/share/nginx/html
depends_on:
- appapp:
image: node:14
working_dir: /app
command: node server.js # Specify a command
networks:
- frontend
volumes:
- shared-volume:/app/datanetworks:
frontend:
driver: bridgevolumes:
shared-volume: # Remove incorrect syntax
Explanation:
- The
web
service runs an Nginx container, andapp
runs a Node.js container. - Both services connect through the
frontend
network, allowing them to communicate. - The
shared-volume
volume is mounted in both containers, providing shared storage for files.
Docker Compose Services
In Docker Compose, every component of your application operates as a separate service, with each service running a single container tailored to a specific role—such as a database, web server, or cache. These services are defined within the `services` section of the `docker-compose.yml` file. This section lets you configure each service individually, specifying details like the Docker image to pull, environment variables, network connections, and storage options. Through this setup, you can control how each part of your application interacts, ensuring smooth communication and resource management across the services.
Key Service Configuration Options
- Image: Image option defines which Docker image we are going to use by the service from the Docker Hub or any other registry.
- Build: Instead of pulling an image, you can build one locally by specifying a directory containing a Dockerfile. The build is ideal for including custom code in your application.
- Ports: This setting maps a container’s internal ports to those on the host machine, enabling access to services from outside the container.
- Volumes: Volumes attach persistent storage to a service ensuring that the data remains accessible even when a container restart.
- Environment: Environment variables allow you to pass configurations or sensitive information, like database credentials or API keys, to the service.
- Depends_on: Depends_on controls the startup order of services, ensuring that certain containers are running before others begin.
Example of docker-compose.yml
Configuration
Here’s a sample configuration that demonstrates how these options are used:
version: '3.8'
services:
db:
image: postgres:13
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes:
- db_data:/var/lib/postgresql/dataweb:
build: ./web
ports:
- "5000:5000"
volumes:
- web_data:/usr/src/app
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
- dbvolumes:
db_data:
web_data:
Explanation:
- The
db
service runs a PostgreSQL container. It uses environment variables to set up a database username and password, and stores data on thedb_data
volume to ensure it’s retained. - The
web
service is built from a Dockerfile in the./web
directory and exposes port 5000. Theweb_data
volume is mounted to store application files persistently. It depends on thedb
service, ensuring the database is available when the web service starts.
Docker Compose Networks
Docker Compose deployments use networks to allow secure communications between the services. Services defined in a docker-compose.yml file are by default placed on one network and are able to connect to each other without any additional setup. For more strict control, you can create additional networks and assign services to them in order to control the way they communicate or to separate some groups of services as the need arises.
Key Network Configuration Options
- Driver: Driver are used in the network driver type, such as
bridge
(the default for local networks) oroverlay
(for multi-host networks in Docker Swarm), which determines how services connect to each other. - Driver Options (
driver_opts
): Driver Options(driver_opts) allows for additional settings on the network driver, useful for fine-tuning network behavior to meet specific needs. - IP Address Management (
ipam
):IP address management configures network-level IP settings, like subnets and IP ranges, to give you greater control over the IP address space assigned to your services.
Example docker-compose.yml
with Custom Networks
Below is an example Compose file that sets up two networks, one for database communication and another for web access.
version: '3.8'services:
db:
image: postgres:13
networks:
- backendweb:
image: nginx:latest
networks:
- frontend
- backend
ports:
- "80:80"networks:
frontend:
driver: bridgebackend:
driver: bridge
ipam:
config:
- subnet: 172.16.238.0/24
Explanation:
- The
db
service uses thebackend
network, isolating it from thefrontend
network to limit access. - The
web
service is connected to bothfrontend
andbackend
networks, allowing it to communicate with thedb
service while remaining accessible via thefrontend
network. - The
backend
network includes IPAM settings with a specific subnet range, ensuring custom IP address management.
Docker Compose Volumes
Volumesin docker compose are used to persist data created or used by the docker containers. By doing so they enable the data to persist even if containers are stopped or removed in your docker-compose. Within a docker-compose. yml file, the volumes section describes all the volumes that are attached to the services allowing you to manage data that exists independently of the container lifecycle.
Key Volume Configuration Options
- External: Set true to signify that the volume was created externally, outside of Docker Compose (such as via docker volume create) and simply referenced in the configuration.
- Driver: This indicates which volume driver the volume should use, and it controls how are these volumes being handled. By default, the driver is local, but other options are also available.
- Driver Options (driver_opts): Additional options to customize the volume driver like filesystem type or different storage parameters.
Example docker-compose.yml
with Volumes
Here’s a practical example showing how to configure a volume for a Pos-tgreSQL database, ensuring that its data is stored persistently.
version: '3.8'services:
db:
image: postgres:13
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes:
- db_data:/var/lib/postgresql/datavolumes:
db_data:
driver: local
driver_opts:
type: none
o: bind
device: /path/to/local/db_data
Explanation
- The
db
service runs a PostgreSQL container, with its data stored in thedb_data
volume. This setup ensures that the database information remains intact across restarts or removals of the container. - The
db_data
volume is configured to use thelocal
driver, and it has driver options set to create a bind mount pointing to a specific path on the host system (/path/to/local/db_data
). This means that all database files are saved in that designated directory on the host. - By using volumes in this way, you can keep essential data safe and easily accessible, separate from the container itself.
Docker Compose Environment Variables
Environment variables are a simple and effective way to pass configuration settings from your host operating system through Docker Compose in order to get to your services. You can set these variables directly on the service definition by using the environment section or load them from an external file.
How to Set Environment Variables in Docker Compose?
- Inline: you may declare env vars directly in the service definition.This approach is simple and gives everything in one place.
- env_file: This option allows you to load environment variables from an external file, making it easier to manage configuration, especially when dealing with many variables.
Example docker-compose.yml
Using Environment Variables
Here’s an example that demonstrates both methods of setting environment variables for a web application and a database service.
version: '3.8'services:
db:
image: postgres:13
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes: - db_data:/var/lib/postgresql/dataweb: image: my-web-app:latest
build: ./web
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
env_file:
- .envvolumes:
db_data:
Explanation
- In the
db
service, thePOSTGRES_USER
andPOSTGRES_PASSWORD
environment variables are defined inline, specifying the database credentials directly. - The
web
service uses an inline variable forDATABASE_URL
, which connects to the PostgreSQL database. Additionally, it loads environment variables from an external file named.env
. This file can contain various settings, such as API keys, application configurations, and other sensitive information.
With a good understanding of these basic principles, developers are ready to use Docker Compose to manage and orchestrate applications that can be quite complex and involve many Docker containers.
Install Docker Compose
We can run Docker Compose on macOs, Widows, and 64-bit Linux.
- For any significant activity, Docker Compose depends on Docker Engine. Depending on your arrangement, we must ensure that Docker Engine is installed either locally or remotely.
- A desktop system such as Docker for Mac and Windows comes with Docker Compose preinstalled.
- Install Docker first as instructed in Docker installation on the Linux system before beginning the installation of Docker Compose.
Install Docker Compose on Ubuntu — A Step-By-Step Guide
Step 1: Update the package Manager
- The following scripts will install the most recent version of Docker Compose and update the package management.
sudo apt-get update
Step 2: Download the Software
- Here we are using the Ubuntu flavor in the Linux Operating system. So the package manager is «apt-get» If you want to install it in Redhat Linux then the package manager will be «yum».
Step 3: Apply Permissions
- Apply the Executable permissions to the software with the following commands:
sudo chmod +x /usr/local/bin/docker-compose
Step 4: Verify the Download Software
- Verify the whether the docker compose is successfully installed or not with the following command:
docker-compose --version
Docker Container
A docker container is a lightweight Linux-based system that packages all the libraries and dependencies of an application, prebuilt and ready to be executed. It is an isolated running image that makes the application feel like the whole system is dedicated to it. Many large organizations are moving towards containers from VMs as they are light and simple to use and maintain. But when it comes to using containers for real-world applications, usually one container is not sufficient. For example, Let’s assume Netflix uses a microservices architecture. Then it needs services for authentication, Login, Database, Payment, etc, and for each of these services, we want to run a separate container. It is preferred for a container to have only a single purpose.
Now, imagine writing separate docker files, and managing configuration and networks for each container. This is where Docker Compose comes into the picture and makes our lives easy.
Why Docker Compose?
As discussed earlier, a real-world application has a separate container for each of its services. And we know that each container needs to have a Dockerfile. It means we will have to write maybe hundreds of docker files and then manage everything about the containers individually, That’s cumbersome.
Hence we use docker-compose, which is a tool that helps in the definition and running of multi-container applications. With the help of Docker Compose you can start and stop the services by using its YAML file. Docker-compose allows us to start and stop all of the services with just a few simple commands and a single YAML file for each configuration.
In contrast to utilizing a prebuilt image from Docker Hub, which you may configure with the docker-compose.yaml file, if you are using a custom image, you will need to declare its configurations in a separate Dockerfile. These are the features that docker-compose support:
- All the services are isolated running on a single host.
- Containers are recreated only when there is some change.
- The volume data is not reset when creating new containers, volumes are preserved.
- Movement of variables and composition within environments.
- It creates a virtual network for easy interaction within the environments.
Now, let’s see how we can use docker-compose, using a simple project.
How to Use Docker Compose?
In this project, we will create a straightforward Restfull API that will return a list of fruits. We will use a flask for this purpose. And a PHP application will request this service and show it in the browser. Both services will run in their own containers.
Step 1: Create Project Directory
- First, Create a separate directory for our complete project. Use the following command.
mkdir dockerComposeProject
- Move inside the directory.
cd dockerComposeProject
Step 2: Create API
we will create a custom image that will use Python to serve our Restful API defined below. Then the service will be further configured using aDockerfile.
- Then create a subdirectory for the service we will name it product. and move into the same.
mkdir product
cd product
- Create
requirements.txt
Inside the product
folder, create a file named requirements.txt
and add the following dependencies:
flask
flask-restful
Step 3: Build Python api.py
- The following is the python file that helps in making an API call:
- Create a Dockerfile to define the container in which the above API will run.
from flask import Flask
from flask_restful import Resource, Api# create a flask object
app = Flask(__name__)
api = Api(app)# creating a class for Fruits that will hold
# the accessors
class Fruits(Resource):
def get(self):
# returns a dictionary with fruits
return {
'fruits': ['Mango', 'Pomegranate', 'Orange', 'Litchi']
}# adds the resources at the root route
api.add_resource(Fruits, '/')# if this file is being executed then run the service
if __name__ == '__main__':
# run the service
app.run(host='0.0.0.0', port=80, debug=True)
Step 4: Create Dockerfile For Python API
FROM python:3
WORKDIR /usr/src/app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "api.py"]
FROM accepts an image name and a version that the docker will download from the docker hub. The current working directory’s contents can be copied to the location where the server expects the code to be by using the copy command. Moreover, the CMD command takes a list of commands to start the service once the container has been started.
Step 5: Create PHP HTML Website
Let’s create a simple website using PHP that will use our API.
- Move to the parent directory and create another subdirectory for the website.
cd ..
mkdir website
cd website
index.php
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fruit Service</title>
</head>
<body>
<h1>Welcome to India's Fruit Shop</h1>
<ul>
<?php
$json = file_get_contents('http://fruit-service');
$obj = json_decode($json);
$fruits = $obj->fruits;
foreach ($fruits as $fruit){
echo "<li>$fruit</li>";
}
?>
</ul>
</body>
</html>
- Now create a compose file where we will define and configure the two services, API and the website.
- Move out of the website subdirectory using the following code.
cd ..
- And then create the file name as . docker-compose.yaml
Step 6: Create Docker-compose.yaml file
- The following is the sample docker compose file code:
version: "3"services:
fruit-service:
build: ./product
volumes:
- ./product:/usr/src/app
ports:
- 5001:80website:
image: php:apache
volumes:
- ./website:/var/www/html
ports:
- 5000:80
depends_on:
- fruit-service
Docker-compose.yaml File
The first line is optional where we specify the version of the docker-compose tool. Next services define a list of services that our application is going to use. The first service is fruit service which is our API and the second one is our website. The fruit service has a property build that contains the dockerfile that is to be built and created as an image. Volumes define storage mapping between the host and the container so that we can make live changes. Finally, port property exposes the containers port 80 through the host’s 5001.
The website service does not use a custom image but we download the PHP image from the Docker hub and then map the websites folder that contains our index.php to /var/www/html (PHP expects the code to be at this location). Ports expose the container port. Finally, the depends_on specifies all the services on which the current service depends.
- The folder structure after creating all the required files and directory will be as follows:
Run the application stack with Docker Compose
- Now that we have our docker-compose.yml file, we can run it.
- To start the application, enter the following command.
docker-compose up -d
Now all the services will start and our website will be ready to be used at localhost:5000.
- Open your browser and enter localhost:5000.
Output
- To stop the application, either press CTRL + C or
docker-compose stop
Advantages of Docker Compose
The following are the advantages of Docker Compose:
- Simplifies Multi-Container Management: Docker Compose facilitates with features such as define, configure, and run multiple containers with a single YAML file, streamlining the management of complex applications.
- Facilitates Environment Consistency: It facilitates with the development, testing, and production environments that are consistent with reducing the risk of environment-related issues.
- Automates Multi-Container Workflows: With Docker Compose, you can easily automate the setup and teardown of multi-container environments, making it ideal for CI/CD pipelines and development workflows.
- Efficient Resource Management: It enables efficient allocation and management of resources across multiple containers, improving application performance and scalability.
Disadvantages of Docker Compose
The following are the disadvantages of Docker Compose:
- Limited Scalability: Docker Compose is not developed for large scaling mechanism which can limit its effectiveness for managing complex deployments.
- Single Host Limitation: Docker Compose will operate on a single host, making it unsuitable for distributed applications with requiring multi-host orchestration.
- Basic Load Balancing: It lacks with advanced load balancing and auto-scaling features found in more robust orchestration tools like Kubernetes.
- Less Robust Monitoring: Docker Compose provides minimal built-in monitoring and logging capabilities compared to more comprehensive solutions.
Important Docker Compose Commands
Command |
Description |
Example |
---|---|---|
docker-compose up |
This command starts all the services defined in your |
docker-compose up -d |
docker-compose down |
Use this command to stop and remove all the containers, networks, and volumes that were created by |
docker-compose down |
docker-compose ps |
This command lists all the containers associated with your Compose application, showing their current status and other helpful information. It’s great for monitoring which services are up and running. |
docker-compose ps |
docker-compose logs |
This command lets you view the logs generated by your services. If you want to focus on a specific service, you can specify its name to filter the logs, which is useful for troubleshooting. |
docker-compose logs web |
docker-compose exec |
With this command, you can run a command inside one of the running service containers. It’s particularly useful for debugging or interacting with your services directly. |
docker-compose exec db psql -U user -d mydb |
docker-compose build |
This command builds or rebuilds the images specified in your |
docker-compose build |
docker-compose pull |
Use this command to pull the latest images for your services from their respective registries. It ensures that you have the most current versions before starting your application. |
docker-compose pull |
docker-compose start |
This command starts containers that are already defined in your Compose file without recreating them. It’s a quick way to get your services running again after they’ve been stopped. |
docker-compose start |
docker-compose stop |
This command stops the running containers but keeps them intact, so you can start them up again later using |
docker-compose stop |
docker-compose config |
This command validates and displays the configuration from your |
docker-compose config |
Best Practices of Docker Compose
The following are the some of the best practices of Docker Compose:
- Use Environment Variables: It is suggestable to store configuration values and secrets in environment variables to keep your
docker-compose.yml
clean and secure. - Keep Services Lightweight: It is preferred to design each service to handle a single responsibility to ensure modularity and ease of maintenance.
- Leverage Volumes: Usage of volumes with enhancing in maintaining the persistent data storage, allowing data to persist across container restarts and updates.
- Version Control Your Compose Files: It is preferred to maintain your
docker-compose.yml
file in version control (e.g., Git) to track changes and collaborate with your team effectively.
Features of Docker Compose
The following are the features of Docker Compose:
- Multi-Container Deployment: it facilitates with easily define and run applications with multiple containers using a single YAML file.
- Service Isolation: Each service runs in its own container, with ensuring the isolation and reducing conflicts between services.
- Simplified Configuration: It helps in centralizing all the configurations, including networking, volumes, and dependencies, in the
docker-compose.yml
file. - Scalability: It provides the effortlessly scaling of services up or down with a single command, allowing for flexible and dynamic resource management.
Conclusion
In this article, we learned about Docker Compose, and why and when to use it. And demonstrated its usuage through a simple project. It helps in automating the creating process of containers through services, networks and volumes with through respective keywords availability. Through using docker compose management and automation of containers and its volumes, networks will be easier.
This is a short hands-on introduction to docker-compose. If you want to know what docker is, check this link out. This is simply a commented version of the official django with docker-compose.
Tested Configuration:
MacOS: Sierra 10.12.06
Docker: 18.09.2 CE
Docker-compose: 1.23.2
1. Requirements
Make sure you have docker and docker-compose installed on your machine. To do this, simply type
and make sure you have something like Docker version 18.09.2-ce, build 6247962
Then type
and make sure you have something like docker-compose version 1.23.2, build 1110ad01
2. Dockerfile
First, create your Dockerfile, please type :
mkdir docker_folder
cd docker_folder
touch Dockerfile
Edit your Dockerfile
You are still in the /docker_folder
, type
It will open your Dockerfile. Now copy the following code in your Dockerfile :
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
What is happening in the Dockerfile:
FROM python:3 will import the python 3 image from the docker hub
ENV PYTHONUNBUFFERED 1 define an environment variable. Later in the script, it will tell python to print STDOUT without buffering anything. that’s useful when you want to print something before the machine crashes
RUN mkdir /code will create a folder on the Image
WORKDIR /code will set the current directory as /code -> acts like a ` cd /code` on the image
COPY requirements.txt /code/ copies the file requirements.txt to the image at the location /code/requirements.txt
RUN pip install -r requirements.txt install all python packages on the image
COPY . /code/ copy the rest of the folder to the folder code
3. Create your requirements
Create your requirements.txt in your folder /docker_folder
and then edit it:
sudo nano requirements.txt
Inside, you can write this:
Django>=2.0,<3.0
psycopg2>=2.7,<3.0
This will tell Docker to install Django and psycopg2 on the image
4. Create docker-compose.yaml
Create a file called docker-compose.yaml in your project directory /docker_folder
and then edit it:
sudo nano docker-compose.yaml
Inside, you can write this:
version: "3"
services:
db:
image: postgres
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
From the documentation:
The docker-compose.yml file describes the services that make your app. In this example those services are a web server and database.
The compose file also describes which Docker images these services use, how they link together, any volumes they might need mounted inside the containers.
Finally, the docker-compose.yml file describes which ports these services expose.
Short analysis:
- we create two services:
web
&db
- we use format version 3 (different from the docker-compose version…). You can see the Compatibility matrix
- service
db
has an image based on thepostgres
image (from docker hub) - service
web
has an image based on theDockerfile
we wrote earlier: that’s thebuild: .
- service
web
launches the command:python manage.py runserver 0.0.0.0:8000
after the image is built - service
web
mounts the folder/docker_folder
to the image folder/code
(ex: modify the code on the fly) - service
web
mounts the ports 8000 [container] to 8000 [image] (simple)
Note: we don’t have any access to the database, exept through the docker network. It means only the service web
can access the database, because this docker network is restricted to db
and web
(type docker network inspect testdjango_default
). Even you, if you want to use a GUI in order to manipulate the Postgres data, you will need to add a line in the db
configuration:
5. Create a Django project
in your terminal type:
sudo docker-compose run web django-admin startproject composeexample .
Explanations:
compose will run django-admin startproject composeexample
in a container, using the web service’s image and configuration. This command instructs Django to create a set of files and directories representing a Django project.
Now, if you look at the files in the /docker_folder
, you have:
Dockerfile (was here before)
composeexample
docker-compose.yaml (was here before)
manage.py
requirements.txt (was here before)
6. Connect the database
In the composeexample/settings.py
file, replace DATABASES = ...
with:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres',
'USER': 'postgres',
'HOST': 'db',
'PORT': 5432,
}
}
To know more about the database: docker & postgres
7. Run
Simply run your docker project using
and see you server working by clicking http://localhost:8000
Monitor
Prints all the running containers
Stop
Two method:
-
Ctrl-C
in the same shell in where you started it -
open a new shell, go to
/docker_folder
directory, and rundocker-compose down
Notes:
docker-compose.yaml VS docker-compose.yml
Shall we use the extension .yml or .yaml ? IT DOESN’T MATTER
Everyone could use .yaml but some Windows developer are afraid of using more than 3 letters extensions…
file ownership
After runnig sudo docker-compose run web django-admin startproject composeexample .
several files were created. Running ls -l
will show who is owning the files. You may need to change their ownership back to you (espcially Linux users) using sudo chown -R $USER:$USER .
Other docker-compose.yaml proposition
This can be interesting for two reasons:
version: "3"
services:
db:
image: postgres
environment:
PGDATA: /var/lib/postgresql/data/django_test_pgdata
POSTGRES_USER: yourusername
POSTGRES_PASSWORD: yourpassword
POSTGRES_DB: test
volumes:
- /usr/local/var/postgres/data/django_test_pgdata:/var/lib/postgresql/data/django_test_pgdata
ports:
- "8002:5432"
web: [SAME CONFIG]
First you may have port 5432
already in use on another Postgres container, or because you already run postgres on your laptop.
Second, you have control on the data. instead of staying inside your postgres container (and beiing deleted when the container stops) you can have persistent data.
Run only one docker
If you want to launch only the database for instance, you can write down:
docker-compose run --service-ports postgres
With --service-ports
meaning you attach the docker to the host network (usually what you want when you don’t launch docker together)
if you have Volumes
Let’s say you have decided to inculde the volume option, like so (probably to cache your node_modules folder):
version: "3"
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- node_modules:/code/node_modules
ports:
- "8000:8000"
Then, if you need to “clean the cache” (because you want to reinstall the dependancies for instance), use this command: docker-compose down --volumes
Ressources:
docker-compose.yaml VS docker-compose.yml: stackoverflow link
More about PYTHONUNBUFFERED
the official docker-compose tuto
Provide feedback
Saved searches
Use saved searches to filter your results more quickly
Sign up
Appearance settings