В прошлой части я разобрал хранилища — тома, bind mount, tmpfs. В третьей части (в этой) планировалось добраться до сети. До неё дойдём, но прежде чем разбирать, как сервисы общаются друг с другом, стоит на секунду остановиться и разобрать сам файл, в котором мы всё это описываем. Потому что compose.yaml — это спецификация со своими top-level разделами, правилами слияния, подстановкой переменных и кучей мелких нюансов, которые легко упустить, если читать документацию по верхам.Эта часть, по сути, большой справочник по структуре Compose-файла: держите статью под рукой и возвращайтесь к ней, когда понадобится конкретный атрибут. Читать далее

В прошлой части я разобрал хранилища — тома, bind mount, tmpfs. В третьей части (в этой) планировалось добраться до сети. До неё дойдём, но прежде чем разбирать, как сервисы общаются друг с другом, стоит на секунду остановиться и разобрать сам файл, в котором мы всё это описываем. Потому что compose.yaml — это спецификация со своими top-level разделами, правилами слияния, подстановкой переменных и кучей мелких нюансов, которые легко упустить, если читать документацию по верхам.
Эта часть, по сути, большой справочник по структуре Compose-файла: держите статью под рукой и возвращайтесь к ней, когда понадобится конкретный атрибут.
1. Из чего состоит compose-файлCompose-файл описывает модель приложения через набор top-level (верхнеуровневых) разделов. Обязателен в основном только один — services, остальные подключаются по необходимости.
Раздел | Что описывает | Обязателен |
|---|---|---|
| Сервисы приложения — абстракция над контейнерами | Да |
| Именованные сети для сервисов | Нет, иначе используется неявная сеть |
| Именованные тома | Нет |
| Неконфиденциальные конфигурационные данные | Нет |
| Чувствительные данные | Нет |
| AI-модели, которые пуллятся и обслуживаются runner’ом | Нет |
| Подключение и слияние других compose-файлов | Нет |
| Произвольные данные для переиспользования, Compose их игнорирует | Нет |
| Только для обратной совместимости, ни на что не влияет | Нет, и больше не нужен |
| Имя проекта по умолчанию | Нет, иначе подставляется автоматически |
2. version и name
profilesвлияют только наservices. Все остальные top-level разделы всегда активны, независимо от того, какие профили включены.
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 up Compose видит 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 снести то, что он создал.
Атрибут | Что делает |
|---|---|
| Политика перезапуска: |
| Проверка “здоровья” контейнера, переопределяет |
| Порядок запуска/остановки сервисов |
| Список профилей, при которых сервис активен (см. раздел 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 в окружении именно этой команды. Сам контейнер при этом продолжает работать как обычно, никто его не перезапускает.
Атрибут | Что делает |
|---|---|
| Публикация портов хост:контейнер. Нельзя использовать с |
| Открыть порт только для других контейнеров в сети, без публикации на хост |
| К каким именованным сетям подключён сервис, плюс настройки на уровне подключения |
|
|
| Связь с контейнерами другого сервиса по имени/алиасу (не обязательны для общения внутри одной сети) |
| Связь с сервисами вне текущего 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.
Атрибут | Что делает |
|---|---|
| Монтирование томов/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.
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-сокету самого движка. Изнутри можно делать |
logging настраивает драйвер логирования для контейнеров сервиса:
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"
driver — имя драйвера логирования. Дефолт и доступные значения зависят от платформы. options — опции драйвера в виде key-value пар.
Атрибут | Что делает |
|---|---|
| Аннотации контейнера (массив или мапа) |
|
|
| Конфигурация развёртывания (раздел 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 вернёт ошибку.
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 пропускает его с предупреждением: пушить туда, по сути, нечего.
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).
6. develop — режим разработки (watch)Важно: job-режимы (
replicated-job,global-job) рассчитаны на задачи, которые завершаются с кодом0. Завершённые задачи остаются, пока их явно не удалят.max-concurrentдля них настраивается только через CLI, в Compose-файле такого параметра нет.
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
7. networks — именованные сетиЕсли
includeначинается с*, обязательно берите паттерн в кавычки — иначе YAML примет звёздочку за alias-node.
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: без атрибутов) — это том с настройками движка по умолчанию.
Эти два раздела почти близнецы: оба монтируют данные файлами в контейнер, оба требуют явного разрешения на уровне сервиса. Разница — 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-модели в ComposeTop-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 вернёт ошибку, а не подключит сервис “по умолчанию”.
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 ООО «МТ ФИНАНС»