
Docker Compose: как описать приложение из нескольких сервисов
Собираем compose.yaml для приложения и Redis, проверяем итоговую конфигурацию, запускаем стек, читаем логи и безопасно пересоздаём сервисы.
Содержание
Docker Compose читает YAML-файл, в котором перечислены процессы приложения, их образы, сети, порты и постоянные каталоги. Каждый такой процесс называется сервисом. Вместо нескольких длинных команд docker run получается одна конфигурация: её можно хранить рядом с кодом, проверять до запуска и повторять на другом подготовленном сервере.
Что будет описано в Compose-файле
Опишите каждый процесс отдельным service в compose.yaml, подключайте внутренние зависимости по имени сервиса, публикуйте только необходимый входной порт и выносите постоянные данные в named volume. Перед запуском выполните docker compose config, затем используйте up -d, ps и logs. Не передавайте реальные секреты в репозиторий и не запускайте down -v, если данные должны сохраниться.
Подготовьте минимальный пример
Соберём небольшое Python-приложение со счётчиком в Redis. Структура каталога:
compose-demo/
├── app.py
├── requirements.txt
├── Dockerfile
├── compose.yaml
└── .env.example
Файл app.py создаёт два HTTP-маршрута: главная страница увеличивает счётчик в Redis, а /health проверяет соединение с ним. Благодаря этому пример показывает реальное взаимодействие двух сервисов, а не два контейнера, которые просто запущены рядом:
import os
from flask import Flask
from redis import Redis
app = Flask(__name__)
cache = Redis(host=os.environ.get("REDIS_HOST", "cache"), decode_responses=True)
@app.get("/")
def index():
visits = cache.incr("visits")
return {"visits": visits}
@app.get("/health")
def health():
cache.ping()
return {"status": "ok"}
app.run(host="0.0.0.0", port=5000)
Имя cache берётся из переменной REDIS_HOST; позднее Compose сделает его сетевым именем Redis. Адрес 0.0.0.0 разрешает принимать запросы через сеть контейнера. Встроенный сервер Flask годится для этого упражнения, но рабочее приложение запускают через выбранный WSGI-сервер.
requirements.txt для воспроизводимого примера:
Flask==3.1.3
redis==8.1.0
Эти версии актуальны на дату обновления материала. В рабочем проекте проверяйте новые релизы, совместимость и уязвимости, затем обновляйте зависимости отдельным изменением.
Dockerfile помещает приложение и зависимости в отдельный образ. Создайте его в том же каталоге; пользователь с номером 10001 не получает полномочий root внутри контейнера:
# syntax=docker/dockerfile:1
FROM python:3.13-alpine
WORKDIR /app
COPY requirements.txt ./
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY app.py ./
USER 10001
CMD ["python", "app.py"]
Для production используйте WSGI-сервер, подходящий приложению, а не встроенный сервер Flask. Здесь он оставлен только для короткой проверки взаимодействия Compose.
Опишите сервисы в compose.yaml
Создайте рядом файл compose.yaml. Сервис web будет собран из Dockerfile и доступен только через loopback-порт хоста, а сервис cache получит отдельный volume для данных:
name: compose-demo
services:
web:
build:
context: .
environment:
REDIS_HOST: cache
ports:
- "127.0.0.1:${APP_PORT:-8000}:5000"
depends_on:
cache:
condition: service_healthy
restart: unless-stopped
cache:
image: redis:8-alpine
command: ["redis-server", "--appendonly", "yes"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
volumes:
- cache-data:/data
restart: unless-stopped
volumes:
cache-data:
Compose создаёт для проекта сеть по умолчанию. web обращается к Redis по имени cache и внутреннему порту сервиса; публиковать Redis на host не нужно. HTTP-порт приложения привязан к loopback, чтобы его мог забрать reverse proxy на том же сервере.
Тег образа в примере удобен для знакомства, но может перемещаться. Для контролируемой публикации выберите протестированную версию или digest и обновляйте её осознанно.
Проверьте подстановку до запуска
Создайте локальный .env из примера. Этот файл задаёт порт на хосте и находится в каталоге compose-demo:
APP_PORT=8000
Добавьте .env в .gitignore, а в репозитории оставьте только .env.example без паролей. Затем проверьте итоговую модель:
docker compose config
docker compose config --quiet
Первая команда показывает подставленные значения и развёрнутые сокращения. Не публикуйте её полный вывод в тикете или логе, если конфигурация содержит секреты. --quiet подходит для проверки синтаксиса без печати модели.
Запустите и проверьте стек
docker compose up -d --build
docker compose ps
docker compose logs --tail 100 web cache
curl --fail --silent --show-error http://127.0.0.1:8000/health
curl --fail --silent --show-error http://127.0.0.1:8000/
В ps оба сервиса должны работать, а cache после стартового периода — иметь состояние healthy. Повторный запрос к / увеличивает счётчик.
Проверить связь из контекста приложения можно командой:
docker compose exec web python -c \
'from redis import Redis; print(Redis(host="cache").ping())'
Если web запущен, но запрос падает, изучите docker compose logs web, затем health cache и итоговую конфигурацию. Не добавляйте произвольный sleep 30: время запуска меняется, а healthcheck выражает фактическую готовность.
Что делает depends_on
Короткая запись depends_on: [cache] задаёт порядок запуска, но Compose не ждёт, когда процесс станет готов принимать запросы. Условие service_healthy связывает запуск зависимого сервиса с успешным healthcheck.
Даже при этом приложение должно уметь повторять временно неудавшееся соединение. Healthcheck помогает оркестрации, но не заменяет устойчивость клиента при последующем перезапуске Redis или кратком сетевом сбое.
Изменяйте сервисы предсказуемо
После правки кода или Compose-файла сначала проверьте синтаксис, затем примените изменение и посмотрите состояние только этого проекта:
docker compose config --quiet
docker compose up -d --build
docker compose ps
docker compose logs --tail 100 web
up пересоздаёт сервисы, конфигурация или образ которых изменились, и сохраняет подключённые named volumes. Для просмотра конкретного образа используйте:
docker compose images
docker compose config --images
Перед production-обновлением сохраните предыдущие ссылки на образы и проверьте совместимость формата данных. Возврат старого образа не откатывает изменения базы автоматически.
Остановка, удаление и данные
Чтобы временно остановить контейнеры без удаления, выполните в каталоге проекта две команды. start вернёт те же контейнеры с прежними настройками:
docker compose stop
docker compose start
Удалить контейнеры и сеть проекта, сохранив named volume:
docker compose down
Команда ниже приведена как предупреждение: параметр -v удаляет вместе с контейнерами именованный volume проекта. Не выполняйте её, если данные нужны или восстановление не проверено:
docker compose down -v
Не применяйте её к рабочему стеку как обычную очистку. Сначала создайте восстановимую копию и посмотрите точные имена через docker volume ls.
Признаки понятного Compose-проекта
- один service описывает один основной процесс;
- внутренние сервисы не публикуются без необходимости;
- приложения используют DNS-имена сервисов, а не IP;
- постоянные данные вынесены в явно названные volumes;
- секреты отсутствуют в Compose-файле и Git;
docker compose config --quietпроходит до применения;- healthcheck проверяет работу, а не только наличие процесса;
- задокументированы backup, обновление и rollback данных.
Перед Compose полезно разобраться с постоянными данными и сетями контейнеров. Структуру собственного образа разбирает материал о Dockerfile, а чувствительные значения — руководство по секретам.
Источники
Самопроверка
Проверьте, что материал усвоен
Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.
Разбираем коротко
Частые вопросы
Нужно ли указывать version в современном compose.yaml?
Для актуальной Compose Specification поле version не требуется. Используйте поддерживаемый Docker Compose V2 и проверяйте файл командой docker compose config.
Удаляет ли docker compose down данные named volumes?
Обычный down удаляет контейнеры и сети проекта, но не named volumes. Параметр -v удаляет и объявленные volumes, поэтому для рабочего стека его нельзя применять без осознанного решения.
Гарантирует ли depends_on готовность базы или cache принимать запросы?
Короткая форма гарантирует порядок запуска, но не готовность приложения. Для готовности нужен healthcheck с условием service_healthy и корректное повторение соединения в клиенте.


