Модуль 06 — Docker Compose

Всё приложение — один файл в репозитории и одна команда для запуска

Прогресс курса Модуль 6 из 9

Что вы освоите в этом модуле

01

Зачем Compose

Приложение из веб-сервиса и Redis в голом Docker — это сеть, том, два docker run с десятком параметров каждый, и всё в правильном порядке. Через месяц никто не вспомнит, с какими параметрами запускали. Compose описывает то же самое в YAML-файле, который лежит в репозитории рядом с кодом, и выполняет одной командой.

Compose входит в Docker как подкоманда docker compose (пакет docker-compose в Debian, docker-compose-plugin в репозитории Docker). Старая отдельная программа docker-compose через дефис — первая версия на Python, она больше не развивается; в старых инструкциях встречается, команды почти те же.

02

Приложение и compose.yaml

Веб-сервис на Flask считает посещения в Redis. Адрес Redis и приветствие берутся из переменных окружения:

import os
import signal
import sys

from flask import Flask
from redis import Redis

app = Flask(__name__)
redis = Redis(host=os.environ.get("REDIS_HOST", "localhost"), port=6379)


@app.get("/")
def index():
    hits = redis.incr("hits")
    return f"{os.environ.get('GREETING', 'Привет')}! Счётчик посещений: {hits}\n"


if __name__ == "__main__":
    signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
    app.run(host="0.0.0.0", port=8000)

Dockerfile — как в модуле 03. Файл Compose:

name: counter

services:
  web:
    build: .
    image: counter-web:1
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      REDIS_HOST: redis
      GREETING: ${GREETING:-Привет}
    depends_on:
      redis:
        condition: service_healthy
    restart: unless-stopped

  redis:
    image: redis:8-alpine
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

volumes:
  redis-data:
КлючЧто задаётАналог в docker run
nameИмя проекта — приставка к именам контейнеров, сети, томов. Без него — имя каталога—
servicesСервисы; имя сервиса — его имя в сети проекта--name, --network-alias
build / imageСобрать из каталога / взять образ; вместе — собрать и назвать такdocker build -t
portsПубликация портов, в кавычках-p
environmentПеременные; ${GREETING:-Привет} — из окружения или .env, иначе значение по умолчанию-e
commandЗаменить CMD образааргументы после образа
volumesТома и bind mount; верхний volumes: объявляет именованные тома-v
restartПолитика перезапуска--restart
healthcheckКоманда проверки готовности--health-cmd
depends_onПорядок запуска и условие—

Файл называется compose.yaml (старое имя docker-compose.yml тоже понимается). Отступы в YAML — только пробелами. Строку version: в начале, которую можно встретить в старых файлах, Compose v2 игнорирует — писать её не нужно.

Прежде чем запускать, полезно посмотреть, как Compose понял файл: docker compose config выводит итоговую конфигурацию с подставленными переменными и полной записью сокращений. Фрагмент для сервиса web:

$ docker compose config | sed -n "/^  web:/,/restart/p"
  web:
    build:
      context: /tmp/dkpage.WqNV5j/ex
      dockerfile: Dockerfile
    depends_on:
      redis:
        condition: service_healthy
        required: true
    environment:
      GREETING: Привет
      REDIS_HOST: redis
    image: counter-web:1
    networks:
      default: null
    ports:
      - mode: ingress
        host_ip: 127.0.0.1
        target: 8000
        published: "8000"
        protocol: tcp
    restart: unless-stopped
03

Запуск: up

docker compose up -d создаёт всё, чего нет, и запускает. --build — пересобрать образы сервисов с build (иначе соберёт только при первом запуске). Строки сборки из вывода убраны:

$ docker compose up -d --build 2>&1 | grep -v -E "^#|^ *$|=>|\[\+\] Building"
 Network counter_default  Creating
 Network counter_default  Created
 Volume "counter_redis-data"  Creating
 Volume "counter_redis-data"  Created
 Container counter-redis-1  Creating
 Container counter-redis-1  Created
 Container counter-web-1  Creating
 Container counter-web-1  Created
 Container counter-redis-1  Starting
 Container counter-redis-1  Started
 Container counter-redis-1  Waiting
 Container counter-redis-1  Healthy
 Container counter-web-1  Starting
 Container counter-web-1  Started

Порядок виден в выводе: сеть counter_default, том counter_redis-data, контейнеры. web не стартует, пока Redis не стал Healthy, — это условие depends_on. Состояние сервисов:

$ docker compose ps
NAME              IMAGE            COMMAND                  SERVICE   CREATED         STATUS                   PORTS
counter-redis-1   redis:8-alpine   "docker-entrypoint.s…"   redis     8 seconds ago   Up 7 seconds (healthy)   6379/tcp
counter-web-1     counter-web:1    "python app.py"          web       8 seconds ago   Up 2 seconds             127.0.0.1:8000->8000/tcp
$ curl -s http://127.0.0.1:8000/; curl -s http://127.0.0.1:8000/; curl -s http://127.0.0.1:8000/
Привет! Счётчик посещений: 1
Привет! Счётчик посещений: 2
Привет! Счётчик посещений: 3

Счётчик живёт в Redis, а не в веб-сервисе: перезапуск web его не обнулит.

04

Журналы, exec, run

Команды Compose принимают имя сервиса вместо имени контейнера. Журналы — всех сервисов сразу или выбранных; -f — следить; --no-log-prefix убирает приставку с именем контейнера:

$ docker compose logs --no-log-prefix web 2>&1 | grep -v WARNING | tail -3
172.24.0.1 - - [23/Oct/2026 17:17:54] "GET / HTTP/1.1" 200 -
172.24.0.1 - - [23/Oct/2026 17:17:54] "GET / HTTP/1.1" 200 -
172.24.0.1 - - [23/Oct/2026 17:17:54] "GET / HTTP/1.1" 200 -
$ docker compose logs --no-log-prefix redis | grep -m1 Ready
1:M 23 Oct 2026 17:17:47.000 * Ready to accept connections tcp
$ docker compose exec redis redis-cli get hits
3
$ docker compose exec web pip list 2>/dev/null | grep -i -E "^(flask|redis) "
Flask        3.1.2
redis        5.2.1

exec работает в уже запущенном контейнере сервиса. run создаёт новый одноразовый контейнер с настройками сервиса — так выполняют миграции базы, разовые скрипты, проверки:

$ docker compose run --rm web id
 Container counter-redis-1  Running
uid=999(app) gid=999(app) groups=999(app)

Строка counter-redis-1 Running — run тоже соблюдает depends_on и запускает зависимости, если они не работают.

05

Сеть и тома проекта

Compose сам создаёт для проекта сеть <проект>_default и подключает к ней все сервисы. Имя сервиса резолвится в адрес его контейнера — поэтому в REDIS_HOST записано просто redis:

$ docker network ls --filter name=counter
NETWORK ID     NAME              DRIVER    SCOPE
418a72bed60d   counter_default   bridge    local
$ docker volume ls --filter name=counter
DRIVER    VOLUME NAME
local     counter_redis-data
$ docker compose exec web python -c "import socket; print(socket.gethostbyname(\"redis\"))"
172.24.0.2

Сети можно описать и явно — верхний ключ networks: и networks: у каждого сервиса. Схему «прокси — приложение — база» из модуля 05 вы соберёте так в итоговом проекте.

Порт Redis в ps показан как 6379/tcp без стрелки — он доступен только внутри сети проекта. Публиковать порт базы или кэша, к которым ходит только приложение, не нужно.

06

Переменные и .env

Значения ${ИМЯ} в compose.yaml Compose берёт из окружения оболочки, а если там нет — из файла .env в каталоге проекта. Окружение важнее файла. Повторный up -d сравнивает конфигурацию с запущенной и пересоздаёт только изменившиеся сервисы — Redis продолжает работать (Running), счётчик не сбрасывается:

$ echo "GREETING=Здравствуйте" > .env
$ docker compose up -d
 Container counter-redis-1  Running
 Container counter-web-1  Recreate
 Container counter-web-1  Recreated
 Container counter-redis-1  Waiting
 Container counter-redis-1  Healthy
 Container counter-web-1  Starting
 Container counter-web-1  Started
$ curl -s http://127.0.0.1:8000/
Здравствуйте! Счётчик посещений: 4
$ GREETING="Добрый день" docker compose up -d 2>&1 | grep web
 Container counter-web-1  Recreate
 Container counter-web-1  Recreated
 Container counter-web-1  Starting
 Container counter-web-1  Started
$ curl -s http://127.0.0.1:8000/
Добрый день! Счётчик посещений: 5

Обычно в репозиторий кладут .env.example с перечнем переменных без значений, а настоящий .env с паролями создают на сервере и добавляют в .gitignore и .dockerignore.

Переменные окружения видны в docker inspect и docker compose config любому, у кого есть доступ к Docker на сервере. Для паролей в Compose есть ключ secrets: — значение подключается файлом в /run/secrets/ и не попадает в окружение. Официальные образы баз понимают это через переменные с окончанием _FILE: POSTGRES_PASSWORD_FILE со значением /run/secrets/db_password. Подробнее — модуль 08.
07

Готовность: healthcheck и depends_on

Без условия depends_on: [redis] лишь гарантирует, что контейнер Redis запущен раньше. Запущен — не значит готов: базе данных после старта нужно несколько секунд, а приложение, которое сразу пытается подключиться, падает. healthcheck описывает проверку готовности: Docker выполняет redis-cli ping каждые 5 секунд, и контейнер получает состояние starting, healthy или unhealthy. condition: service_healthy заставляет ждать healthy:

$ docker compose up -d 2>&1 | tail -3; docker compose ps --format "table {{.Service}}\t{{.Status}}"
 Container counter-redis-1  Healthy
 Container counter-web-1  Starting
 Container counter-web-1  Started
SERVICE   STATUS
redis     Up 5 seconds (healthy)
web       Up Less than a second
$ docker compose ps --format "table {{.Service}}\t{{.Status}}"
SERVICE   STATUS
redis     Up 13 seconds (healthy)
web       Up 7 seconds
$ docker inspect -f "{{json .State.Health.Log}}" counter-redis-1 | head -c 160; echo
[{"Start":"2026-10-23T20:18:36.524678813+03:00","End":"2026-10-23T20:18:36.600954243+03:00","ExitCode":0,"Output":"PONG\n"},{"Start":"2026-10-23T20:18:41.602244
$ docker compose down -v
 Container counter-web-1  Stopping
 Container counter-web-1  Stopped
 Container counter-web-1  Removing
 Container counter-web-1  Removed
 Container counter-redis-1  Stopping
 Container counter-redis-1  Stopped
 Container counter-redis-1  Removing
 Container counter-redis-1  Removed
 Volume counter_redis-data  Removing
 Network counter_default  Removing
 Volume counter_redis-data  Removed
 Network counter_default  Removed

Каждую проверку Docker записывает: время, код, вывод. Для PostgreSQL проверка — pg_isready -U postgres, для веб-сервиса — запрос на адрес проверки (в образе должна быть утилита для запроса: curl, wget или сам Python).

Готовность нужна не только при запуске. Приложение должно переживать и временную недоступность базы — переподключаться, а не падать навсегда. restart: unless-stopped страхует, но не заменяет этого.

08

stop, down и down -v

stop останавливает контейнеры, не удаляя их; start запускает снова:

$ docker compose stop
 Container counter-web-1  Stopping
 Container counter-web-1  Stopped
 Container counter-redis-1  Stopping
 Container counter-redis-1  Stopped
$ docker compose ps -a --format "table {{.Service}}\t{{.Status}}"
SERVICE   STATUS
redis     Exited (0) Less than a second ago
web       Exited (0) Less than a second ago
$ docker compose start
 Container counter-redis-1  Starting
 Container counter-redis-1  Started
 Container counter-redis-1  Waiting
 Container counter-redis-1  Healthy
 Container counter-web-1  Starting
 Container counter-web-1  Started

down удаляет контейнеры и сеть проекта, но не именованные тома. Поднимаем заново — счётчик продолжается с того же числа. down -v удаляет и тома — данные пропали:

$ docker compose down
 Container counter-web-1  Stopping
 Container counter-web-1  Stopped
 Container counter-web-1  Removing
 Container counter-web-1  Removed
 Container counter-redis-1  Stopping
 Container counter-redis-1  Stopped
 Container counter-redis-1  Removing
 Container counter-redis-1  Removed
 Network counter_default  Removing
 Network counter_default  Removed
$ docker volume ls --filter name=counter
DRIVER    VOLUME NAME
local     counter_redis-data
$ docker compose up -d 2>&1 | tail -3
 Container counter-redis-1  Healthy
 Container counter-web-1  Starting
 Container counter-web-1  Started
$ curl -s http://127.0.0.1:8000/
Здравствуйте! Счётчик посещений: 6
$ docker compose down -v
 Container counter-web-1  Stopping
 Container counter-web-1  Stopped
 Container counter-web-1  Removing
 Container counter-web-1  Removed
 Container counter-redis-1  Stopping
 Container counter-redis-1  Stopped
 Container counter-redis-1  Removing
 Container counter-redis-1  Removed
 Volume counter_redis-data  Removing
 Network counter_default  Removing
 Volume counter_redis-data  Removed
 Network counter_default  Removed
$ docker volume ls --filter name=counter
DRIVER    VOLUME NAME
КомандаКонтейнерыСетьТомаОбразы
stopостанавливает———
downудаляетудаляет——
down -vудаляетудаляетудаляет—
down --rmi localудаляетудаляет—собранные Compose
down -v на сервере — то же, что удалить базу. Привычка «down -v, чтобы начать с чистого листа» из разработки на сервер не переносится.
09

Шпаргалка

КомандаЧто делает
docker compose up -d [--build]Создать и запустить; пересоздать изменившееся
docker compose ps [-a]Состояние сервисов
docker compose logs -f [сервис]Журналы
docker compose exec сервис командаКоманда в работающем контейнере
docker compose run --rm сервис командаРазовый контейнер с настройками сервиса
docker compose pullСкачать новые версии образов; применить — up -d
docker compose restart сервисПерезапустить без пересоздания (новые переменные не применятся)
docker compose configПроверить файл и посмотреть итоговую конфигурацию
docker compose down [-v]Удалить контейнеры и сеть [и тома]

Команды работают с проектом в текущем каталоге. Из другого каталога — docker compose -f /opt/app/compose.yaml … или -p имя_проекта.

Ключевые выводы модуля

Запомните главное

  • compose.yaml в репозитории заменяет инструкцию по запуску; docker compose config — проверка
  • Сервисы видят друг друга по имени сервиса в сети проекта; базу и кэш не публиковать
  • Переменные — из окружения, потом из .env; .env с паролями не коммитить
  • healthcheck + depends_on: condition: service_healthy — запуск по готовности
  • up -d пересоздаёт только изменённые сервисы; restart не применяет новые настройки
  • down тома сохраняет, down -v удаляет данные
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Модуль 05: Сети Docker Все модули Модуль 07: Образы для эксплуатации