Классическая ситуация: три docker-compose файла, dev, staging и prod, и в каждом на девяносто процентов одинаковый набор настроек. Restart policy одинаковый, logging одинаковый, network одинаковая, различается по сути пара переменных окружения и лимит памяти. А потом кто-то в пятницу вечером правит restart policy в dev файле, забывает про два остальных, и в понедельник продакшн падает при первом же чихе контейнера, потому что там осталась настройка по умолчанию.
YAML anchors решают именно эту проблему. Идея простая: один раз описываешь блок настроек, ставишь на него якорь через амперсанд, а дальше ссылаешься на этот якорь звездочкой в любом другом месте документа.
Базовый синтаксис anchor и alias
x-common-settings: &common
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
services:
web:
<<: *common
image: nginx:alpine
Амперсанд перед common это объявление якоря, звездочка в <<: *common это ссылка на него, а сам двойной символ меньше это специальный merge key, который говорит YAML “подмешай сюда все поля из указанного якоря”. Сервис web в итоге получит и restart, и logging, как будто вы прописали их вручную.
Раздача по трем окружениям через переопределение
Вот тут начинается настоящая магия. Merge key не просто копирует блок, он позволяет локально переопределить отдельные поля поверх унаследованных.
x-common-settings: &common
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
services:
web:
<<: *common
image: myapp:latest
deploy:
resources:
limits:
memory: 512m
Здесь web берет restart и logging из якоря, но добавляет свои собственные поля image и deploy, которых в якоре не было. Если то же самое поле есть и в якоре, и в самом сервисе, побеждает то значение, которое написано в сервисе, то есть локальное переопределение имеет приоритет.
Практический пример: dev, staging, prod в одном файле
| Окружение | memory limit | replicas | log level |
|---|---|---|---|
| dev | без ограничения | 1 | debug |
| staging | 512m | 2 | info |
| prod | 1024m | 4 | warn |
Вместо трех файлов можно держать один compose файл с профилями, где базовые настройки идут через якорь, а специфика окружения переопределяется через merge key на уровне каждого сервиса. Комбинируя это с docker compose --profile prod up, получаем один файл, три поведения, ноль копипасты.
Когда это не работает
Merge key переопределяет поля только на первом уровне вложенности объекта, а не рекурсивно вглубь. Если внутри logging.options в сервисе вы хотите поменять только max-size, но оставить остальные опции logging из якоря, этого не произойдет автоматически: весь блок logging в сервисе просто заменит весь блок logging из якоря целиком.
Это не баг YAML, а особенность именно merge key: он работает на уровне ключей объекта верхнего уровня, а не сливает вложенные объекты рекурсивно.
Если нужно менять именно вложенный параметр, проще завести отдельный более специфичный якорь только для этого случая, чем полагаться на автоматическое глубокое слияние, которого в спецификации YAML попросту нет.
Итоговый чеклист
- Выносите в якорь только то, что реально повторяется в трех и более местах, ради одного повторения городить якорь не стоит
- Помните, что переопределение работает только на первом уровне вложенности, не рекурсивно
- Проверяйте итоговую конфигурацию через
docker compose config, эта команда покажет полностью развернутый YAML без якорей, ровно то, что реально увидит docker - Если конфиг стал сложным и трудночитаемым из-за большого числа якорей, это сигнал вынести общую часть в отдельный
docker-compose.base.ymlи подключать черезextends, а не наращивать вложенность якорей до бесконечности
Инструменты по теме
Если якоря в вашем файле уже разрослись так, что сложно на глаз понять итоговый результат после всех подстановок, разверните файл полностью через YAML Anchors Resolver, он покажет документ без единой ссылки, только с реальными значениями.
Если наоборот, нужно объединить базовый compose файл с override без якорей вообще, для этого есть отдельный Docker Compose Merge, который сливает два файла по тем же правилам, что использует сам docker compose.