Докер
Docker Fundamentals: что внутри compose.yaml и как там всё устроено
Третья часть серии статей по Docker. Подробный справочник по структуре compose-файла — top-level разделы, services, build, deploy, networks, volumes, configs, secrets, интерполяция переменных, merge-правила и YAML-фрагменты.
Введение
В прошлой части я разобрал хранилища — тома, bind mount, tmpfs. В третьей части (в этой) планировалось добраться до сети. До неё дойдём, но прежде чем разбирать, как сервисы общаются друг с другом, стоит на секунду остановиться и разобрать сам файл, в котором мы всё это описываем. Потому что compose.yaml — это спецификация со своими top-level разделами, правилами слияния, подстановкой переменных и кучей мелких нюансов, которые легко упустить, если читать документацию по верхам.
Эта часть, по сути, большой справочник по структуре Compose-файла: держите статью под рукой и возвращайтесь к ней, когда понадобится конкретный атрибут.
Из чего состоит compose-файл
Compose-файл описывает модель приложения через набор top-level (верхнеуровневых) разделов. Обязателен в основном только один — services, остальные подключаются по необходимости.
| Раздел | Что описывает | Обязателен |
|---|---|---|
services | Сервисы приложения — абстракция над контейнерами | Да |
networks | Именованные сети для сервисов | Нет, иначе используется неявная сеть default |
volumes | Именованные тома | Нет |
configs | Неконфиденциальные конфигурационные данные | Нет |
secrets | Чувствительные данные | Нет |
models | AI-модели, которые пуллятся и обслуживаются runner’ом | Нет |
include | Подключение и слияние других compose-файлов | Нет |
x-* (extensions) | Произвольные данные для переиспользования, Compose их игнорирует | Нет |
version | Только для обратной совместимости, ни на что не влияет | Нет, и больше не нужен |
name | Имя проекта по умолчанию | Нет, иначе подставляется автоматически |
profilesвлияют только наservices. Все остальные top-level разделы всегда активны, независимо от того, какие профили включены.
version и name
Кому и зачем: если вы только начинаете — можно пропустить.
versionбольше ни на что не влияет, аnameнужна только если вы хотите явно задать имя проекта вместо автогенерируемого.
version — поле, которое раньше реально влияло на то, по какой схеме Compose читает файл. Сейчас оно ни на что не влияет: Compose всегда разбирает файл по самой свежей схеме, что бы в version ни было написано. Если поле в файле есть, Compose просто выведет предупреждение, что оно устарело, и продолжит работать как обычно. Смысла писать его в новых файлах нет. (С другой стороны, возможно, оно используется в старых версиях Docker, где применяется старая схема.)
name — имя проекта, которое используется по умолчанию, если вы не задаёте его другим способом (флагом, переменной окружения и т.п.). Имя проекта доступно для интерполяции как COMPOSE_PROJECT_NAME:
services
Кому и зачем: всем. Это единственный обязательный раздел compose-файла — без него Compose просто не поймёт, что запускать.
Сервис, по сути, рецепт для одного или нескольких одинаковых контейнеров: какой образ запускать, с какими портами, переменными окружения, томами и так далее. Когда я пишу services.web, я описываю не один конкретный контейнер, а правило, по которому Compose его создаёт. Контейнеров по этому правилу может получиться и несколько одинаковых копий (реплик), если задать scale или deploy.replicas. Удобство в том, что сервис можно масштабировать или пересоздавать отдельно от остальных: например, поднять три копии web, пока db остаётся в одном экземпляре.
У сервиса есть две опциональные секции, и у каждой свой мини-стандарт внутри общего. build описывает, как собрать образ (это Compose Build Specification), deploy, как развернуть сервис и с какими ограничениями (Compose Deploy Specification). Если платформа их не поддерживает, файл всё равно остаётся валидным, просто эти секции игнорируются.
Атрибутов у сервиса очень много, так что разобью их по смыслу.
Образ, сборка и запуск процесса
| Атрибут | Что делает | Пример / примечание |
|---|---|---|
image | Образ для запуска контейнера, формат [registry/][project/]image[:tag @digest] | image: redis:5 |
build | Конфигурация сборки образа из исходников (см. раздел «build») | — |
platform | Целевая платформа os[/arch[/variant]] | platform: linux/arm64/v8 |
pull_policy | Когда и как Compose пуллит образ: always, never, missing (default, alias if_not_present), build, daily, weekly, every_<duration> | pull_policy: every_12h |
scale | Сколько контейнеров поднимать по умолчанию (должно совпадать с deploy.replicas, если задано и то, и то) | scale: 3 |
runtime | Какой OCI runtime использовать, по умолчанию runc | runtime: runc |
provider | Передаёт управление жизненным циклом сервиса внешнему бинарю (Compose сам не управляет) | см. ниже |
read_only | Контейнер создаётся с файловой системой только на чтение | read_only: true |
init | Запускает init-процесс (PID 1), который форвардит сигналы и подчищает зомби-процессы | init: true |
command | Переопределяет CMD из образа. null — команда из образа, []/'' — пустая команда | command: bundle exec thin -p 3000 |
entrypoint | Переопределяет ENTRYPOINT из образа; если задан и не null, CMD образа игнорируется | список или строка, как в Dockerfile |
working_dir | Переопределяет рабочую директорию (аналог WORKDIR) | — |
user | Пользователь, от которого выполняется процесс (аналог USER), иначе root | — |
hostname / domainname | Кастомное имя хоста / домена контейнера (валидный RFC 1123) | — |
container_name | Своё имя контейнера вместо автогенерируемого. Формат [a-zA-Z0-9][a-zA-Z0-9_.-]+. С ним нельзя масштабировать сервис | container_name: my-web-container |
Про
command: в отличие отCMDв Dockerfile, это поле не выполняется автоматически черезSHELL. Если нужна интерполяция переменных шеллом — оборачивайте сами:command: /bin/sh -c 'echo "hello $$HOSTNAME"'.
Про provider отдельно, потому что без объяснения этот пример выглядит как магия. Короче: иногда нужный «сервис» — это не контейнер вообще, а что-то внешнее, например, облачная база данных, которую поднимает не Docker, а сторонняя программа. provider говорит Compose: «Не пытайся создавать контейнер для этого сервиса сам, вызови вот эту программу, и пусть она разбирается».
Что здесь происходит по шагам:
- У сервиса
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снести то, что он создал.
Жизненный цикл и зависимости
| Атрибут | Что делает |
|---|---|
restart | Политика перезапуска: no (по умолчанию), always, on-failure[:max-retries], unless-stopped |
healthcheck | Проверка “здоровья” контейнера, переопределяет HEALTHCHECK из образа |
depends_on | Порядок запуска/остановки сервисов |
profiles | Список профилей, при которых сервис активен (см. раздел «profiles») |
post_start | Хуки, выполняемые после старта контейнера |
pre_stop | Хуки перед остановкой контейнера (не сработают при аварийном завершении) |
stop_signal | Сигнал для остановки, по умолчанию SIGTERM |
stop_grace_period | Сколько ждать перед SIGKILL, по умолчанию 10 секунд |
healthcheck пример:
test может быть строкой (тогда это эквивалент CMD-SHELL, команда выполняется через /bin/sh на Linux) или списком, где первый элемент — NONE, CMD или CMD-SHELL. Чтобы выключить healthcheck из образа — test: NONE или disable: true.
depends_on — короткий и длинный синтаксис:
| Параметр длинного синтаксиса | Значение |
|---|---|
condition: service_started | То же самое, что короткий синтаксис |
condition: service_healthy | Ждать, пока зависимость не станет healthy (здоров и готов работать) |
condition: service_completed_successfully | Ждать успешного завершения зависимости |
restart: true | Перезапускать этот сервис после обновления зависимости. Касается только явного рестарта через Compose, не автоматического рестарта runtime’а |
required: false | Не падать, если зависимость недоступна — только предупреждение |
post_start / pre_stop устроены одинаково:
Здесь после старта контейнера test Compose выполнит внутри него команду ./do_something_on_startup.sh — от имени root, с повышенными правами и с дополнительной переменной FOO=BAR в окружении именно этой команды. Сам контейнер при этом продолжает работать как обычно, никто его не перезапускает.
Сеть на уровне сервиса
| Атрибут | Что делает |
|---|---|
ports | Публикация портов хост:контейнер. Нельзя использовать с network_mode: host — будет runtime error |
expose | Открыть порт только для других контейнеров в сети, без публикации на хост |
networks | К каким именованным сетям подключён сервис, плюс настройки на уровне подключения |
network_mode | bridge, none, host, service:{name}, container:{name} несовместимо с networks |
links | Связь с контейнерами другого сервиса по имени/алиасу (не обязательны для общения внутри одной сети) |
external_links | Связь с сервисами вне текущего Compose-приложения |
dns, dns_opt, dns_search | Кастомные DNS-серверы, опции резолвера, домены поиска |
extra_hosts | Дополнительные записи в /etc/hosts |
mac_address | MAC-адрес контейнера (на уровне сервиса). Некоторые runtime’ы могут отклонить это значение (тогда используйте networks.<name>.mac_address) |
ports, короткий синтаксис [HOST:]CONTAINER[/PROTOCOL]:
Поясню пару строк, чтоб было понятно. "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.
Длинный синтаксис портов:
expose:
Если в Dockerfile образа уже объявлены порты через
EXPOSE, они видны другим контейнерам в сети даже еслиexposeв Compose-файле не задан.
extra_hosts поддерживает короткий синтаксис (список строк) и длинный (маппинг):
networks на уровне сервиса поддерживает дополнительные параметры для каждого подключения к сети:
Под-атрибут networks.<name> | Что делает |
|---|---|
aliases | Альтернативные имена сервиса в этой сети (свои для каждой сети). Алиас может быть shared между несколькими контейнерами и сервисами — тогда к кому именно он резолвится, не гарантируется |
ipv4_address, ipv6_address | Статический IP (нужен ipam с подходящим subnet в top-level networks) |
interface_name | Имя сетевого интерфейса внутри контейнера |
link_local_ips | Список link-local IP |
mac_address | MAC именно для этой сети |
driver_opts | Опции драйвера, специфичные для подключения |
gw_priority | Сеть с наибольшим значением становится дефолтным шлюзом. По умолчанию 0 |
priority | Порядок подключения сервиса к сетям. Не влияет на выбор шлюза и не контролирует имя интерфейса (eth0 и т.п.) — для этого нужен interface_name |
Сервис backend подключён сразу к двум сетям, и в каждой у него своё “прозвище”. Контейнеры в сети back-tier могут достучаться до него по имени database, а контейнеры в сети admin по имени mysql. Имя самого сервиса (backend) при этом тоже продолжает работать как обычно. Если networks не задан вообще, сервис неявно подключается к сети default, это эквивалентно networks: {default: {}}. Чтобы вообще отключить сетевой доступ, network_mode: none.
Данные и конфигурация
| Атрибут | Что делает |
|---|---|
volumes | Монтирование томов/bind mount/tmpfs в контейнер (на уровне сервиса) |
volumes_from | Подключить все тома другого сервиса/контейнера целиком. Можно указать ro/rw и ссылаться на контейнер вне Compose через container:<name> |
configs | Доступ к конфигам из top-level configs |
secrets | Доступ к секретам из top-level secrets |
env_file | Файл(ы) с переменными окружения |
environment | Переменные окружения напрямую в файле |
label_file | Файл(ы) с лейблами, альтернатива labels для случаев когда лейблов много |
labels | Метаданные на контейнере |
tmpfs | tmpfs-монтирование (короткая форма, без отдельного top-level раздела) |
volumes, короткий синтаксис VOLUME:CONTAINER_PATH[:ACCESS_MODE], длинный — объект с type/source/target/read_only/bind/volume/tmpfs/image:
configs и secrets устроены практически одинаково: короткий синтаксис просто даёт доступ и монтирует под именем источника, длинный позволяет задать target/uid/gid/mode:
Нюанс:
uid/gid/modeдля секретов работают только если источник секретаenvironment. Если источникfile, Compose использует bind-mount, и эти атрибуты тихо игнорируются.
label_file — удобно, когда лейблов много и не хочется засорять Compose-файл:
Формат файла такой же, как у env_file — пары KEY=VALUE. Если несколько файлов, обрабатываются сверху вниз; при конфликте побеждает последний файл. Если одно и то же поле задано и в label_file, и в labels — побеждает labels.
env_file:
Если переменная задана и в env_file, и в environment — побеждает environment, даже если значение пустое.
Несколько правил парсинга формата .env файла, которые полезно знать:
- Строки начиная с
#— комментарии, игнорируются - Разделитель между ключом и значением —
=или: - Значения в двойных кавычках поддерживают escape-последовательности:
\n,\t,\\ - Значения в одинарных кавычках берутся буквально:
VAR='$OTHER'→$OTHER - Инлайновый комментарий для незакавыченных значений нужно предварять пробелом:
VAR=VAL # comment→VAL
environment, мапа или список (булевы значения обязательно в кавычках, иначе YAML превратит их в True/False):
tmpfs:
Про лейблы и зарезервированный префикс
Compose автоматически проставляет на каждый контейнер два canonical label’а:
com.docker.compose.project— имя проектаcom.docker.compose.service— имя сервиса из Compose-файла
Префикс com.docker.compose зарезервирован. Если указать лейбл с таким префиксом в Compose-файле, будет runtime error.
Ресурсы и изоляция
CPU
| Атрибут | Что делает |
|---|---|
cpus | Сколько (потенциально виртуальных) ядер CPU выделить контейнеру. Число дробное, 0.000 значит “без лимита”. Если задано и здесь, и в deploy.resources.limits.cpus, значения должны совпадать |
cpu_count | Целое число — сколько именно CPU контейнер может использовать |
cpu_percent | Какой процент от всех доступных CPU доступен контейнеру |
cpu_shares | Относительный вес контейнера при распределении CPU между несколькими контейнерами. Это не абсолютное число ядер, а пропорция по сравнению с другими |
cpu_period | Период CFS-планировщика ядра Linux (Completely Fair Scheduler). Работает в связке с cpu_quota |
cpu_quota | Сколько времени CPU достаётся контейнеру за один такой период |
cpu_rt_runtime | Время, которое контейнер может работать в режиме real-time планировщика. Указывается числом микросекунд или duration, например 400ms |
cpu_rt_period | Период того же real-time планировщика, тоже в микросекундах или duration |
cpuset | Список или диапазон конкретных ядер, на которых разрешено выполняться: 0-3 или 0,1 |
Память
| Атрибут | Что делает |
|---|---|
mem_limit | Жёсткий лимит памяти в байтовом формате (512m, 1g…). Должен совпадать с deploy.resources.limits.memory, если задан и там, и там |
mem_reservation | Гарантированный резерв памяти, тот же формат. Сверяется с deploy.resources.reservations.memory |
mem_swappiness | Число от 0 до 100. Показывает, насколько активно ядро хоста выгружает память контейнера в swap: 0 — не выгружать вообще, 100 — выгружать максимально активно. Дефолт зависит от платформы |
memswap_limit | Лимит на память плюс swap вместе. Работает только если задан mem_limit. Пример: mem_limit: 300m, memswap_limit: 1g — контейнеру достанется 300 МБ обычной памяти и до 700 МБ (1g минус 300m) свопа сверху. Не задали memswap_limit, но задали mem_limit? Тогда Docker по умолчанию выдаёт swap в том же объёме, что и сам лимит памяти. Значение 0 — игнорируется и считается незаданным. Значение, равное mem_limit — контейнер вообще не получает swap. -1 — swap без ограничений |
Диск и устройства
| Атрибут | Что делает |
|---|---|
blkio_config.weight | Относительный приоритет контейнера в очереди на диск. Число от 10 до 1000, по умолчанию 500: чем больше, тем больше доля пропускной способности при конкуренции с другими контейнерами |
blkio_config.weight_device | То же самое, но отдельно для конкретного устройства: path плюс свой weight |
blkio_config.device_read_bps / device_write_bps | Жёсткий лимит скорости чтения или записи для конкретного устройства, в байтах в секунду |
blkio_config.device_read_iops / device_write_iops | То же самое, но лимит не на скорость, а на число операций в секунду |
devices | Прокидывает устройство хоста в контейнер: HOST_PATH:CONTAINER_PATH[:CGROUP_PERMISSIONS]. Либо CDI-синтаксис (vendor1.com/device=gpu), если за выбор устройства отвечает сам runtime |
device_cgroup_rules | Правила cgroup для устройств в формате, который понимает само ядро Linux (Device Whitelist Controller) |
gpus | Запрашивает GPU для контейнера: список объектов с driver и count, либо просто строка all, чтобы отдать все доступные GPU |
storage_opt | Опции storage-драйвера контейнера. Например, ограничение размера: size: '1G' |
Пример блока I/O лимитов:
Здесь weight: 300 понижает приоритет контейнера в очереди на диск (дефолт 500, тут ниже). А device_read_bps работает отдельно от веса и жёстко: чтение именно с /dev/sdb не быстрее 12 МБ/с, неважно, какой у контейнера приоритет.
Capabilities и безопасность
| Атрибут | Что делает |
|---|---|
privileged | Запускает контейнер с повышенными привилегиями, по сути почти без изоляции от хоста. Конкретный эффект зависит от платформы |
cap_add | Добавляет конкретные Linux capabilities. Например: cap_add: [ALL] |
cap_drop | Убирает конкретные capabilities: cap_drop: [NET_ADMIN, SYS_ADMIN] |
security_opt | Переопределяет схему лейблов безопасности (SELinux/AppArmor). Булевы опции можно указывать без значения (no-new-privileges), с =true или :true — всё эквивалентно |
group_add | Добавляет пользователя внутри контейнера в дополнительные группы по имени или номеру. Пригождается, когда несколько контейнеров от разных пользователей пишут в один файл на общем томе: владельцем файла делают общую группу |
Namespace и изоляция
| Атрибут | Что делает |
|---|---|
ipc | Режим изоляции IPC. shareable: свой приватный IPC namespace с возможностью поделиться им с другими контейнерами. service:{name}: присоединиться к IPC namespace другого сервиса вместо своего |
pid | В каком PID namespace запускать контейнер. Значения зависят от платформы |
uts | UTS namespace, то есть имя хоста и домена на уровне ядра. host означает, что контейнер использует тот же UTS namespace, что и хост |
userns_mode | Какой user namespace использовать для сервиса. Значения платформо-зависимы |
cgroup | В каком cgroup namespace запускать контейнер: host — в cgroup namespace самого движка, private — в своём собственном, изолированном |
cgroup_parent | Родительская cgroup для контейнера, если нужно поместить его в конкретное место иерархии cgroup |
isolation | Технология изоляции контейнера. Поддерживаемые значения платформо-зависимы, актуально в первую очередь для Windows |
Прочие лимиты
| Атрибут | Что делает |
|---|---|
pids_limit | Максимум процессов и потоков (PID) внутри контейнера. -1 снимает лимит. Должен совпадать с deploy.resources.limits.pids, если задан и там |
oom_kill_disable | Запрещает платформе убивать именно этот контейнер при нехватке памяти на хосте |
oom_score_adj | Число от -1000 до 1000, которое влияет на то, выберет ли платформа этот контейнер для убийства при OOM. Чем больше число, тем выше шанс быть убитым первым |
sysctls | Меняет kernel-параметры внутри контейнера, но только namespaced — те, что не затрагивают хост целиком. Пример: net.core.somaxconn |
ulimits | Переопределяет ulimit для контейнера. Либо число для одного лимита, либо объект с soft и hard |
shm_size | Размер /dev/shm (shared memory) внутри контейнера, в байтовом формате |
credential_spec | Спецификация учётных данных managed service account для Windows-контейнеров. Варианты: file://..., registry://..., либо ссылка на конфиг через config |
use_api_socket | Даёт контейнеру доступ к API-сокету самого движка. Изнутри можно делать pull и push от тех же credentials, что и снаружи |
Логирование
logging настраивает драйвер логирования для контейнеров сервиса:
driver — имя драйвера логирования. Дефолт и доступные значения зависят от платформы. options — опции драйвера в виде key-value пар.
Прочее: метаданные, расширение, модели
| Атрибут | Что делает |
|---|---|
annotations | Аннотации контейнера (массив или мапа) |
attach | false означает не собирать логи сервиса, пока не попросили явно |
deploy | Конфигурация развёртывания (раздел «deploy») |
develop | Конфигурация для live-разработки (раздел «develop») |
models | Какие AI-модели использует сервис (раздел «models») |
extends | Наследование конфигурации сервиса из другого файла/сервиса |
tty | Выделить псевдо-TTY (true/false) |
stdin_open | Держать stdin открытым (аналог -i) |
models на уровне сервиса:
Если endpoint_var/model_var не заданы, имена переменных генерируются автоматически: имя модели в верхнем регистре, - заменяется на _, плюс суффикс _URL.
extends — отдельная большая тема, потому что у него свои правила слияния, отличаются от обычного merge между файлами (см. раздел «merge»):
- Если
fileне указан, берётся сервис из текущего файла. - Циклические ссылки запрещены, Compose вернёт ошибку.
- При
extendsресурсы (volumes, networks, configs, secrets, links, depends_on и т.п.), которые использует наследуемый сервис, не подтягиваются автоматически, их нужно объявить в файле, который наследует. Правила слияния приextends(своя, отдельная от общего merge-механизма логика):
| Тип значения | Как сливается |
|---|---|
Мапы (environment, labels, healthcheck, sysctls, ulimits, build.args и т.п.) | Ключи текущего сервиса перекрывают ключи из наследуемого, остальное сохраняется |
volumes, devices, blkio_config.device_* | Считаются мапами по ключу, в качестве ключа берутся пути назначения внутри контейнера |
Списки (cap_add, cap_drop, configs, ports, secrets, expose, security_opt и пр.) | Элементы объединяются, дубликаты удаляются |
Списки dns, dns_search, env_file, tmpfs (если задан в виде списка) | Объединяются, дубликаты не удаляются |
| Скаляры | Значение текущего сервиса побеждает |
Отдельный нюанс с healthcheck: текущий сервис не может выставить disable: true, если наследуемый сервис этого не делает, в таком случае Compose вернёт ошибку.
build — как Compose собирает образ
Кому и зачем: если у вас уже есть готовый образ из реестра (Docker Hub и т.п.) — раздел можно пропустить. Нужен, только если вы собираете образ из исходников через Compose.
build можно задать строкой (путь к контексту сборки) или объектом с детальными настройками. Если задана строка, в этой папке Compose будет искать Dockerfile. Относительный путь резолвится от директории проекта, абсолютный — работает, но Compose выдаст предупреждение о непортируемости файла.
Если у сервиса заданы и build, и image, поведение регулируется pull_policy: по умолчанию Compose сперва пытается запулить образ, и только если не нашёл, собирает из исходников.
Атрибут build.* | Что делает | Пример |
|---|---|---|
context | Путь к директории с Dockerfile или Git URL. По умолчанию . (директория проекта). Абсолютный путь — предупреждение о непортируемости | context: ./dir |
dockerfile | Альтернативный путь к Dockerfile относительно контекста | dockerfile: webapp.Dockerfile |
dockerfile_inline | Содержимое Dockerfile прямо в compose-файле (несовместимо с dockerfile) | dockerfile_inline: FROM baseimage ... |
args | Build-аргументы (ARG из Dockerfile), мапа или список | args: {GIT_COMMIT: cdc3b19} |
additional_contexts | Доп. именованные контексты для сборки. Поддерживает пути, Git URL, ссылки на образы (docker-image://my-app:latest) и образы других сервисов (service:name) | additional_contexts: {base: service:base} |
cache_from / cache_to | Источники/назначения кэша сборки, формат [NAME type=TYPE[,KEY=VALUE]] | cache_from: [alpine:latest, type=gha] |
target | Стадия в multi-stage Dockerfile | target: prod |
network | Сеть для RUN-инструкций во время сборки, либо none | network: host |
platforms | Список целевых платформ образа. Если не задан — Compose включает платформу сервиса автоматически. Ошибка, если список не пустой, но не содержит платформу сервиса | ["linux/amd64", "linux/arm64"] |
pull | Принудительно пуллить базовые образы (FROM), даже если они в локальном кэше | pull: true |
no_cache | Полная пересборка без кэша builder’а. Применяется только к слоям из Dockerfile; referenced images всё равно могут браться из локального стора (для их обновления используйте pull: true) | no_cache: true |
privileged | Сборка с повышенными привилегиями | privileged: true |
isolation | Технология изоляции контейнера сборки | платформо-зависимо |
labels | Метаданные на итоговом образе | мапа или список |
shm_size | Размер shared memory при сборке | shm_size: "2gb" |
ssh | SSH-доступ для сборки (например, клонирование приватного репо) | ssh: [default] или ssh: [myproject=~/.ssh/key.pem] |
secrets | Доступ к секретам только во время сборки | см. ниже |
tags | Дополнительные теги для образа, в дополнение к image | ["myimage:mytag"] |
ulimits | ulimit’ы для контейнера сборки | как в services.ulimits |
extra_hosts | Доп. записи hosts во время сборки | как в services.extra_hosts |
entitlements | Доп. привилегированные права для сборки | [network.host, security.insecure] |
provenance | Provenance attestation для образа (bool или mode=...) | provenance: mode=max |
sbom | SBOM attestation (bool или generator=...) | sbom: true |
Секреты при сборке доступны только в момент build и работают иначе, чем services.secrets. В длинном синтаксисе атрибут target — это ID секрета в Dockerfile (тот самый id= в RUN --mount=type=secret):
Если у образа нет атрибута image, при пуше Compose пропускает его с предупреждением: пушить туда, по сути, нечего.
deploy — параметры развёртывания
Кому и зачем: если вы запускаете Compose на одной машине без Swarm/Kubernetes — большая часть раздела не применяется. Актуально для кластерных развёртываний с репликами, лимитами ресурсов и placement-констрейнтами.
deploy — это опциональная секция про то, как платформа должна запускать и масштабировать сервис. Если платформа не умеет в Deploy Spec, секция просто игнорируется, файл всё равно валиден.
| Атрибут | Что делает |
|---|---|
mode | Модель репликации: replicated (по умолчанию), global (1 задача на ноду), replicated-job (N задач до успешного завершения), global-job (1 задача на ноду до успешного завершения; автоматически запускается на новых нодах по мере их добавления) |
replicas | Сколько контейнеров держать запущенными при mode: replicated |
endpoint_mode | vip (виртуальный IP, балансировка платформой) или dnsrr (DNS round-robin) |
labels | Метаданные на самом сервисе (не на контейнерах) |
placement.constraints | Жёсткие требования к ноде (node.labels.disktype==ssd) |
placement.preferences | Стратегия распределения задач, пока только spread |
resources.limits / resources.reservations | Максимум / гарантированный минимум ресурсов |
restart_policy | Условия и параметры перезапуска контейнеров. Если не задан — Compose смотрит на restart из service-конфигурации как fallback |
update_config | Как накатывать обновления (rolling update) |
rollback_config | Как откатывать неудачное обновление |
Что тут настроено, по шагам: Compose держит 2 реплики сервиса (replicas: 2) с общим виртуальным IP на всех (endpoint_mode: vip), запускает их только на нодах с SSD (constraints) и старается равномерно раскидать реплики по зонам (preferences). Каждому контейнеру разрешено не больше половины ядра CPU и 50 МБ памяти, а гарантированно выделено четверть ядра и 20 МБ. При падении контейнер перезапускается с паузой 5 секунд между попытками, но не больше 3 раз. А когда сервис обновляется, контейнеры пересоздаются по 2 штуки за раз, и старая версия останавливается перед запуском новой (stop-first), а не одновременно с ней.
Параметры resources.*.devices (резервирование устройств типа GPU/TPU):
| Атрибут | Что делает |
|---|---|
capabilities | Обязательный список возможностей: gpu, tpu, либо специфичные для драйвера (с префиксом, например nvidia-compute) |
driver | Какой драйвер использовать для устройства |
count | Сколько устройств зарезервировать. Если не задан или задан как all — резервируются все подходящие устройства. Взаимоисключимо с device_ids |
device_ids | Конкретные ID устройств (взаимоисключимо с count) |
options | Опции драйвера в виде key-value |
restart_policy:
| Атрибут | Значение по умолчанию |
|---|---|
condition | any — перезапускать всегда; on-failure — только при ненулевом коде; none — никогда |
delay | 0 — задержка между попытками |
max_attempts | без ограничений. Важный нюанс: неудачная попытка засчитывается только если контейнер не поднялся успешно в течение window. То есть при max_attempts: 2 Compose может физически попробовать больше двух раз, пока не накопится 2 засчитанных провала |
window | 0 — оценивать успех сразу |
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-файле такого параметра нет.
develop — режим разработки (watch)
Кому и зачем: если вы не используете live-разработку (автоматическую пересборку при изменении файлов) — можно пропустить. Полезно, когда вы активно итерируете код и хотите, чтобы Compose сам подхватывал изменения.
develop — опциональная секция, появилась в Compose 2.22.0, нужна для “внутреннего цикла” разработки: следить за файлами и реагировать на изменения без полного пересоздания всего стека руками.
В этом примере у frontend действие sync: при изменении файлов в ./webapp/html Compose просто копирует их внутрь работающего контейнера по пути /var/www, не трогая сам контейнер (кроме папки node_modules, её игнорируем). У backend действие rebuild: при изменении файлов в ./backend/src Compose пересобирает образ заново и пересоздаёт контейнер с нуля — дольше, но нужно, когда правки требуют пересборки (например, меняется зависимость).
Атрибуты внутри каждого правила watch:
| Атрибут | Что делает |
|---|---|
path | Путь (относительно проекта), который мониторится |
action | Что делать при изменении: rebuild, restart (с 2.32.0), sync, sync+restart (с 2.23.0), sync+exec (с 2.32.0) |
target | Куда внутри контейнера синхронизировать файлы (только для sync-действий) |
ignore | Паттерны путей, которые игнорируются (синтаксис как у .dockerignore). Если в build-контексте есть .dockerignore, его паттерны загружаются как implicit content, а паттерны из Compose-модели добавляются к ним |
include | Паттерны путей, которые наоборот включаются в отслеживание (удобно вместо длинного ignore) |
initial_sync | Проверять при старте watch-сессии, что файлы в уже существующем контейнере синхронизированы |
exec | Команда, которая выполняется внутри контейнера при action: sync+exec |
exec — те же поля, что у lifecycle-хуков (command, user, privileged, working_dir, environment):
Если
includeначинается с*, обязательно берите паттерн в кавычки — иначе YAML примет звёздочку за alias-node.
networks — именованные сети
Кому и зачем: всем, у кого больше одного сервиса. Если сервисы должны общаться друг с другом по именам, а не через
localhost, этот раздел нужен.
Top-level networks позволяет объявить сети, которые можно переиспользовать между сервисами (подключение к сети на уровне сервиса всё равно нужно делать явно через services.<name>.networks).
В этом примере proxy и db изолированы друг от друга, потому что не делят общую сеть — общаться напрямую может только app.
| Атрибут | Что делает |
|---|---|
driver | Драйвер сети, ошибка если недоступен на платформе |
driver_opts | Опции драйвера, key-value |
attachable | Разрешить отдельным (standalone) контейнерам подключаться к сети |
enable_ipv4 / enable_ipv6 | Включить/выключить выдачу IPv4/IPv6 адресов. enable_ipv4: false удобен, когда нужна сеть только с IPv6 |
internal | Изолировать сеть от внешнего мира (по умолчанию Compose даёт внешнюю связность) |
ipam | Кастомная IPAM-конфигурация: driver, config (subnet/ip_range/gateway/aux_addresses), options |
labels | Метаданные сети (массив или мапа). Compose также автоматически проставляет com.docker.compose.project и com.docker.compose.network |
name | Кастомное имя сети без привязки к имени проекта |
external | Сеть уже существует и управляется не Compose; все прочие атрибуты, кроме name, недопустимы |
Если networks вообще не объявлен в файле, Compose создаёт неявную сеть default, и все сервисы без явного networks к ней подключаются автоматически. Кастомизировать её можно так же, как обычную сеть:
Внешняя сеть — по аналогии с внешними томами:
volumes верхнего уровня
Кому и зачем: если вы используете named volumes (а не только bind mount) — нужно. Без top-level объявления Compose не будет знать, что такой том существует.
Top-level volumes объявляет тома, которые можно переиспользовать между сервисами.
Оба сервиса смотрят в один и тот же том db-data, но по разным путям внутри своих контейнеров: backend пишет туда данные базы (/etc/data), а backup видит те же файлы по пути /var/lib/backup/data — то есть может забрать и заархивировать их, не трогая контейнер с самой базой.
docker compose up создаёт том, если он ещё не существует. Если том уже есть — используется существующий. Если том был удалён вручную вне Compose — пересоздаётся.
| Атрибут | Что делает |
|---|---|
driver | Драйвер тома, ошибка если недоступен |
driver_opts | Опции драйвера. Через driver: local + driver_opts (type: none, o: bind, device: /абсолютный/путь) делают “именованный bind mount” — стабильное имя тома, который физически указывает на конкретную папку хоста |
external | Том уже существует, Compose его не создаёт; прочие атрибуты кроме name недопустимы |
labels | Метаданные тома. Применяются только к именованным томам, не к bind mount; видны через docker volume inspect. Compose также автоматически проставляет com.docker.compose.project и com.docker.compose.volume |
name | Кастомное имя тома без скоупа по имени стека |
Пример внешнего тома с параметризованным именем для поиска (имя в файле фиксировано, а реальное имя на платформе задаётся через переменную):
Пустая запись (db-data: без атрибутов) — это том с настройками движка по умолчанию.
configs и secrets
Кому и зачем: если вы передаёте конфиги и секреты через обычные
environmentилиvolumes(bind mount) — можно пропустить. Этот раздел для более структурированного подхода через специализированные top-level секции.
Эти два раздела почти близнецы: оба монтируют данные файлами в контейнер, оба требуют явного разрешения на уровне сервиса. Разница — secrets заточены под чувствительные данные и имеют более узкий набор источников.
configs | secrets | |
|---|---|---|
| Источники | file, environment, content, external | file, environment |
| Куда монтируется по умолчанию | /<config-name> (Linux) / C:\<config-name> (Windows) | /run/secrets/<secret-name> |
| Права по умолчанию | мир-readable, 0444 | мир-readable, 0444 |
environment как источник поддерживается docker stack deploy? | — | Нет, только обычный Compose. Для stack deploy используйте file или external |
name для внешнего ресурса | поддерживается | поддерживается |
Все четыре варианта источника для configs:
secrets — только file и environment:
При деплое <project_name>_http_config и <project_name>_server-certificate создаются автоматически. Если external: true — все прочие атрибуты, кроме name, под запретом, Compose отклонит файл как невалидный, если найдёт что-то ещё.
Поиск внешнего ресурса под другим именем (удобно, когда имя ключа известно заранее, а реальный ID подставляется при деплое):
models — AI-модели в Compose
Кому и зачем: только если вы используете AI-модели через Compose runner. Для обычных проектов без ML — раздел не нужен.
Top-level models описывает AI-модели, которые Compose пуллит как OCI-артефакты, запускает через model runner и отдаёт сервисам как API.
Сервис app получает доступ к модели, а Compose сам прокидывает в контейнер переменную с адресом, например AI_MODEL_URL.
| Атрибут | Что делает |
|---|---|
model | Обязательный. Идентификатор OCI-артефакта модели, который пуллится и запускается |
context_size | Максимальный размер контекста (в токенах) |
runtime_flags | Список сырых флагов командной строки для движка инференса |
Длинный синтаксис на уровне сервиса (с явным именем переменной) уже был в разделе 3.7 — endpoint_var / model_var.
profiles — включаем нужные сервисы
Кому и зачем: если у вас один compose-файл, но разные сервисы для dev/prod/test — profiles позволяют включать только нужные. Если всё запускается всегда — можно пропустить.
profiles позволяет держать в одном файле сервисы для разных сценариев (тесты, дебаг, прод) и включать только нужные. Сервис без profiles всегда активен. Если ни один профиль сервиса не совпал с активными — сервис игнорируется, если только его не запросили явно командой (тогда его профиль активируется автоматически).
| Сценарий запуска | Какие сервисы в модели |
|---|---|
| Без активных профилей | только web |
Профиль test | web, test_lib, coverage_lib |
Профиль debug | web, debug_lib — но модель невалидна: debug_lib зависит от test_lib, а у него нет общего профиля с debug_lib |
Профили test и debug вместе | все четыре сервиса |
Явный запуск coverage_lib | активируется профиль test, test_lib подключается как зависимость |
Явный запуск debug_lib без профиля test | ошибка — зависимость test_lib не подходит по профилю |
Явный запуск debug_lib + активный профиль test | профиль debug включается автоматически, test_lib тоже стартует |
Важно: ссылки на другие сервисы через links, extends или синтаксис service:xxx не включают автоматически отключенный профилем сервис — в этом случае Compose вернёт ошибку, а не подключит сервис “по умолчанию”.
include — модульные compose-файлы
Кому и зачем: если ваш compose-файл стал огромным и вы хотите разбить его на части — этот раздел для вас. Для небольших проектов — не нужен.
include нужен, чтобы выносить часть модели приложения в отдельные файлы и подключать их — для переиспользования, или когда разные команды должны видеть только свою часть. Каждый подключённый файл загружается как отдельная Compose-модель со своей собственной project directory (относительные пути внутри него считаются от его собственной папки, а не от вашей). Конфликты имён ресурсов Compose не сливает — только предупреждает.
Короткий синтаксис — просто список путей:
Длинный синтаксис добавляет контроль над тем, как парсится подпроект:
| Атрибут | Что делает |
|---|---|
path | Обязательный. Путь к файлу, либо список путей, если несколько файлов нужно слить в один подпроект |
project_directory | Базовая папка для относительных путей внутри включаемого файла, по умолчанию — папка самого файла |
env_file | .env-файл(ы) со значениями по умолчанию для интерполяции в подключаемом файле. По умолчанию ищется .env в project_directory включаемого файла. Принимает строку или список строк, если нужно смержить несколько env-файлов |
Переменные окружения локального проекта имеют приоритет над значениями из env_file подключённого файла — то есть переопределить подпроект “снаружи” можно. include работает рекурсивно: если подключённый файл сам что-то include-ит, эти файлы подключатся тоже. И поддерживается интерполяция прямо в пути:
Extensions (x-) и YAML-фрагменты
Кому и зачем: если вы хотите переиспользовать куски YAML через якоря (
&/*/<<:) или добавить кастомные поля, которые Compose игнорирует — раздел полезен. Для простых файлов — можно пропустить.
Это два разных механизма с одной целью — не повторять одно и то же по десять раз в файле.
Extensions — любое поле, начинающееся с x-. Это единственное место, где Compose молча игнорирует неизвестное поле, причём работает на любом уровне вложенности, включая платформоспецифичные расширения внутри стандартных секций. Исторически сложившиеся вендорские префиксы: docker (Docker), kubernetes (Kubernetes).
Фрагменты — это обычный YAML-механизм анкоров (&имя) и алиасов (*имя), без всякого отношения к Compose как таковому. Анкор резолвится раньше, чем подставляются переменные (${VAR}), поэтому переменными нельзя управлять самими анкорами/алиасами. Анкор можно поставить прямо на поле внутри сервиса, не только в x--блоке:
Анкоры особенно хорошо работают в связке с x--расширениями, чтобы общий блок не “принадлежал” ни одному сервису:
Частичное переопределение через YAML merge (<<:) — взять анкор, но поменять конкретное поле:
Несколько анкоров сразу — <<: [*a, *b]:
YAML merge (
<<:) работает только с мапами. Если используете список переменных окружения вида- FOO=BAR, фрагменты в этом виде не сработают — нужна именно мап-формаFOO: BAR.
И раз уж заговорили про extension.md — там же, на правах справочника, описаны два формата значений, которые используются по всему Compose-файлу:
Байтовые значения ({amount}{unit}, единицы b, k/kb, m/mb, g/gb):
Длительности ({value}{unit}, единицы us, ms, s, m, h, можно комбинировать без разделителя):
interpolation — подстановка переменных
Кому и зачем: всем, кто использует
.env-файлы или переменные окружения в compose-файле. Если у вас нет ни одной${VAR}— можно пропустить.
Compose поддерживает Bash-подобный синтаксис подстановки переменных: $VAR и ${VAR} равнозначны, но у фигурных скобок есть дополнительные формы. Важно: Compose обрабатывает строку после $ только если она образует валидное имя переменной — либо [_a-zA-Z][_a-zA-Z0-9]*, либо ${...}. В остальных случаях строка сохраняется как есть.
Интерполяция применяется до merge, на уровне каждого файла отдельно.
| Форма | Что делает |
|---|---|
${VAR} | Прямая подстановка значения |
${VAR:-default} | default, если VAR не задана или пустая |
${VAR-default} | default, только если VAR не задана вообще (пустая строка — это всё ещё значение) |
${VAR:?error} | Завершить с ошибкой, если VAR не задана или пустая |
${VAR?error} | Завершить с ошибкой, только если VAR совсем не задана |
${VAR:+replacement} | replacement, если VAR задана и не пустая, иначе пустая строка |
${VAR+replacement} | replacement, если VAR задана (даже пустым значением) |
Подстановки можно вкладывать друг в друга: ${VARIABLE:-${FOO:-default}}.
Если переменная не резолвится и default не задан — Compose выводит предупреждение и подставляет пустую строку. Расширенные shell-фичи типа ${VARIABLE/foo/bar} не поддерживаются.
Чтобы получить буквальный знак доллара и не дать Compose его интерпретировать — $$:
Отдельный нюанс: интерполяция применяется только к значениям, не к ключам. Если ключ — произвольная пользовательская строка (например, в labels или environment), для интерполяции ключа нужен альтернативный синтаксис со знаком =:
merge — слияние нескольких compose-файлов
Кому и зачем: если вы используете несколько compose-файлов (
-f base.yml -f override.yml) — важно понимать, как они сливаются. Если у вас один файл — раздел можно пропустить.
Когда модель приложения собирается из нескольких файлов (например, compose.yaml + compose.override.yaml), Compose сливает их по понятным правилам, плюс пара спецтегов для ручного управления.
| Тип данных | Правило |
|---|---|
Мапа (mapping) | Недостающие ключи добавляются, общие — рекурсивно сливаются |
Список (sequence) | Значения из второго файла добавляются к значениям из первого |
→ результат: key1: value1, key2: VALUE, key3: value3.
Но есть исключения из этих двух правил:
| Что | Правило |
|---|---|
command, entrypoint, healthcheck.test | Не складываются, а полностью перезаписываются последним файлом |
volumes, secrets, configs (уникальный ключ — target), ports (уникальный ключ — {ip, target, published, protocol}) | Хотя формально это списки, Compose считает их по уникальному ключу: новые записи добавляются, совпадающие по ключу — сливаются как мапы |
Пример с уникальным ключом для volumes (совпал target: /work — значит, это “тот же” элемент, второй файл выигрывает):
Два спецтега YAML для ручного управления слиянием:
!reset — стереть значение, заданное предыдущим файлом (тип сохраняется как default/null, конкретное значение после тега не важно, но для читаемости лучше явно писать null или []):
!override — полностью заменить значение, игнорируя обычные правила слияния (актуально для ports/volumes/secrets/configs, которые иначе слились бы по уникальному ключу, а не заменились целиком):
Заключение
Если выбросить из головы все таблицы, смысл главы простой: 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 и атрибуты подключения на уровне сервиса я здесь уже показал.
