Docker Compose profiles: несколько окружений
Рано или поздно в проекте на Docker Compose заводится зоопарк файлов: docker-compose.yml, docker-compose.override.yml, docker-compose.dev.yml, docker-compose.staging.yml, docker-compose.prod.yml — и уже никто не помнит, какой файл с каким мержится и в каком порядке. Фича profiles, стабильная в Compose Specification с 2021 года, решает эту задачу иначе: один файл, сервисы помечены тегами-профилями, а нужный набор запускается флагом при старте. Разберём, как это работает на практике.
Содержание
Что такое profiles и зачем они нужны
profiles — это список меток у сервиса в docker-compose.yml. Сервис с непустым списком profiles запускается только тогда, когда хотя бы один из его профилей явно включён при вызове docker compose. Сервисы без ключа profiles (или с пустым списком) запускаются всегда, независимо от того, какие профили включены — это ваш «базовый» набор.
Смысл в том, чтобы описать *все возможные* сервисы проекта в одном файле — веб-приложение, базу, кеш, воркеры, инструменты отладки, тестовые заглушки — а решение о том, что реально поднимать, принимать в момент запуска, а не заранее в отдельном YAML.
Плюсы такого подхода:
- один источник правды по конфигурации сервисов — не нужно синхронизировать образы, версии, сети между несколькими файлами;
- явное перечисление окружений прямо в файле — открыл
docker-compose.ymlи видишь все профили, которые есть в проекте; - легко добавить временный набор сервисов (например,
debugилиseed) без риска зацепить продакшен-конфигурацию.
Минус тоже есть: если сервисов много и профили пересекаются нетривиально, файл разрастается и требует дисциплины в именовании. Об этом — ниже, в разделе про ограничения.
Синтаксис: как назначить сервис профилю
Профили назначаются ключом profiles внутри описания сервиса. У сервиса может быть несколько профилей — тогда он включается, если активен хотя бы один из них:
services:
web:
build: .
ports:
- "8000:8000"
depends_on:
- db
db:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
adminer:
image: adminer
profiles: ["dev"]
ports:
- "8080:8080"
debug-tools:
image: busybox
profiles: ["dev", "debug"]
command: sleep infinity
seed:
build: ./seed
profiles: ["staging", "dev"]
depends_on:
- db
volumes:
db_data:
Здесь web и db — база, они не зависят от профиля и стартуют всегда. adminer (веб-интерфейс для БД) включается только с профилем dev. debug-tools включается и в dev, и отдельно в debug. seed (наполнение тестовыми данными) — в dev и staging, но не в проде.
Важный нюанс: depends_on не активирует профиль сам по себе. Если web зависит от сервиса с профилем, а нужный профиль не включён, Compose до версии с фиксом мог падать с ошибкой о недостающей зависимости — сейчас это поведение задокументировано так: зависимость с профилем должна либо сама попадать под активный профиль, либо не быть в depends_on сервиса, который стартует без профилей. Проще говоря: не завязывайте всегда-стартующие сервисы на сервисы с профилями через depends_on — используйте profiles последовательно по всей цепочке зависимостей.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSПример: dev, staging и production в одном файле
Практическая раскладка для типового проекта — веб-приложение, БД, Redis, воркер очередей и набор инструментов:
services:
web:
build: .
ports:
- "8000:8000"
db:
image: postgres:16
profiles: [] # база нужна всегда, ключ можно не указывать
redis:
image: redis:7
worker:
build: .
command: celery -A app worker
profiles: ["staging", "prod"]
adminer:
image: adminer
profiles: ["dev"]
mailhog:
image: mailhog/mailhog
profiles: ["dev"]
nginx:
image: nginx:1.27
volumes:
- ./nginx/prod.conf:/etc/nginx/nginx.conf:ro
profiles: ["prod"]
Логика: web, db, redis — костяк, нужны везде. worker подключаем только там, где реально нужна фоновая обработка задач (staging и prod, но не в dev, где разработчик гоняет задачи синхронно). adminer и mailhog (перехват почты для локальной разработки) — только в dev. nginx как reverse proxy перед приложением — только в prod, потому что на dev-машине разработчика он не нужен.
Такая раскладка не требует пересобирать образы под окружение — меняется только набор *запущенных* сервисов, а не конфигурация внутри контейнеров (переменные окружения для разных стадий по-прежнему стоит держать в разных .env-файлах, это отдельная задача).
Опциональные сервисы для отладки и разработки
Отдельный частый сценарий — не разные окружения, а опциональные инструменты внутри одного окружения. Например, вы хотите держать под рукой pgAdmin, но не гонять его постоянно на сервере разработки, потому что он ест память:
services:
pgadmin:
image: dpage/pgadmin4
profiles: ["tools"]
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
jaeger:
image: jaegertracing/all-in-one
profiles: ["tools", "debug"]
ports:
- "16686:16686"
Так вы держите основной набор сервисов лёгким по умолчанию (docker compose up их не тронет), а тяжёлые или узкоспециализированные инструменты поднимаете отдельной командой, когда они реально нужны — и гасите вместе с остальными через docker compose down, не забывая ни про один контейнер.
Запуск конкретного профиля
Профиль включается флагом --profile при любой команде docker compose:
# только dev-профиль (плюс сервисы без профиля)
docker compose --profile dev up -d
# несколько профилей сразу
docker compose --profile dev --profile debug up -d
# через переменную окружения — удобно для CI и .env
COMPOSE_PROFILES=staging docker compose up -d
# посмотреть, что реально будет запущено
docker compose --profile staging config --services
Флаг --profile можно повторять или задавать через COMPOSE_PROFILES (список через запятую) — это удобно прописать в .env-файле проекта, чтобы не набирать флаг каждый раз вручную:
# .env
COMPOSE_PROFILES=dev
Тогда обычный docker compose up -d в каталоге проекта сам подхватит нужный набор сервисов. Для CI/CD стоит явно передавать профиль в команде деплоя, а не полагаться на .env на сервере — так меньше шансов случайно поднять staging-набор на проде из-за забытого файла.
Чтобы запустить вообще всё, что описано в файле, есть флаг-джокер:
docker compose --profile "*" up -d
Полезно для локальной проверки, что весь файл валиден и ни один сервис не сломан — но на боевом сервере так делать не стоит, там должен запускаться конкретный явный набор.
Profiles против отдельных compose-файлов с override
До появления profiles стандартным способом было разносить окружения по файлам и мержить их флагом -f:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
| Критерий | Несколько файлов + override | profiles в одном файле |
|---|---|---|
| Источник правды | Разбросан по файлам, легко разойтись | Один файл, видно всё сразу |
| Порядок применения | Важен порядок -f, легко ошибиться | Порядка нет, включаете набор меток |
| Изменение параметров сервиса между окружениями (env, реплики) | Удобно — override переопределяет любое поле | Неудобно — profiles включают/выключают сервис целиком, а не меняют его параметры |
| Добавление нового окружения | Новый файл + не забыть про все остальные | Новая метка в существующих сервисах |
| Git-история | Diff размазан по файлам | Diff локализован в одном файле |
Ключевое отличие: profiles решают вопрос «какие сервисы включены», а override-файлы решают вопрос «какие у сервиса параметры». Это не взаимоисключающие механизмы — их часто комбинируют: базовая структура и профили в основном файле, а точечные отличия по переменным окружения или лимитам ресурсов между dev и prod — через docker-compose.override.yml или -f. Если вам нужно не просто включать/выключать сервисы, а менять их конфигурацию по окружениям (реплики, лимиты CPU, домены), почитайте про настройку docker compose для продакшена — там разобран как раз этот сценарий.
Если у вас в основном меняется набор сервисов (включить воркер, отладочные тулы, тестовые данные) — profiles проще и меньше кода. Если меняются в основном параметры одних и тех же сервисов — override-файлы гибче. Для больших проектов рабочая комбинация: profiles для «включить/выключить», плюс .env-файлы под каждое окружение для значений переменных.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSЧастые вопросы
Можно ли комбинировать profiles с несколькими compose-файлами?
Да, это не конкурирующие механизмы. Можно объявить profiles в базовом файле и одновременно передавать -f docker-compose.override.yml для переопределения отдельных полей — они применяются независимо друг от друга.
Что будет, если запустить docker compose up без флага --profile?
Стартуют только сервисы без ключа profiles (или с пустым списком). Все сервисы, у которых указан хотя бы один профиль, будут пропущены — это поведение по умолчанию и оно безопасно: случайно не поднимете лишнее на проде.
Как остановить сервисы конкретного профиля?
docker compose --profile dev down остановит и удалит контейнеры, относящиеся к активному набору (включая сервисы без профиля). Если нужно погасить именно один сервис — проще docker compose stop имя_сервиса.
Профили можно комбинировать с docker compose run?
Да, docker compose run --profile debug имя_сервиса учитывает зависимости внутри указанного профиля так же, как up.
Работает ли --profile в старых версиях Docker Compose?
Фича доступна начиная с Compose v2 (и Compose file format версии 3.9+ для v1-совместимого синтаксиса). Если у вас legacy docker-compose v1, сначала обновите Compose — заодно проверьте, что версия Docker Engine на сервере это поддерживает.
Нужно ли перечислять профиль явно у зависимых сервисов, если зависимость уже под профилем?
Да. Compose не подтягивает профиль автоматически через depends_on — если сервис A зависит от B, а B под профилем dev, то и A должен быть под тем же профилем (или запускаться вместе с ним осознанно), иначе при активации dev вы получите ошибку about a missing dependency у не входящих в профиль сервисов.