## Зачем Docker Compose

Когда сервисов несколько (БД, бэкенд, фронт, прокси), писать длинные `docker run` неудобно. **Compose** описывает весь стек в одном файле (`compose.yaml` или `docker-compose.yml`), поднимает общую сеть и тома, учитывает порядок старта через `depends_on` и проверки `healthcheck`.

Команды:

```
docker compose up -d --build
docker compose ps
docker compose logs -f api
docker compose down
docker compose down -v
```

Последняя команда с **`-v`** удалит именованные тома — данные БД пропадут (удобно для сброса демо, опасно если это прод).

---

## Где лежит учебный проект

В репозитории подготовлен полный стек (четыре сервиса):

| Путь | Содержимое |
|------|------------|
| `docker/examples/multi-service/compose.yaml` | Описание всех сервисов, сетей, томов |
| `docker/examples/multi-service/.env.example` | Пример переменных окружения для Compose |
| `docker/examples/multi-service/web/` | Nginx + статика через `COPY` |
| `docker/examples/multi-service/api/` | Flask + Postgres + загрузка файлов в том |
| `docker/examples/multi-service/proxy/` | Входной nginx: `/` → web, `/api/` → api |

Скопируйте переменные окружения:

```
cd docker/examples/multi-service
cp .env.example .env
```

**Скриншот:** файловый менеджер или IDE с деревом папок `multi-service` (видны `compose.yaml`, `web`, `api`, `proxy`).

<img src="./assets/compose_project_tree.png" width="auto">

Запуск:

```
docker compose up -d --build
```

**Скриншот:** вывод `docker compose up` — этапы build, Creating..., Started.

<img src="./assets/compose_up_build_started.png" width="auto">

**Скриншот:** `docker compose ps` — все сервисы `running` (или `healthy` где настроено).

<img src="./assets/compose_ps_all_running.png" width="auto">

Откройте в браузере `http://127.0.0.1:8080` (если в `.env` не меняли `HTTP_PORT`).

**Скриншот:** главная страница демо со ссылками на API.

<img src="./assets/compose_browser_home.png" width="auto">

Проверка API через прокси:

```
curl -s http://127.0.0.1:8080/api/health | jq .
curl -s http://127.0.0.1:8080/api/notes | jq .
curl -s -X POST http://127.0.0.1:8080/api/notes \
  -H 'Content-Type: application/json' \
  -d '{"body":"Первая заметка из curl"}' | jq .

echo demo > /tmp/upload-demo.txt
curl -s -F "file=@/tmp/upload-demo.txt" http://127.0.0.1:8080/api/upload | jq .
curl -s http://127.0.0.1:8080/api/uploads | jq .
```

**Скриншот:** терминал с ответами JSON.

<img src="./assets/compose_curl_api_notes.png" width="auto">

---

## Логическая схема стека

1. **Пользователь хоста** подключается только к **proxy** (порт `HTTP_PORT`).
2. **proxy** по пути `/` отдаёт статику с **web**; по `/api/` — **api** (внутренний URL без префикса `/api`).
3. **api** ходит в **db** по hostname `db` и порту `5432` (стандарт Postgres) — резолвится DNS-ом Docker в той же сети `backend`.
4. Данные БД — в томе **`pgdata`**; загруженные файлы API — в томе **`api_uploads`**.

**Скриншот:** нарисованная схема стрелок browser → proxy → web/api → db; подписать тома.

<img src="./assets/compose_architecture_diagram.png" width="700">

---

## Разбор ключевых фрагментов `compose.yaml`

### Имена и сеть

Поле `name: multi-service-demo` задаёт **префикс** для имён контейнеров/ресурсов по умолчанию (удобно не конфликтовать с другими проектами).

Сеть `backend` с драйвером `bridge` — все перечисленные сервисы в одной L2-сети, видят друг друга по **DNS-имени сервиса** (`db`, `api`, `web`, `proxy`).

### Зависимости и здоровье

```
depends_on:
  db:
    condition: service_healthy
```

Без `condition` Compose лишь **упорядочивает** старт, но не ждёт готовности БД. С `healthcheck` у `db` сервис `api` не начнёт считаться поднятым для зависимых, пока Postgres не пройдёт проверку.

**Скриншот:** `docker compose logs db` в момент `database system is ready to accept connections`.

<img src="./assets/compose_logs_db_ready.png" width="auto">

### `expose` vs `ports`

- **`ports`** — публикация на хост (нужно для proxy).
- **`expose`** — документация + открытие порта **между** контейнерами сети; на сетевой интерфейс хоста не выводится.

Так мы не публикуем Postgres наружу — к БД можно попасть только из контейнеров в той же сети (или если вы явно добавите `ports` для отладки).

### Тома

```yaml
volumes:
  pgdata:
  api_uploads:
```

Именованные тома хранятся в области Docker на диске. Пример «скопировать/сохранить файл в Docker» в runtime:

```
echo hello > /tmp/up.txt
# имя контейнера смотрите в выводе: docker compose ps -q api
docker cp /tmp/up.txt "$(docker compose ps -q api)":/data/uploads/host-demo.txt
docker compose exec api ls -la /data/uploads
```

**Скриншот:** список файлов в `/data/uploads` внутри контейнера `api`.

<img src="./assets/compose_api_uploads_volume.png" width="auto">

Альтернатива для разработки — **bind-mount** каталога с хоста в сервис (в учебном файле не включено, чтобы не привязывать пути к вашему `$HOME`):

```yaml
services:
  api:
    volumes:
      - ./local_uploads:/data/uploads
```

---

## Копирование файлов в контейнер в контексте Compose

| Способ | Когда использовать |
|--------|---------------------|
| `COPY` в `Dockerfile` | Статика, код, конфиги, которые должны быть **в образе** на момент деплоя |
| Именованный / bind том | Данные, логи, загрузки пользователей, меняющиеся без пересборки |
| `docker cp` | Разовая отладка, экстренная подмена файла |

---

## Обновление одного сервиса после правки кода

```
# изменили только api/app.py
docker compose build api
docker compose up -d api
```

**Скриншот:** пересборка только слоя `COPY app.py` (кэш BuildKit для предыдущих шагов).

<img src="./assets/compose_rebuild_api_cache.png" width="auto">

---

## Просмотр конфигурации, которую «увидел» Compose

```
docker compose config
```

**Скриншот:** полный YAML после подстановки `.env` (проверьте порты и пароли перед скрином — размыть секреты).

<img src="./assets/compose_config_interpolated.png" width="auto">

---

## Остановка и очистка

Мягко (тома сохраняются):

```
docker compose down
```

Полный сброс демо:

```
docker compose down -v --rmi local
```

**Скриншот:** `docker volume ls` без томов проекта после `down -v`.

<img src="./assets/compose_down_volumes_removed.png" width="auto">

---

## Идеи для дополнительных скриншотов (по желанию)

- **Portainer** или Docker Desktop — список контейнеров и томов в GUI.
- **`docker compose top`** — процессы внутри сервисов.
- **Ошибка** неверного `depends_on` / healthcheck — учебный кадр «как выглядит», если api стартует раньше БД без `condition`.

---

## Документация

- [Compose file reference](https://docs.docker.com/compose/compose-file/)
- [Networking in Compose](https://docs.docker.com/compose/networking/)
- [Use Compose Watch](https://docs.docker.com/compose/file-watch/) — live reload при разработке (отдельная тема)
