← [Раздел](README.md) · [Главная](../README.md)

# Lab 03 — Docker Compose стенд

## Цель

Поднять **локальный стенд** ShopLite: API, PostgreSQL, Redis, health checks и переменные окружения — по принципам 12-factor, без секретов в образе.

## Предварительно

- [Lab 02 — NFR](lab-02-nfr-checklist.md)
- [Контейнеры и Docker](../10-deploy/kontejnery-i-docker.md)
- Docker Compose v2 установлен

## Время

~2–3 часа

---

## Шаг 1 — Каркас проекта

```bash
mkdir -p lab-03-compose/api
cd lab-03-compose
```

Минимальный API на любом языке (Go/Node/Python) с endpoint'ами:

- `GET /health/live` → 200
- `GET /health/ready` → 200 если БД доступна
- `GET /api/products` → JSON из Postgres
- `POST /api/cart` → запись в Redis (упрощённо)

---

## Шаг 2 — Dockerfile (multi-stage)

`api/Dockerfile` — по образцу из [kontejnery-i-docker.md](../10-deploy/kontejnery-i-docker.md):

- non-root user;
- один бинарь/слой runtime;
- `EXPOSE 8080`.

Соберите локально:

```bash
docker build -t shoplite-api:lab03 ./api
```

---

## Шаг 3 — docker-compose.yml

```yaml
services:
  api:
    build: ./api
    ports:
      - "8080:8080"
    environment:
      DATABASE_URL: postgres://shop:example@db:5432/shoplite?sslmode=disable
      REDIS_URL: redis://cache:6379/0
      LOG_LEVEL: info
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health/live"]
      interval: 10s
      timeout: 3s
      retries: 3

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: shop
      POSTGRES_PASSWORD: example
      POSTGRES_DB: shoplite
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U shop -d shoplite"]
      interval: 5s
      timeout: 3s
      retries: 5

  cache:
    image: redis:7-alpine
    command: ["redis-server", "--save", "", "--appendonly", "no"]

volumes:
  pgdata:
```

Пароль `example` — **только для localhost**.

---

## Шаг 4 — init.sql

```sql
CREATE TABLE IF NOT EXISTS products (
  id SERIAL PRIMARY KEY,
  sku TEXT NOT NULL,
  title TEXT NOT NULL,
  price_cents INT NOT NULL
);

INSERT INTO products (sku, title, price_cents) VALUES
  ('BOOK-1', 'Архитектура облака', 99000),
  ('MUG-1', 'Кружка SRE', 15000);
```

---

## Шаг 5 — Запуск и smoke

```bash
docker compose up --build -d
docker compose ps
curl -s http://localhost:8080/api/products | head
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/health/ready
```

Ожидание: `200` на ready после старта БД.

---

## Шаг 6 — Проверка NFR из Lab 02

| NFR | Как проверить на стенде |
|-----|-------------------------|
| NFR-P01 | `curl -w time_total` 10 раз, среднее |
| NFR-S02 | `grep -r password api/` — только через env |
| Health | `docker compose kill api` → compose restart policy |

Запишите результаты в `docs/nfr/lab03-smoke-results.md`.

---

## Шаг 7 — Логи в stdout

Убедитесь, что API пишет **JSON** в stdout:

```json
{"level":"info","event":"request","path":"/api/products","duration_ms":12}
```

`docker compose logs api` должен быть читаемым.

---

## Шаг 8 — Очистка

```bash
docker compose down -v
```

Добавьте в корневой `README.md` инструкцию «Как запустить Lab 03».

---

## Шаг 9 — Коммит

```bash
git add lab-03-compose docs/nfr/lab03-smoke-results.md
git commit -m "lab03: docker-compose stack for ShopLite"
```

---

## Самопроверка

1. Зачем `depends_on` с `condition: service_healthy`?
2. Где хранятся данные Postgres между перезапусками?
3. Почему пароль БД в compose допустим только локально?
4. Чем `/health/live` отличается от `/health/ready` в лабе?

---

## Дальше

→ [Lab 04 — Review](lab-04-review-arhitektury.md)  
← [Lab 02 — NFR](lab-02-nfr-checklist.md)
