Compose-файл: services и основные поля
В прошлой статье была главная идея: Compose запускает не один контейнер, а стек из нескольких сервисов. Теперь разберём сам YAML-файл.
Compose-файл состоит из секций верхнего уровня. Чаще всего ты увидишь такие:
| Секция | Что описывает |
|---|---|
services |
какие контейнерные роли есть в приложении: web, app, db, worker |
networks |
какие сети есть у проекта и какие сервисы через них видят друг друга |
В этой статье главное — секция services и базовые поля сервиса. Сети затронем на уровне формы записи, а хранение данных через volumes разберём позже на практическом примере с PostgreSQL.
Service — это роль, container — это экземпляр
Сервис в Compose — это описание роли.
Например:
services:
web:
image: nginx:alpine
ports:
- "8080:80"
web — не имя одного конкретного контейнера в вечном смысле. Это роль: “контейнер nginx, который публикует HTTP наружу”.
Когда ты запускаешь:
docker compose up -d
Compose создаёт конкретный контейнер для сервиса web. Если потом сервис пересоздать, контейнер может получить новый ID, но роль web в compose-файле остаётся той же.
Это важно для логов и команд:
docker compose ps
docker compose logs web
docker compose exec web sh
Ты работаешь с сервисом web, а Compose сам находит нужный контейнер проекта.
Минимальный сервис
Минимально сервису нужен источник image:
services:
web:
image: nginx:alpine
image означает: “возьми готовый образ из локального Docker cache или registry”.
Если image нужно собрать из Dockerfile, используют build:
services:
app:
build: .
Такой вариант означает: “собери image из Dockerfile в текущей директории и запусти сервис из него”.
Более явная форма:
services:
app:
build:
context: ./backend
dockerfile: Dockerfile.prod
context — директория, которую Docker отправит в сборку. dockerfile — какой Dockerfile использовать внутри этого context.
Когда выбирать:
| Ситуация | Поле |
|---|---|
| запускаешь готовый nginx, postgres, redis | image |
| запускаешь своё приложение из исходников | build |
| в CI уже собрали image и положили в registry | image: registry.example.com/team/app:1.2.3 |
Поля сервиса, которые чаще всего нужны
Пример сервиса с типичными полями:
services:
web:
image: nginx:alpine
ports:
- "8080:80"
environment:
APP_ENV: dev
depends_on:
- app
restart: unless-stopped
Что здесь происходит:
| Поле | Простое объяснение |
|---|---|
image |
какой image запускать |
ports |
какие порты открыть с хоста внутрь контейнера |
environment |
какие переменные окружения передать процессу внутри контейнера |
depends_on |
какие сервисы стартовать раньше |
restart |
когда Docker должен перезапускать контейнер |
Важно: эти поля описывают разные стороны сервиса. ports — сеть снаружи, environment — настройки приложения, depends_on — порядок старта.
Ports: публиковать только входные точки
ports:
- "8080:80"
Левая часть — порт хоста. Правая часть — порт внутри контейнера.
localhost:8080 на хосте -> порт 80 внутри контейнера web
Если сервис нужен только соседним контейнерам, порт наружу публиковать не нужно. Например, база данных обычно не должна быть доступна с хоста:
services:
app:
image: myapp
environment:
DATABASE_HOST: db
db:
image: postgres:15-alpine
app сможет обратиться к db:5432 внутри сети Compose, даже если у db нет секции ports.
Практическое правило:
- публикуй наружу
frontend,nginx, API gateway или debug UI; - не публикуй наружу базы, очереди и внутренние worker-сервисы без причины.
Так меньше конфликтов портов и меньше случайно открытых внутренних сервисов.
Environment: настройки внутри контейнера
environment:
APP_ENV: dev
DATABASE_URL: postgres://app:secret@db:5432/app
Эти значения попадают в окружение процесса внутри контейнера. Приложение может прочитать их как обычные environment variables.
Пример: backend берёт DATABASE_URL, чтобы понять, к какой базе подключаться. В Compose для соседнего сервиса используется имя сервиса db, а не localhost.
postgres://app:secret@db:5432/app
^^
имя сервиса db
Подробно .env, env_file и подстановку переменных разберём в отдельной статье про конфигурацию. Здесь важно только не смешивать смысл: environment — это то, что попадёт внутрь контейнера.
Networks под сервисом и на верхнем уровне
По умолчанию Compose создаёт одну сеть проекта и подключает к ней все сервисы. Для простых стеков этого достаточно.
Но иногда нужно разделить доступ:
services:
web:
image: nginx:alpine
networks:
- frontend
app:
image: myapp
networks:
- frontend
- backend
db:
image: postgres:15-alpine
networks:
- backend
networks:
frontend:
backend:
Здесь:
webвидитapp, потому что оба вfrontend;appвидитdb, потому что оба вbackend;webне видитdb, потому что у них нет общей сети.
Это простая изоляция внутри одного Docker host. Она не заменяет полноценную security-модель, но помогает не делать базу видимой всем сервисам проекта.
depends_on — порядок старта, не готовность
services:
app:
depends_on:
- db
Эта запись означает: сначала запусти контейнер db, потом контейнер app.
Но это не означает, что PostgreSQL уже готов принимать подключения. Контейнер базы может быть running, а сама база ещё инициализирует каталог данных.
Поэтому приложение всё равно должно уметь повторять подключение к базе. Позже в курсе будет отдельная статья про healthcheck и depends_on.condition, где разберём готовность сервиса подробнее.
Restart policy
restart: "no"
restart: on-failure
restart: unless-stopped
restart: always
Для лабораторных часто можно оставить default. Для долгоживущих локальных сервисов обычно используют unless-stopped.
Но restart policy не исправляет ошибку приложения. Если приложение падает из-за неправильного пароля к базе, Docker может перезапускать контейнер снова и снова, но причина останется той же.
Проверка:
docker compose ps
docker compose logs app --tail=50
Если видишь постоянные restart'ы, сначала читай logs, а не увеличивай restart policy.
Проверка итоговой конфигурации
Перед запуском полезно посмотреть, что Compose реально понял из YAML:
docker compose config
Эта команда:
- проверяет синтаксис;
- подставляет переменные;
- объединяет override-файлы;
- показывает итоговую конфигурацию.
Это особенно полезно, когда в файле есть .env, несколько compose-файлов или переменные вроде ${APP_PORT:-8080}.
Как читать compose-файл
Читай файл не сверху вниз как роман, а по вопросам:
- Какие сервисы есть в
services? - Какой сервис является входной точкой снаружи?
- Какие ports опубликованы на хост?
- Какие сервисы общаются только внутри сети Compose?
- Есть ли данные, которые должны жить дольше контейнера?
- Какие переменные окружения переданы приложению?
- Есть ли
depends_on, и не перепутана ли готовность с порядком старта?
Так compose-файл становится картой системы: видно, где вход, где внутренние зависимости, где данные и какие настройки управляют поведением сервисов.
