Profiles: опциональные сервисы в одном compose
В реальном проекте бывает: «есть core-сервисы (Postgres, app), а есть опциональные — Adminer для UI к БД, Kafka-UI для просмотра топиков, debug-контейнер с tcpdump». Их не нужно держать запущенными всегда, но хочется иметь под рукой. Profiles — механизм compose для именно этого. В этом уроке разбираем синтаксис, паттерны и подводные камни.
Базовый синтаксис
services:
postgres:
image: postgres:17
environment: { POSTGRES_PASSWORD: secret }
app:
image: myapp
depends_on: [postgres]
adminer:
image: adminer:4
profiles: ["ui"]
ports: ["8080:8080"]
# Без флага — Adminer не стартует.
docker compose up -d
# Up: postgres, app. Adminer пропущен.
# С флагом — стартует.
docker compose --profile ui up -d
# Up: postgres, app, adminer.
Сервисы без profiles: ключа стартуют всегда. Сервисы с профилями — только когда хотя бы один их профиль активирован через --profile.
Несколько профилей на сервисе
services:
jaeger:
image: jaegertracing/all-in-one
profiles: ["debug", "observability"]
jaeger запустится, если активирован debug ИЛИ observability:
docker compose --profile debug up
docker compose --profile observability up
docker compose --profile debug --profile observability up # оба, но jaeger стартует один раз
Активация нескольких профилей
docker compose --profile ui --profile debug up
Через переменную среды:
COMPOSE_PROFILES=ui,debug docker compose up
Все профили из списка активны, все их сервисы стартуют.
--profile "*" — активировать все профили сразу:
docker compose --profile "*" up
— стартует абсолютно всё, что есть в compose-файле.
Реальные кейсы
1. Optional UI к БД
Как Docker разрешает имена сервисовservices:
postgres:
image: postgres:17
environment: { POSTGRES_PASSWORD: secret }
adminer:
image: adminer:4
profiles: ["ui"]
depends_on: [postgres]
ports: ["127.0.0.1:8080:8080"]
environment:
ADMINER_DEFAULT_SERVER: postgres
В обычной работе UI не нужен — psql из терминала хватает. Когда нужен веб-интерфейс: docker compose --profile ui up -d.
2. Debug-toolbox
services:
tcpdump:
image: nicolaka/netshoot
profiles: ["debug"]
network_mode: "service:app" # делит netns с app
command: tcpdump -i any -w /captures/dump.pcap
volumes:
- ./captures:/captures
netshoot — образ с tcpdump, dig, curl, nslookup. Запускается только когда нужно дебажить трафик. network_mode: "service:app" подсаживается в namespace другого контейнера — видит его трафик.
docker compose --profile debug up tcpdump
# Снифит трафик app до Ctrl+C.
3. Тестовые сервисы
services:
app:
image: myapp
mock-s3:
image: minio/minio
profiles: ["test"]
command: server /data
test-runner:
image: myapp
profiles: ["test"]
depends_on: [mock-s3, app]
command: pytest
В обычной работе mock S3 и runner не запускаются. При запуске в CI или вручную для smoke-test:
docker compose --profile test up --abort-on-container-exit
--abort-on-container-exit останавливает весь стенд, как только хоть один контейнер вышел — полезно для test-run, чтобы по exit-code узнать результат.
Default profile
Сервисы без profiles: — это «default-профиль», они всегда запускаются. Это даёт чистую модель:
- Core: без profile, всегда поднимается. Это твой минимум для работы.
- Optional: с profile, поднимается по запросу.
Profiles vs override files
Profiles похожи на override-файлы (следующий урок) — оба позволяют менять стенд под ситуацию. Когда что использовать?
| Случай | Profile | Override-файл |
|---|---|---|
| Добавить optional-сервис (один и тот же сервис) | да | нет |
| Изменить параметры существующего сервиса | нет | да |
| Сделать вариант «без UI» / «с UI» | да | возможно |
| dev vs prod (разные образы, разные volumes) | возможно | да, идиоматично |
| dev vs CI (только дополнительные сервисы) | да | возможно |
Profile — добавление сервиса. Override — изменение сервиса. Они часто комбинируются.
Зависимости с профилями
Если app зависит от kafka, а kafka под profile streaming:
services:
app:
image: myapp
depends_on: [kafka] # kafka в профиле streaming
kafka:
image: confluentinc/cp-kafka
profiles: ["streaming"]
docker compose up
# Error: service "app" depends on "kafka" which is in profile streaming, not active
Compose не запустит app, потому что depends_on указывает на сервис, который сейчас не активирован.
Решение:
- Запустить с профилем:
--profile streaming up. - Объединить app и kafka в один профиль, либо вынести app тоже в
profiles: ["streaming"]. - Использовать профили на верхнем уровне продумано: depends_on на не-активный сервис — это ошибка дизайна.
Авто-активация: если ты делаешь docker compose up app — compose активирует все профили, в которых app участвует, плюс профили зависимостей. Но если app сам не в профиле — он не подтянет за собой профильные зависимости автоматически. Будь явным с —profile.
Реальный compose-файл с profiles
services:
postgres:
image: postgres:17
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-secret}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
redis:
image: redis:7-alpine
app:
build: ./app
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
environment:
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-secret}@postgres:5432/postgres
# Optional UI
adminer:
image: adminer:4
profiles: ["ui"]
ports: ["127.0.0.1:8080:8080"]
redis-commander:
image: rediscommander/redis-commander
profiles: ["ui"]
environment:
REDIS_HOSTS: local:redis:6379
ports: ["127.0.0.1:8081:8081"]
# Optional observability
prometheus:
image: prom/prometheus
profiles: ["observability"]
volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml:ro"]
ports: ["127.0.0.1:9090:9090"]
# Optional tests
test-runner:
profiles: ["test"]
build: ./app
depends_on: [app]
command: pytest tests/
volumes:
pgdata:
Сценарии запуска:
# Каждодневная работа.
docker compose up -d
# Поковыряться в БД через UI.
docker compose --profile ui up -d
# Включить мониторинг.
docker compose --profile observability up -d
# CI: запустить тесты.
docker compose --profile test up --abort-on-container-exit
# Всё разом.
docker compose --profile ui --profile observability up -d
Один compose.yml для всех режимов работы. Никаких разных файлов для dev/staging.
Попробуй сам
mkdir -p profiles-demo && cd profiles-demo
cat > compose.yml <<'YAML'
services:
core:
image: alpine:3.20
command: sh -c 'while true; do echo "core tick $(date)"; sleep 5; done'
ui:
image: alpine:3.20
profiles: ["ui"]
command: sh -c 'echo "UI service started"; sleep 1000'
debug:
image: alpine:3.20
profiles: ["debug"]
command: sh -c 'echo "debug service started"; sleep 1000'
YAML
# 1. Без флага.
docker compose up -d
docker compose ps
# Только core стартует.
# 2. С --profile ui.
docker compose --profile ui up -d
docker compose ps
# core + ui.
# 3. С двумя профилями.
docker compose --profile ui --profile debug up -d
docker compose ps
# core + ui + debug.
# 4. Через переменную окружения.
COMPOSE_PROFILES=ui,debug docker compose up -d
# Тот же эффект.
# 5. Все профили.
docker compose --profile "*" up -d
docker compose ps
# Cleanup.
docker compose down
cd ..
rm -rf profiles-demo
Хорошее правило: core-сервисы — без profiles. UI и админ-инструменты — profiles: ["ui"]. Тесты — profiles: ["test"]. Опциональные observability-сервисы — profiles: ["observability"]. Это даёт чистый, читаемый compose-файл и понятную модель «зачем мне нужен какой профиль».
В следующем уроке — override-файлы: автомерж compose.override.yml, явный merge через -f, паттерн base+dev+prod.