7 приемов Docker Compose, которые наведут порядок в вашем стеке

Почти любой compose.yaml со временем превращается в длинную простыню из скопированных блоков, а сервисы стартуют в случайном порядке. Типичная картина: небольшой стек из Python-приложения, воркера, PostgreSQL и Nginx. Настройки повторяются в каждом сервисе, а после перезагрузки сервера приложение поднимается раньше базы и падает с ошибкой подключения, пока все не устаканится.
Обычно это лечат через sleep в скриптах запуска или через несколько почти одинаковых Compose-файлов, и оба способа быстро становятся сложнее самого стека. Между тем в Docker Compose v2 уже есть встроенные инструменты для этих задач: якоря YAML, healthcheck, профили, include, watch и другие. Многие из них появились или заметно расширились в версиях с 2.17 по 2.24, поэтому их легко пропустить.
Ниже семь приемов на примере одного стека. Все они входят в спецификацию Compose и не требуют сторонних плагинов.
Проверьте версию Compose
Убедитесь, что вы используете плагин Docker Compose v2 (команда docker compose через пробел), а не устаревший docker-compose. Для всех примеров нужна версия 2.24 или новее, а для приема с !reset и !override версия 2.24.4 или новее.
docker compose version
# Docker Compose version v2.39.21. Общие настройки через якоря YAML и поля x-
Самая частая проблема в разросшемся файле связана с повторами. Если web и worker используют одинаковую политику перезапуска и лимиты логов, любое изменение приходится вносить дважды. Вынесите общие настройки в поле расширения и подключайте их через якорь.
x-common: &common
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
web:
<<: *common
build: .
ports:
- "8080:8000"
worker:
<<: *common
build: .
command: python worker.pyПоля, которые начинаются с x-, Compose игнорирует при создании сервисов, поэтому туда удобно складывать переиспользуемые блоки. Запись &common задает якорю имя, а <<: *common вливает его содержимое в сервис. Все, что указано прямо в сервисе, перекрывает общие значения. Заодно max-size и max-file ограничивают логи тремя файлами по 10 МБ и не дают контейнеру забить диск.
Проверить итоговую конфигурацию можно командой docker compose config: в выводе у обоих сервисов будут одинаковые restart и logging, хотя описаны они один раз.
2. Правильный порядок запуска через service_healthy
Обычный depends_on гарантирует только то, что контейнер базы запустится раньше приложения. Он не ждет, пока PostgreSQL начнет принимать подключения, поэтому приложение все равно может упасть с connection refused. Добавьте базе healthcheck и сделайте web зависимым от ее здорового состояния.
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
start_period: 10s
web:
<<: *common
build: .
ports:
- "8080:8000"
depends_on:
db:
condition: service_healthy
restart: trueКоманда pg_isready сообщает об успехе, когда база готова к подключениям. Проверка идет каждые 5 секунд, после 10 неудач подряд контейнер считается нездоровым, а первые 10 секунд на инициализацию не учитываются. condition: service_healthy заставляет Compose запускать web только после успешной проверки, а restart: true перезапускает web, когда Compose явно перезапускает db. Пароль подставляется из файла .env в каталоге проекта.
docker compose up -d --waitФлаг –wait возвращает управление только тогда, когда сервисы запущены и здоровы. Если проверка базы так и не пройдет, Compose сообщит об ошибке зависимости, и вместо бесконечного цикла падений приложения вы получите понятную причину.
3. Необязательные сервисы через профили
Некоторые сервисы нужны только иногда. Например, Adminer для отладки базы не обязательно держать запущенным постоянно. Привяжите его к профилю:
adminer:
image: adminer
ports:
- "8081:8080"
profiles: ["debug"]Сервисы с неактивным профилем Compose пропускает, а сервисы без профиля запускаются всегда. Обычный docker compose up -d оставит Adminer выключенным, а когда он понадобится, достаточно включить профиль.
docker compose --profile debug up -d
# или через переменную окружения в .env
COMPOSE_PROFILES=debug4. Разделение большого файла через include
Когда compose.yaml разрастается, в нем становится трудно ориентироваться. Элемент include позволяет вынести связанные сервисы в отдельные файлы, оставив их частью одного проекта. Например, перенесите базу в db/compose.yaml.
# db/compose.yaml
services:
db:
image: postgres:17
env_file: db.env
volumes:
- ./data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10# compose.yaml
include:
- db/compose.yamlПути внутри подключенного файла считаются относительно него самого, поэтому db.env и ./data указывают на файлы внутри каталога db. Зависимость depends_on в основном файле продолжает работать, потому что Compose собирает все в единую модель проекта. Если случайно оставить в основном файле второй сервис db, Compose сообщит о конфликте и не станет молча выбирать одну из версий.
5. Синхронизация кода через develop.watch
В разработке утомляет пересобирать образ после каждой правки. Compose Watch умеет синхронизировать локальные изменения прямо в работающий контейнер. Добавьте сервису web секцию develop:
develop:
watch:
- action: sync
path: ./app
target: /app
ignore:
- __pycache__/
- action: rebuild
path: requirements.txtДействие sync копирует изменения из ./app в /app без пересборки образа, а кэш байткода Python исключается. Действие rebuild пересобирает образ при изменении requirements.txt, чтобы новые зависимости действительно установились. Запускается режим командой docker compose watch. Эта настройка рассчитана на разработку, на продакшен-серверах она не нужна.
6. Управление наследованием через !reset и !override
Для продакшена часто накладывают второй Compose-файл поверх базового. По умолчанию Compose объединяет значения, поэтому новый порт в override-файле добавится к старому, а не заменит его. Отсюда классическая загадка, почему удаленный порт все еще торчит наружу. Теги !override и !reset позволяют управлять этим явно.
# compose.prod.yaml
services:
web:
ports: !override
- "127.0.0.1:8080:8000"
adminer:
ports: !reset []!override полностью заменяет значение из базового файла: web будет слушать только 127.0.0.1:8080. !reset [] очищает унаследованное значение: у Adminer в продакшене не останется опубликованных портов. Проверить результат можно так:
docker compose -f compose.yaml -f compose.prod.yaml config7. Встроенные конфиги через configs.content
Раз web теперь слушает только localhost, внешние запросы должен принимать обратный прокси. Конфигурацию Nginx не обязательно хранить отдельным файлом, ее можно встроить прямо в compose.yaml через верхнеуровневый элемент configs.
configs:
nginx_conf:
content: |
server {
listen 80;
location / {
proxy_pass http://web:8000;
proxy_set_header Host $$host;
}
}
services:
proxy:
image: nginx:stable
ports:
- "80:80"
configs:
- source: nginx_conf
target: /etc/nginx/conf.d/default.confОбратите внимание на $$host. Compose подставляет переменные и внутри content, поэтому обычный $host он воспринял бы как свою переменную и, скорее всего, заменил бы пустой строкой. Двойной знак доллара экранирует подстановку, и до Nginx доходит именно $host.
Как пользоваться этим безопасно
Когда итоговая конфигурация собирается из нескольких файлов, перед деплоем запускайте docker compose config. Команда проверит синтаксис и покажет модель, которую Compose будет использовать, так что неожиданные слияния и переопределения видны сразу. Учтите, что в ее выводе переменные окружения раскрываются открытым текстом, поэтому не выкладывайте его целиком в публичные обсуждения и тикеты.
Файлы с паролями закройте от посторонних и не коммитьте в репозиторий, а в .dockerignore исключите служебные каталоги, чтобы контекст сборки был меньше и образы собирались быстрее.
chmod 600 .env db/db.env
# .gitignore
.env
db/db.env
# .dockerignore
.git
db/dataИтог
Якоря и поля x- убирают повторы, healthcheck с service_healthy решает проблему порядка запуска, профили прячут вспомогательные сервисы, include делит большой проект на части, watch ускоряет разработку, !reset и !override дают точный контроль над слоями конфигурации, а configs.content избавляет от лишних мелких файлов. Все это уже есть в Compose v2, и ни один из приемов не требует дополнительных инструментов.
Источник: TecMint





