
В прошлой части я разобрал хранилища — тома, bind mount, tmpfs. В третьей части (в этой) планировалось добраться до сети. До неё дойдём, но прежде чем разбирать, как сервисы общаются друг с другом, стоит на секунду остановиться и разобрать сам файл, в котором мы всё это описываем. Потому что compose.yaml — это спецификация со своими top-level разделами, правилами слияния, подстановкой переменных и кучей мелких нюансов, которые легко упустить, если читать документацию по верхам.
Эта часть, по сути, большой справочник по структуре Compose-файла: держите статью под рукой и возвращайтесь к ней, когда понадобится конкретный атрибут.
1. Из чего состоит compose-файл
Compose-файл описывает модель приложения через набор top-level (верхнеуровневых) разделов. Обязателен в основном только один — services, остальные подключаются по необходимости.
Раздел |
Что описывает |
Обязателен |
|---|---|---|
|
Сервисы приложения — абстракция над контейнерами |
Да |
|
Именованные сети для сервисов |
Нет, иначе используется неявная сеть |
|
Именованные тома |
Нет |
|
Неконфиденциальные конфигурационные данные |
Нет |
|
Чувствительные данные |
Нет |
|
AI-модели, которые пуллятся и обслуживаются runner’ом |
Нет |
|
Подключение и слияние других compose-файлов |
Нет |
|
Произвольные данные для переиспользования, Compose их игнорирует |
Нет |
|
Только для обратной совместимости, ни на что не влияет |
Нет, и больше не нужен |
|
Имя проекта по умолчанию |
Нет, иначе подставляется автоматически |
profilesвлияют только наservices. Все остальные top-level разделы всегда активны, независимо от того, какие профили включены.
2. version и name
version — поле, которое раньше реально влияло на то, по какой схеме Compose читает файл. Сейчас оно ни на что не влияет: Compose всегда разбирает файл по самой свежей схеме, что бы в version ни было написано. Если поле в файле есть, Compose просто выведет предупреждение, что оно устарело, и продолжит работать как обычно. Смысла писать его в новых файлах нет. (С другой стороны, возможно, оно используется в старых версиях Docker, где применяется старая схема.)
name — имя проекта, которое используется по умолчанию, если вы не задаёте его другим способом (флагом, переменной окружения и т.п.). Имя проекта доступно для интерполяции как COMPOSE_PROJECT_NAME:
name: myapp services: foo: image: busybox command: echo "I'm running ${COMPOSE_PROJECT_NAME}"
3. services
Сервис, по сути, рецепт для одного или нескольких одинаковых контейнеров: какой образ запускать, с какими портами, переменными окружения, томами и так далее. Когда я пишу services.web, я описываю не один конкретный контейнер, а правило, по которому Compose его создаёт. Контейнеров по этому правилу может получиться и несколько одинаковых копий (реплик), если задать scale или deploy.replicas. Удобство в том, что сервис можно масштабировать или пересоздавать отдельно от остальных: например, поднять три копии web, пока db остаётся в одном экземпляре.
У сервиса есть две опциональные секции, и у каждой свой мини-стандарт внутри общего. build описывает, как собрать образ (это Compose Build Specification), deploy, как развернуть сервис и с какими ограничениями (Compose Deploy Specification). Если платформа их не поддерживает, файл всё равно остаётся валидным, просто эти секции игнорируются.
Атрибутов у сервиса очень много, так что разобью их по смыслу.
3.1 Образ, сборка и запуск процесса
Атрибут |
Что делает |
Пример / примечание |
|---|---|---|
|
Образ для запуска контейнера, формат |
|
|
Конфигурация сборки образа из исходников (см. раздел 4) |
— |
|
Целевая платформа |
|
|
Когда и как Compose пуллит образ: |
|
|
Сколько контейнеров поднимать по умолчанию (должно совпадать с |
|
|
Какой OCI runtime использовать, по умолчанию |
|
|
Передаёт управление жизненным циклом сервиса внешнему бинарю (Compose сам не управляет) |
см. ниже |
|
Контейнер создаётся с файловой системой только на чтение |
|
|
Запускает init-процесс (PID 1), который форвардит сигналы и подчищает зомби-процессы |
|
|
Переопределяет |
|
|
Переопределяет |
список или строка, как в Dockerfile |
|
Переопределяет рабочую директорию (аналог |
— |
|
Пользователь, от которого выполняется процесс (аналог |
— |
|
Кастомное имя хоста / домена контейнера (валидный RFC 1123) |
— |
|
Своё имя контейнера вместо автогенерируемого. Формат |
|
Про
command: в отличие отCMDв Dockerfile, это поле не выполняется автоматически черезSHELL. Если нужна интерполяция переменных шеллом — оборачивайте сами:command: /bin/sh -c 'echo "hello $$HOSTNAME"'.
Про provider отдельно, потому что без объяснения этот пример выглядит как магия. Короче: иногда нужный «сервис» — это не контейнер вообще, а что-то внешнее, например, облачная база данных, которую поднимает не Docker, а сторонняя программа. provider говорит Compose: «Не пытайся создавать контейнер для этого сервиса сам, вызови вот эту программу, и пусть она разбирается».
services: database: provider: type: awesomecloud options: type: mysql foo: bar app: image: myapp depends_on: - database
Что здесь происходит по шагам:
У сервиса
databaseнетimage, потому что контейнер для него вообще не будет создан.При
docker compose upCompose видитprovider.type: awesomecloudи запускает внешнюю программу с этим именем, передав ей всё, что лежит вoptions(type: mysql,foo: bar). Дальше создание и настройка самой базы — целиком забота этой программы, не Compose.Когда
awesomecloudподготовит базу, она возвращает Compose какие-то данные о ней, допустим, адрес для подключения и ключ доступа (URLиAPI_KEY).Compose передаёт эти данные сервису
app, потому что он зависит отdatabase(depends_on). Передаёт через переменные окружения, и к именам переменных приклеивает имя сервиса-провайдера в верхнем регистре — получаютсяDATABASE_URLиDATABASE_API_KEY.Внутри контейнера
appможно просто прочитатьDATABASE_URLи подключиться, неважно, что реальная база крутится не в Docker, а где-то у облачного провайдера. Приdocker compose downто же самое в обратную сторону: контейнер удалять не нужно (его и не было), вместо этого Compose попроситawesomecloudснести то, что он создал.
3.2 Жизненный цикл и зависимости
Атрибут |
Что делает |
|---|---|
|
Политика перезапуска: |
|
Проверка “здоровья” контейнера, переопределяет |
|
Порядок запуска/остановки сервисов |
|
Список профилей, при которых сервис активен (см. раздел 11) |
|
Хуки, выполняемые после старта контейнера |
|
Хуки перед остановкой контейнера (не сработают при аварийном завершении) |
|
Сигнал для остановки, по умолчанию |
|
Сколько ждать перед |
healthcheck пример:
healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] interval: 1m30s timeout: 10s retries: 3 start_period: 40s start_interval: 5s
test может быть строкой (тогда это эквивалент CMD-SHELL, команда выполняется через /bin/sh на Linux) или списком, где первый элемент — NONE, CMD или CMD-SHELL. Чтобы выключить healthcheck из образа — test: NONE или disable: true.
depends_on — короткий и длинный синтаксис:
# короткий — просто порядок запуска, без ожидания healthy services: web: depends_on: - db - redis # длинный — с условиями services: web: depends_on: db: condition: service_healthy restart: true redis: condition: service_started # Запускает db и redis, ждёт healthcheck для db и запуска redis, затем запускает web.
Параметр длинного синтаксиса |
Значение |
|---|---|
|
То же самое, что короткий синтаксис |
|
Ждать, пока зависимость не станет healthy (здоров и готов работать) |
|
Ждать успешного завершения зависимости |
|
Перезапускать этот сервис после обновления зависимости. Касается только явного рестарта через Compose, не автоматического рестарта runtime’а |
|
Не падать, если зависимость недоступна — только предупреждение |
post_start / pre_stop устроены одинаково:
services: test: post_start: - command: ./do_something_on_startup.sh user: root privileged: true environment: - FOO=BAR
Здесь после старта контейнера test Compose выполнит внутри него команду ./do_something_on_startup.sh — от имени root, с повышенными правами и с дополнительной переменной FOO=BAR в окружении именно этой команды. Сам контейнер при этом продолжает работать как обычно, никто его не перезапускает.
3.3 Сеть на уровне сервиса
Атрибут |
Что делает |
|---|---|
|
Публикация портов хост:контейнер. Нельзя использовать с |
|
Открыть порт только для других контейнеров в сети, без публикации на хост |
|
К каким именованным сетям подключён сервис, плюс настройки на уровне подключения |
|
|
|
Связь с контейнерами другого сервиса по имени/алиасу (не обязательны для общения внутри одной сети) |
|
Связь с сервисами вне текущего Compose-приложения |
|
Кастомные DNS-серверы, опции резолвера, домены поиска |
|
Дополнительные записи в |
|
MAC-адрес контейнера (на уровне сервиса). Некоторые runtime’ы могут отклонить это значение (тогда используйте |
ports, короткий синтаксис [HOST:]CONTAINER[/PROTOCOL]:
ports: - "3000" - "3000-3005" - "8000:8000" - "9090-9091:8080-8081" - "127.0.0.1:8001:8001" - "6060:6060/udp" - "127.0.0.1:5000-5010:5000-5010" - "::1:6000:6000" - "[::1]:6001:6001"
Поясню пару строк, чтоб было понятно. "3000" — задан только порт контейнера, какой порт хоста подставится, решит сам Docker (возьмёт случайный свободный). "8000:8000" — порт хоста 8000 ведёт на порт контейнера 8000, оба фиксированы. "127.0.0.1:8001:8001" — то же самое, но слушать будем только на localhost хоста, а не на всех интерфейсах сразу. "[::1]:6001:6001" — то же самое, но для IPv6-адреса.
Если не указать host IP явно (как в первых трёх строках), Docker слушает на
0.0.0.0, то есть на всех интерфейсах — это может обойти файрвол хоста и открыть порт наружу, если у хоста публичный IP.
Длинный синтаксис портов:
ports: - name: web target: 80 host_ip: 127.0.0.1 published: "8080" protocol: tcp app_protocol: http mode: host
expose:
expose: - "3000" - "8080-8085/tcp"
Если в Dockerfile образа уже объявлены порты через
EXPOSE, они видны другим контейнерам в сети даже еслиexposeв Compose-файле не задан.
extra_hosts поддерживает короткий синтаксис (список строк) и длинный (маппинг):
# короткий extra_hosts: - "somehost=162.242.195.82" - "otherhost=50.31.209.229" - "myhostv6=[::1]" # длинный extra_hosts: somehost: "162.242.195.82" otherhost: "50.31.209.229"
networks на уровне сервиса поддерживает дополнительные параметры для каждого подключения к сети:
Под-атрибут |
Что делает |
|---|---|
|
Альтернативные имена сервиса в этой сети (свои для каждой сети). Алиас может быть shared между несколькими контейнерами и сервисами — тогда к кому именно он резолвится, не гарантируется |
|
Статический IP (нужен |
|
Имя сетевого интерфейса внутри контейнера |
|
Список link-local IP |
|
MAC именно для этой сети |
|
Опции драйвера, специфичные для подключения |
|
Сеть с наибольшим значением становится дефолтным шлюзом. По умолчанию |
|
Порядок подключения сервиса к сетям. Не влияет на выбор шлюза и не контролирует имя интерфейса ( |
services: backend: networks: back-tier: aliases: - database admin: aliases: - mysql
Сервис backend подключён сразу к двум сетям, и в каждой у него своё “прозвище”. Контейнеры в сети back-tier могут достучаться до него по имени database, а контейнеры в сети admin по имени mysql. Имя самого сервиса (backend) при этом тоже продолжает работать как обычно. Если networks не задан вообще, сервис неявно подключается к сети default, это эквивалентно networks: {default: {}}. Чтобы вообще отключить сетевой доступ, network_mode: none.
3.4 Данные и конфигурация
Атрибут |
Что делает |
|---|---|
|
Монтирование томов/bind mount/tmpfs в контейнер (на уровне сервиса) |
|
Подключить все тома другого сервиса/контейнера целиком. Можно указать |
|
Доступ к конфигам из top-level |
|
Доступ к секретам из top-level |
|
Файл(ы) с переменными окружения |
|
Переменные окружения напрямую в файле |
|
Файл(ы) с лейблами, альтернатива |
|
Метаданные на контейнере |
|
tmpfs-монтирование (короткая форма, без отдельного top-level раздела) |
volumes, короткий синтаксис VOLUME:CONTAINER_PATH[:ACCESS_MODE], длинный — объект с type/source/target/read_only/bind/volume/tmpfs/image:
services: backend: image: example/backend volumes: - type: volume source: db-data target: /data volume: nocopy: true subpath: sub - type: bind source: /var/run/postgres/postgres.sock target: /var/run/postgres/postgres.sock
configs и secrets устроены практически одинаково: короткий синтаксис просто даёт доступ и монтирует под именем источника, длинный позволяет задать target/uid/gid/mode:
services: redis: image: redis:latest configs: - source: my_config target: /redis_config uid: "103" gid: "103" mode: 0440 secrets: - source: my-token uid: "103" gid: "103" mode: 0o440 configs: my_config: external: true secrets: my-token: environment: "MY_TOKEN"
Нюанс:
uid/gid/modeдля секретов работают только если источник секретаenvironment. Если источникfile, Compose использует bind-mount, и эти атрибуты тихо игнорируются.
label_file — удобно, когда лейблов много и не хочется засорять Compose-файл:
services: one: label_file: ./app.labels two: label_file: - ./app.labels - ./additional.labels
Формат файла такой же, как у env_file — пары KEY=VALUE. Если несколько файлов, обрабатываются сверху вниз; при конфликте побеждает последний файл. Если одно и то же поле задано и в label_file, и в labels — побеждает labels.
env_file:
env_file: - path: ./default.env required: true # по умолчанию - path: ./override.env required: false - path: ./raw.env format: raw # без интерполяции, значения как есть
Если переменная задана и в env_file, и в environment — побеждает environment, даже если значение пустое.
Несколько правил парсинга формата .env файла, которые полезно знать:
Строки начиная с
#— комментарии, игнорируютсяРазделитель между ключом и значением —
=или:Значения в двойных кавычках поддерживают escape-последовательности:
\n,\t,\\Значения в одинарных кавычках берутся буквально:
VAR='$OTHER'→$OTHERИнлайновый комментарий для незакавыченных значений нужно предварять пробелом:
VAR=VAL # comment→VAL
environment, мапа или список (булевы значения обязательно в кавычках, иначе YAML превратит их в True/False):
environment: RACK_ENV: development SHOW: "true" USER_INPUT:
tmpfs:
services: app: tmpfs: - /data:mode=755,uid=1009,gid=1009 - /run
Про лейблы и зарезервированный префикс
Compose автоматически проставляет на каждый контейнер два canonical label’а:
com.docker.compose.project— имя проектаcom.docker.compose.service— имя сервиса из Compose-файла
Префикс com.docker.compose зарезервирован. Если указать лейбл с таким префиксом в Compose-файле, будет runtime error.
3.5 Ресурсы и изоляция
CPU
Атрибут |
Что делает |
|---|---|
|
Сколько (потенциально виртуальных) ядер CPU выделить контейнеру. Число дробное, |
|
Целое число — сколько именно CPU контейнер может использовать |
|
Какой процент от всех доступных CPU доступен контейнеру |
|
Относительный вес контейнера при распределении CPU между несколькими контейнерами. Это не абсолютное число ядер, а пропорция по сравнению с другими |
|
Период CFS-планировщика ядра Linux (Completely Fair Scheduler). Работает в связке с |
|
Сколько времени CPU достаётся контейнеру за один такой период |
|
Время, которое контейнер может работать в режиме real-time планировщика. Указывается числом микросекунд или duration, например |
|
Период того же real-time планировщика, тоже в микросекундах или duration |
|
Список или диапазон конкретных ядер, на которых разрешено выполняться: |
Память
Атрибут |
Что делает |
|---|---|
|
Жёсткий лимит памяти в байтовом формате ( |
|
Гарантированный резерв памяти, тот же формат. Сверяется с |
|
Число от 0 до 100. Показывает, насколько активно ядро хоста выгружает память контейнера в swap: |
|
Лимит на память плюс swap вместе. Работает только если задан |
Диск и устройства
Атрибут |
Что делает |
|---|---|
|
Относительный приоритет контейнера в очереди на диск. Число от 10 до 1000, по умолчанию 500: чем больше, тем больше доля пропускной способности при конкуренции с другими контейнерами |
|
То же самое, но отдельно для конкретного устройства: |
|
Жёсткий лимит скорости чтения или записи для конкретного устройства, в байтах в секунду |
|
То же самое, но лимит не на скорость, а на число операций в секунду |
|
Прокидывает устройство хоста в контейнер: |
|
Правила cgroup для устройств в формате, который понимает само ядро Linux (Device Whitelist Controller) |
|
Запрашивает GPU для контейнера: список объектов с |
|
Опции storage-драйвера контейнера. Например, ограничение размера: |
Пример блока I/O лимитов:
services: foo: image: busybox blkio_config: weight: 300 device_read_bps: - path: /dev/sdb rate: '12mb'
Здесь weight: 300 понижает приоритет контейнера в очереди на диск (дефолт 500, тут ниже). А device_read_bps работает отдельно от веса и жёстко: чтение именно с /dev/sdb не быстрее 12 МБ/с, неважно, какой у контейнера приоритет.
Capabilities и безопасность
Атрибут |
Что делает |
|---|---|
|
Запускает контейнер с повышенными привилегиями, по сути почти без изоляции от хоста. Конкретный эффект зависит от платформы |
|
Добавляет конкретные Linux capabilities. Например: |
|
Убирает конкретные capabilities: |
|
Переопределяет схему лейблов безопасности (SELinux/AppArmor). Булевы опции можно указывать без значения ( |
|
Добавляет пользователя внутри контейнера в дополнительные группы по имени или номеру. Пригождается, когда несколько контейнеров от разных пользователей пишут в один файл на общем томе: владельцем файла делают общую группу |
Namespace и изоляция
Атрибут |
Что делает |
|---|---|
|
Режим изоляции IPC. |
|
В каком PID namespace запускать контейнер. Значения зависят от платформы |
|
UTS namespace, то есть имя хоста и домена на уровне ядра. |
|
Какой user namespace использовать для сервиса. Значения платформо-зависимы |
|
В каком cgroup namespace запускать контейнер: |
|
Родительская cgroup для контейнера, если нужно поместить его в конкретное место иерархии cgroup |
|
Технология изоляции контейнера. Поддерживаемые значения платформо-зависимы, актуально в первую очередь для Windows |
Прочие лимиты
Атрибут |
Что делает |
|---|---|
|
Максимум процессов и потоков (PID) внутри контейнера. |
|
Запрещает платформе убивать именно этот контейнер при нехватке памяти на хосте |
|
Число от -1000 до 1000, которое влияет на то, выберет ли платформа этот контейнер для убийства при OOM. Чем больше число, тем выше шанс быть убитым первым |
|
Меняет kernel-параметры внутри контейнера, но только namespaced — те, что не затрагивают хост целиком. Пример: |
|
Переопределяет ulimit для контейнера. Либо число для одного лимита, либо объект с |
|
Размер |
|
Спецификация учётных данных managed service account для Windows-контейнеров. Варианты: |
|
Даёт контейнеру доступ к API-сокету самого движка. Изнутри можно делать |
3.6 Логирование
logging настраивает драйвер логирования для контейнеров сервиса:
logging: driver: syslog options: syslog-address: "tcp://192.168.0.42:123"
driver — имя драйвера логирования. Дефолт и доступные значения зависят от платформы. options — опции драйвера в виде key-value пар.
3.7 Прочее: метаданные, расширение, модели
Атрибут |
Что делает |
|---|---|
|
Аннотации контейнера (массив или мапа) |
|
|
|
Конфигурация развёртывания (раздел 5) |
|
Конфигурация для live-разработки (раздел 6) |
|
Какие AI-модели использует сервис (раздел 10) |
|
Наследование конфигурации сервиса из другого файла/сервиса |
|
Выделить псевдо-TTY ( |
|
Держать stdin открытым (аналог |
models на уровне сервиса:
services: short_syntax: image: app models: - my_model long_syntax: image: app models: my_model: endpoint_var: MODEL_URL model_var: MODEL
Если endpoint_var/model_var не заданы, имена переменных генерируются автоматически: имя модели в верхнем регистре, - заменяется на _, плюс суффикс _URL.
extends — отдельная большая тема, потому что у него свои правила слияния, отличаются от обычного merge между файлами (см. раздел 15):
extends: file: common.yml service: webapp
Если
fileне указан, берётся сервис из текущего файла.Циклические ссылки запрещены, Compose вернёт ошибку.
При
extendsресурсы (volumes, networks, configs, secrets, links, depends_on и т.п.), которые использует наследуемый сервис, не подтягиваются автоматически, их нужно объявить в файле, который наследует. Правила слияния приextends(своя, отдельная от общего merge-механизма логика):
Тип значения |
Как сливается |
|---|---|
Мапы ( |
Ключи текущего сервиса перекрывают ключи из наследуемого, остальное сохраняется |
|
Считаются мапами по ключу, в качестве ключа берутся пути назначения внутри контейнера |
Списки ( |
Элементы объединяются, дубликаты удаляются |
Списки |
Объединяются, дубликаты не удаляются |
Скаляры |
Значение текущего сервиса побеждает |
Отдельный нюанс с healthcheck: текущий сервис не может выставить disable: true, если наследуемый сервис этого не делает, в таком случае Compose вернёт ошибку.
4. build — как Compose собирает образ
build можно задать строкой (путь к контексту сборки) или объектом с детальными настройками. Если задана строка, в этой папке Compose будет искать Dockerfile. Относительный путь резолвится от директории проекта, абсолютный — работает, но Compose выдаст предупреждение о непортируемости файла.
services: webapp: build: ./dir # context = ./dir, Dockerfile внутри обязателен webapp2: build: https://github.com/mycompany/example.git#branch_or_tag:subdirectory
Если у сервиса заданы и build, и image, поведение регулируется pull_policy: по умолчанию Compose сперва пытается запулить образ, и только если не нашёл, собирает из исходников.
Атрибут |
Что делает |
Пример |
|---|---|---|
|
Путь к директории с Dockerfile или Git URL. По умолчанию |
|
|
Альтернативный путь к Dockerfile относительно контекста |
|
|
Содержимое Dockerfile прямо в compose-файле (несовместимо с |
|
|
Build-аргументы ( |
|
|
Доп. именованные контексты для сборки. Поддерживает пути, Git URL, ссылки на образы ( |
|
|
Источники/назначения кэша сборки, формат |
|
|
Стадия в multi-stage Dockerfile |
|
|
Сеть для |
|
|
Список целевых платформ образа. Если не задан — Compose включает платформу сервиса автоматически. Ошибка, если список не пустой, но не содержит платформу сервиса |
|
|
Принудительно пуллить базовые образы ( |
|
|
Полная пересборка без кэша builder’а. Применяется только к слоям из Dockerfile; referenced images всё равно могут браться из локального стора (для их обновления используйте |
|
|
Сборка с повышенными привилегиями |
|
|
Технология изоляции контейнера сборки |
платформо-зависимо |
|
Метаданные на итоговом образе |
мапа или список |
|
Размер shared memory при сборке |
|
|
SSH-доступ для сборки (например, клонирование приватного репо) |
|
|
Доступ к секретам только во время сборки |
см. ниже |
|
Дополнительные теги для образа, в дополнение к |
|
|
ulimit’ы для контейнера сборки |
как в |
|
Доп. записи hosts во время сборки |
как в |
|
Доп. привилегированные права для сборки |
|
|
Provenance attestation для образа (bool или |
|
|
SBOM attestation (bool или |
|
Секреты при сборке доступны только в момент build и работают иначе, чем services.secrets. В длинном синтаксисе атрибут target — это ID секрета в Dockerfile (тот самый id= в RUN --mount=type=secret):
services: frontend: build: context: . secrets: - source: server-certificate target: cert # это id для --mount=type=secret,id=cert uid: "103" gid: "103" mode: 0440 secrets: server-certificate: external: true
# Dockerfile FROM nginx RUN --mount=type=secret,id=cert,required=true,target=/root/cert ...
Если у образа нет атрибута image, при пуше Compose пропускает его с предупреждением: пушить туда, по сути, нечего.
5. deploy — параметры развёртывания
deploy — это опциональная секция про то, как платформа должна запускать и масштабировать сервис. Если платформа не умеет в Deploy Spec, секция просто игнорируется, файл всё равно валиден.
Атрибут |
Что делает |
|---|---|
|
Модель репликации: |
|
Сколько контейнеров держать запущенными при |
|
|
|
Метаданные на самом сервисе (не на контейнерах) |
|
Жёсткие требования к ноде ( |
|
Стратегия распределения задач, пока только |
|
Максимум / гарантированный минимум ресурсов |
|
Условия и параметры перезапуска контейнеров. Если не задан — Compose смотрит на |
|
Как накатывать обновления (rolling update) |
|
Как откатывать неудачное обновление |
services: frontend: image: example/webapp deploy: mode: replicated replicas: 2 endpoint_mode: vip placement: constraints: - node.labels.disktype==ssd preferences: - spread: node.labels.zone resources: limits: cpus: '0.50' memory: 50M pids: 1 reservations: cpus: '0.25' memory: 20M devices: - capabilities: ["gpu"] count: 2 restart_policy: condition: on-failure delay: 5s max_attempts: 3 window: 120s update_config: parallelism: 2 delay: 10s order: stop-first
Что тут настроено, по шагам: Compose держит 2 реплики сервиса (replicas: 2) с общим виртуальным IP на всех (endpoint_mode: vip), запускает их только на нодах с SSD (constraints) и старается равномерно раскидать реплики по зонам (preferences). Каждому контейнеру разрешено не больше половины ядра CPU и 50 МБ памяти, а гарантированно выделено четверть ядра и 20 МБ. При падении контейнер перезапускается с паузой 5 секунд между попытками, но не больше 3 раз. А когда сервис обновляется, контейнеры пересоздаются по 2 штуки за раз, и старая версия останавливается перед запуском новой (stop-first), а не одновременно с ней.
Параметры resources.*.devices (резервирование устройств типа GPU/TPU):
Атрибут |
Что делает |
|---|---|
|
Обязательный список возможностей: |
|
Какой драйвер использовать для устройства |
|
Сколько устройств зарезервировать. Если не задан или задан как |
|
Конкретные ID устройств (взаимоисключимо с |
|
Опции драйвера в виде key-value |
restart_policy:
Атрибут |
Значение по умолчанию |
|---|---|
|
|
|
|
|
без ограничений. Важный нюанс: неудачная попытка засчитывается только если контейнер не поднялся успешно в течение |
|
|
update_config / rollback_config — одинаковый набор полей: parallelism, delay, failure_action (continue/rollback/pause для update, continue/pause для rollback), monitor, max_failure_ratio, order (stop-first/start-first).
Важно: job-режимы (
replicated-job,global-job) рассчитаны на задачи, которые завершаются с кодом0. Завершённые задачи остаются, пока их явно не удалят.max-concurrentдля них настраивается только через CLI, в Compose-файле такого параметра нет.
6. develop — режим разработки (watch)
develop — опциональная секция, появилась в Compose 2.22.0, нужна для “внутреннего цикла” разработки: следить за файлами и реагировать на изменения без полного пересоздания всего стека руками.
services: frontend: image: example/webapp build: ./webapp develop: watch: - path: ./webapp/html action: sync target: /var/www ignore: - node_modules/ backend: image: example/backend build: ./backend develop: watch: - path: ./backend/src action: rebuild
В этом примере у frontend действие sync: при изменении файлов в ./webapp/html Compose просто копирует их внутрь работающего контейнера по пути /var/www, не трогая сам контейнер (кроме папки node_modules, её игнорируем). У backend действие rebuild: при изменении файлов в ./backend/src Compose пересобирает образ заново и пересоздаёт контейнер с нуля — дольше, но нужно, когда правки требуют пересборки (например, меняется зависимость).
Атрибуты внутри каждого правила watch:
Атрибут |
Что делает |
|---|---|
|
Путь (относительно проекта), который мониторится |
|
Что делать при изменении: |
|
Куда внутри контейнера синхронизировать файлы (только для |
|
Паттерны путей, которые игнорируются (синтаксис как у |
|
Паттерны путей, которые наоборот включаются в отслеживание (удобно вместо длинного |
|
Проверять при старте watch-сессии, что файлы в уже существующем контейнере синхронизированы |
|
Команда, которая выполняется внутри контейнера при |
exec — те же поля, что у lifecycle-хуков (command, user, privileged, working_dir, environment):
services: frontend: develop: watch: - path: ./etc/config action: sync+exec target: /etc/config/ exec: command: app reload
Если
includeначинается с*, обязательно берите паттерн в кавычки — иначе YAML примет звёздочку за alias-node.
7. networks — именованные сети
Top-level networks позволяет объявить сети, которые можно переиспользовать между сервисами (подключение к сети на уровне сервиса всё равно нужно делать явно через services.<name>.networks).
services: proxy: build: ./proxy networks: - frontend app: build: ./app networks: - frontend - backend db: image: postgres:18 networks: - backend networks: frontend: driver: bridge driver_opts: com.docker.network.bridge.host_binding_ipv4: "127.0.0.1" backend: driver: custom-driver
В этом примере proxy и db изолированы друг от друга, потому что не делят общую сеть — общаться напрямую может только app.
Атрибут |
Что делает |
|---|---|
|
Драйвер сети, ошибка если недоступен на платформе |
|
Опции драйвера, key-value |
|
Разрешить отдельным (standalone) контейнерам подключаться к сети |
|
Включить/выключить выдачу IPv4/IPv6 адресов. |
|
Изолировать сеть от внешнего мира (по умолчанию Compose даёт внешнюю связность) |
|
Кастомная IPAM-конфигурация: |
|
Метаданные сети (массив или мапа). Compose также автоматически проставляет |
|
Кастомное имя сети без привязки к имени проекта |
|
Сеть уже существует и управляется не Compose; все прочие атрибуты, кроме |
Если networks вообще не объявлен в файле, Compose создаёт неявную сеть default, и все сервисы без явного networks к ней подключаются автоматически. Кастомизировать её можно так же, как обычную сеть:
networks: default: name: a_network driver_opts: com.docker.network.bridge.host_binding_ipv4: 127.0.0.1
Внешняя сеть — по аналогии с внешними томами:
networks: outside: external: true
8. volumes верхнего уровня
Top-level 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:
Оба сервиса смотрят в один и тот же том db-data, но по разным путям внутри своих контейнеров: backend пишет туда данные базы (/etc/data), а backup видит те же файлы по пути /var/lib/backup/data — то есть может забрать и заархивировать их, не трогая контейнер с самой базой.
docker compose up создаёт том, если он ещё не существует. Если том уже есть — используется существующий. Если том был удалён вручную вне Compose — пересоздаётся.
Атрибут |
Что делает |
|---|---|
|
Драйвер тома, ошибка если недоступен |
|
Опции драйвера. Через |
|
Том уже существует, Compose его не создаёт; прочие атрибуты кроме |
|
Метаданные тома. Применяются только к именованным томам, не к bind mount; видны через |
|
Кастомное имя тома без скоупа по имени стека |
Пример внешнего тома с параметризованным именем для поиска (имя в файле фиксировано, а реальное имя на платформе задаётся через переменную):
volumes: db-data: external: true name: actual-name-of-volume
Пустая запись (db-data: без атрибутов) — это том с настройками движка по умолчанию.
9. configs и secrets
Эти два раздела почти близнецы: оба монтируют данные файлами в контейнер, оба требуют явного разрешения на уровне сервиса. Разница — secrets заточены под чувствительные данные и имеют более узкий набор источников.
|
|
|
|---|---|---|
Источники |
|
|
Куда монтируется по умолчанию |
|
|
Права по умолчанию |
мир-readable, |
мир-readable, |
|
— |
Нет, только обычный Compose. Для stack deploy используйте |
|
поддерживается |
поддерживается |
Все четыре варианта источника для configs:
configs: http_config: file: ./httpd.conf # 1. из файла http_config_ext: external: true # 2. уже существует на платформе app_config: content: | # 3. инлайн-контент, с интерполяцией переменных debug=${DEBUG} spring.application.name=${COMPOSE_PROJECT_NAME} simple_config: environment: "SIMPLE_CONFIG_VALUE" # 4. из переменной окружения хоста
secrets — только file и environment:
secrets: server-certificate: file: ./server.cert token: environment: "OAUTH_TOKEN"
При деплое <project_name>_http_config и <project_name>_server-certificate создаются автоматически. Если external: true — все прочие атрибуты, кроме name, под запретом, Compose отклонит файл как невалидный, если найдёт что-то ещё.
Поиск внешнего ресурса под другим именем (удобно, когда имя ключа известно заранее, а реальный ID подставляется при деплое):
configs: http_config: external: true name: "${HTTP_CONFIG_KEY}"
10. models — AI-модели в Compose
Top-level models описывает AI-модели, которые Compose пуллит как OCI-артефакты, запускает через model runner и отдаёт сервисам как API.
services: app: image: app models: - ai_model models: ai_model: model: ai/model
Сервис app получает доступ к модели, а Compose сам прокидывает в контейнер переменную с адресом, например AI_MODEL_URL.
Атрибут |
Что делает |
|---|---|
|
Обязательный. Идентификатор OCI-артефакта модели, который пуллится и запускается |
|
Максимальный размер контекста (в токенах) |
|
Список сырых флагов командной строки для движка инференса |
Длинный синтаксис на уровне сервиса (с явным именем переменной) уже был в разделе 3.7 — endpoint_var / model_var.
models: my_model: model: ai/model context_size: 1024 runtime_flags: - "--a-flag" - "--another-flag=42"
11. profiles — включаем нужные сервисы
profiles позволяет держать в одном файле сервисы для разных сценариев (тесты, дебаг, прод) и включать только нужные. Сервис без profiles всегда активен. Если ни один профиль сервиса не совпал с активными — сервис игнорируется, если только его не запросили явно командой (тогда его профиль активируется автоматически).
services: web: image: web_image test_lib: image: test_lib_image profiles: [test] coverage_lib: image: coverage_lib_image depends_on: - test_lib profiles: [test] debug_lib: image: debug_lib_image depends_on: - test_lib profiles: [debug]
Сценарий запуска |
Какие сервисы в модели |
|---|---|
Без активных профилей |
только |
Профиль |
|
Профиль |
|
Профили |
все четыре сервиса |
Явный запуск |
активируется профиль |
Явный запуск |
ошибка — зависимость |
Явный запуск |
профиль |
Важно: ссылки на другие сервисы через links, extends или синтаксис service:xxx не включают автоматически отключенный профилем сервис — в этом случае Compose вернёт ошибку, а не подключит сервис “по умолчанию”.
12. include — модульные compose-файлы
include нужен, чтобы выносить часть модели приложения в отдельные файлы и подключать их — для переиспользования, или когда разные команды должны видеть только свою часть. Каждый подключённый файл загружается как отдельная Compose-модель со своей собственной project directory (относительные пути внутри него считаются от его собственной папки, а не от вашей). Конфликты имён ресурсов Compose не сливает — только предупреждает.
include: - my-compose-include.yaml services: serviceA: build: . depends_on: - serviceB # объявлен в подключённом файле, но доступен как свой
Короткий синтаксис — просто список путей:
include: - ../commons/compose.yaml - ../another_domain/compose.yaml
Длинный синтаксис добавляет контроль над тем, как парсится подпроект:
include: - path: ../commons/compose.yaml project_directory: .. env_file: ../another/.env
Атрибут |
Что делает |
|---|---|
|
Обязательный. Путь к файлу, либо список путей, если несколько файлов нужно слить в один подпроект |
|
Базовая папка для относительных путей внутри включаемого файла, по умолчанию — папка самого файла |
|
|
Переменные окружения локального проекта имеют приоритет над значениями из env_file подключённого файла — то есть переопределить подпроект “снаружи” можно. include работает рекурсивно: если подключённый файл сам что-то include-ит, эти файлы подключатся тоже. И поддерживается интерполяция прямо в пути:
include: - ${INCLUDE_PATH:?FOO}/compose.yaml
13. Extensions (x-) и YAML-фрагменты
Это два разных механизма с одной целью — не повторять одно и то же по десять раз в файле.
Extensions — любое поле, начинающееся с x-. Это единственное место, где Compose молча игнорирует неизвестное поле, причём работает на любом уровне вложенности, включая платформоспецифичные расширения внутри стандартных секций. Исторически сложившиеся вендорские префиксы: docker (Docker), kubernetes (Kubernetes).
x-custom: foo: [bar, zot] services: webapp: image: example/webapp x-foo: bar
service: backend: deploy: placement: x-aws-role: "arn:aws:iam::XXXXXXXXXXXX:role/foo" x-aws-region: "eu-west-3"
Фрагменты — это обычный YAML-механизм анкоров (&имя) и алиасов (*имя), без всякого отношения к Compose как таковому. Анкор резолвится раньше, чем подставляются переменные (${VAR}), поэтому переменными нельзя управлять самими анкорами/алиасами. Анкор можно поставить прямо на поле внутри сервиса, не только в x--блоке:
services: first: image: my-image:latest environment: &env - CONFIG_KEY - EXAMPLE_KEY second: image: another-image:latest environment: *env
Анкоры особенно хорошо работают в связке с x--расширениями, чтобы общий блок не “принадлежал” ни одному сервису:
x-env: &env environment: - CONFIG_KEY - EXAMPLE_KEY services: first: <<: *env image: my-image:latest second: <<: *env image: another-image:latest
Частичное переопределение через YAML merge (<<:) — взять анкор, но поменять конкретное поле:
volumes: db-data: &default-volume driver: default name: "data" metrics: <<: *default-volume name: "metrics"
Несколько анкоров сразу — <<: [*a, *b]:
x-environment: &default-environment FOO: BAR x-keys: &keys KEY: VALUE services: frontend: environment: <<: [*default-environment, *keys] YET_ANOTHER: VARIABLE
YAML merge (
<<:) работает только с мапами. Если используете список переменных окружения вида- FOO=BAR, фрагменты в этом виде не сработают — нужна именно мап-формаFOO: BAR.
И раз уж заговорили про extension.md — там же, на правах справочника, описаны два формата значений, которые используются по всему Compose-файлу:
Байтовые значения ({amount}{unit}, единицы b, k/kb, m/mb, g/gb):
2b 1024kb 2048k 300m 1gb
Длительности ({value}{unit}, единицы us, ms, s, m, h, можно комбинировать без разделителя):
10ms 40s 1m30s 1h5m30s20ms
14. interpolation — переменные ${VAR}
Compose поддерживает Bash-подобный синтаксис подстановки переменных: $VAR и ${VAR} равнозначны, но у фигурных скобок есть дополнительные формы. Важно: Compose обрабатывает строку после $ только если она образует валидное имя переменной — либо [_a-zA-Z][_a-zA-Z0-9]*, либо ${...}. В остальных случаях строка сохраняется как есть.
Интерполяция применяется до merge, на уровне каждого файла отдельно.
Форма |
Что делает |
|---|---|
|
Прямая подстановка значения |
|
|
|
|
|
Завершить с ошибкой, если |
|
Завершить с ошибкой, только если |
|
|
|
|
Подстановки можно вкладывать друг в друга: ${VARIABLE:-${FOO:-default}}.
Если переменная не резолвится и default не задан — Compose выводит предупреждение и подставляет пустую строку. Расширенные shell-фичи типа ${VARIABLE/foo/bar} не поддерживаются.
Чтобы получить буквальный знак доллара и не дать Compose его интерпретировать — $$:
web: command: "$$VAR_NOT_INTERPOLATED_BY_COMPOSE"
Отдельный нюанс: интерполяция применяется только к значениям, не к ключам. Если ключ — произвольная пользовательская строка (например, в labels или environment), для интерполяции ключа нужен альтернативный синтаксис со знаком =:
services: foo: labels: "$VAR_NOT_INTERPOLATED_BY_COMPOSE": "BAR" # ключ — как есть services: foo: labels: - "$VAR_INTERPOLATED_BY_COMPOSE=BAR" # а здесь сработает
15. merge — слияние нескольких compose-файлов
Когда модель приложения собирается из нескольких файлов (например, compose.yaml + compose.override.yaml), Compose сливает их по понятным правилам, плюс пара спецтегов для ручного управления.
Тип данных |
Правило |
|---|---|
Мапа ( |
Недостающие ключи добавляются, общие — рекурсивно сливаются |
Список ( |
Значения из второго файла добавляются к значениям из первого |
# файл 1 # файл 2 services: services: foo: foo: key1: value1 key2: VALUE key2: value2 key3: value3
→ результат: key1: value1, key2: VALUE, key3: value3.
Но есть исключения из этих двух правил:
Что |
Правило |
|---|---|
|
Не складываются, а полностью перезаписываются последним файлом |
|
Хотя формально это списки, Compose считает их по уникальному ключу: новые записи добавляются, совпадающие по ключу — сливаются как мапы |
Пример с уникальным ключом для volumes (совпал target: /work — значит, это “тот же” элемент, второй файл выигрывает):
# файл 1: volumes: [foo:/work] # файл 2: volumes: [bar:/work] # результат: volumes: [bar:/work]
Два спецтега YAML для ручного управления слиянием:
!reset — стереть значение, заданное предыдущим файлом (тип сохраняется как default/null, конкретное значение после тега не важно, но для читаемости лучше явно писать null или []):
# compose.yaml services: app: image: myapp ports: ["8080:80"] environment: FOO: BAR # compose.override.yaml services: app: ports: !reset [] environment: FOO: !reset null # результат services: app: image: myapp
!override — полностью заменить значение, игнорируя обычные правила слияния (актуально для ports/volumes/secrets/configs, которые иначе слились бы по уникальному ключу, а не заменились целиком):
# compose.yaml: ports: ["8080:80"] # compose.override.yaml: services: app: ports: !override - "8443:443" # результат: ports: ["8443:443"] # без !override получили бы оба порта одновременно
Заключение
Если выбросить из головы все таблицы, смысл главы простой: compose.yaml — это не один большой плоский список настроек, а несколько независимых top-level разделов (services, networks, volumes, configs, secrets, models, include, x-*), которые ссылаются друг на друга по имени. Сервис сам по себе — самый объёмный раздел, потому что в нём собрано почти всё: какой образ запускать, как его собрать (build), как развернуть (deploy), как разрабатывать (develop), к каким сетям/томам/секретам подключить.
Отдельно стоит держать в голове три механики, которые работают сквозь весь файл и легко забываются: интерполяция переменных (${VAR} и её формы с default/required/alternative — применяется до merge, на уровне каждого файла отдельно), правила merge при работе с несколькими файлами (обычный merge ≠ правила extends, и для обоих есть исключения вроде command/healthcheck.test или уникальных ключей у volumes/ports), и YAML-анкоры — они подставляются раньше, чем интерполяция переменных, так что переменными анкоры не настроить.
Теперь, когда структура самого файла понятна, в следующей части пойду разбирать сетевую модель Docker подробнее — благо top-level networks и атрибуты подключения на уровне сервиса я здесь уже показал.
© 2026 ООО «МТ ФИНАНС»
Joshuya
Thank you for the article! Could you please tell me how I can download the tables from it? I'd like to combine them all into a single cheat sheet for easy reference. Unfortunately, they don't copy properly on my end. :(