Итоговая практика
Соберём мини-стек для аналитики с нуля: база, загрузчик и BI-инструмент. Всё запускается одной командой и работает на вашем компьютере.
orders.csv → loader → Postgres → Metabase (дашборды в браузере) ↘ Adminer (просмотр таблиц)На схеме — то, что мы построим. Одна сеть, четыре сервиса, два вида хранения данных.

Шаг 1. Папка проекта
Заголовок раздела «Шаг 1. Папка проекта»Создайте новую папку и скопируйте в неё данные и скрипты из учебного набора:
mkdir ~/shop-stackcd ~/shop-stackcp -r ~/docker-practice/app ~/docker-practice/data .lsПроверка: app data.
Шаг 2. Файлы
Заголовок раздела «Шаг 2. Файлы»Dockerfile (образ загрузчика):
FROM python:3.12-slim
RUN useradd --create-home appuser
WORKDIR /app
COPY app/requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
COPY app/ .
USER appuser
CMD ["python", "report.py"].env:
echo "DB_PASSWORD=secret" > .envecho "DB_PASSWORD=change-me" > .env.exampleecho ".env" > .gitignorecompose.yaml:
services: db: image: postgres:17 restart: unless-stopped environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: shop ports: - "127.0.0.1:5432:5432" volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres -d shop"] interval: 5s timeout: 3s retries: 10
loader: build: . command: ["python", "load_orders.py"] environment: DB_HOST: db DB_PASSWORD: ${DB_PASSWORD} volumes: - ./data:/data:ro depends_on: db: condition: service_healthy
metabase: image: metabase/metabase:v0.65.4 restart: unless-stopped environment: MB_DB_FILE: /metabase-data/metabase.db ports: - "3000:3000" volumes: - metabase-data:/metabase-data depends_on: db: condition: service_healthy
adminer: image: adminer:5 ports: - "8080:8080" depends_on: - db
volumes: pgdata: metabase-data:Что здесь нового:
metabaseхранит свои настройки (пользователь, дашборды) в файле.MB_DB_FILEзадаёт путь к файлу, а томmetabase-dataсохраняет его.- Два тома: один для данных, второй для настроек BI.
adminer— тот же веб-клиент базы, что в уроке Docker Compose. Удобно смотреть таблицы без Metabase.
Проверьте файл:
docker compose config -q && echo okПроверка: ok.
Шаг 3. Запуск
Заголовок раздела «Шаг 3. Запуск»Сначала остановите стек из прошлых уроков. Иначе порты 5432 и 8080 будут заняты:
cd ~/docker-practice && docker compose downcd ~/shop-stackЗапустите новый стек:
docker compose up -d --buildMetabase стартует медленно. Следите за логом:
docker compose logs -f metabaseВыйти из лога: Ctrl+C. Проверьте готовность:
curl localhost:3000/api/healthПроверка: {"status":"ok"}. Пока Metabase запускается, ответ другой: {"status":"initializing", ...} или ошибка curl: (52) или curl: (56). Подождите 1–2 минуты и повторите.
Проверьте, что загрузчик отработал:
docker compose logs loaderПроверка: Загружено строк: 500.
Шаг 4. Metabase
Заголовок раздела «Шаг 4. Metabase»Откройте http://localhost:3000.
- Выберите язык English: так названия кнопок совпадут с уроком. Создайте администратора. Почта может быть любой. Пароль запомните.
- Metabase предложит добавить данные. Выберите PostgreSQL. Если пропустили шаг, откройте Admin → Databases → Add database.
- Заполните поля:
| Поле | Значение |
|---|---|
| Display name | shop |
| Host | db |
| Port | 5432 |
| Database name | shop |
| Username | postgres |
| Password | пароль из .env (secret) |
Нажмите Save. Metabase проверит подключение и прочитает таблицы.
Шаг 5. Первый вопрос
Заголовок раздела «Шаг 5. Первый вопрос»- Нажмите New → SQL query и выберите базу
shop. - Вставьте запрос и запустите:
SELECT channel, round(sum(amount), 2) AS revenueFROM ordersWHERE status = 'completed'GROUP BY channelORDER BY revenue DESC;Проверка:
| channel | revenue |
|---|---|
| organic | 23715.82 |
| ads | 23082.74 |
| 15459.67 |
Эти цифры совпадают с report.py и dbt из прошлых уроков. Сохраните вопрос и выберите вид «Bar» (столбцы).
Практика
Заголовок раздела «Практика»Задание 1
Заголовок раздела «Задание 1»Остановите стек командой down и запустите снова. Войдите в Metabase. Остались ли ваш пользователь, подключение и сохранённый вопрос?
Проверка: да. Данные в Postgres тоже на месте: 500 строк.
Решение
Запускаем только db и metabase, без загрузчика. Иначе загрузчик заново зальёт 500 строк, и проверка ничего не докажет.
docker compose downdocker compose up -d --wait db metabasecurl localhost:3000/api/healthdocker compose exec db psql -U postgres -d shop -tc "select count(*) from orders"down удаляет контейнеры, но не тома. Всё состояние лежит в томах pgdata и metabase-data.
Задание 2
Заголовок раздела «Задание 2»Докажите, что down -v стирает данные. Выполните docker compose down -v. Запустите только базу: docker compose up -d --wait db. Посчитайте строки в orders.
Проверка: ошибка relation "orders" does not exist. Таблицы нет: том удалён.
Решение
docker compose down -vdocker compose up -d --wait dbdocker compose exec db psql -U postgres -d shop -tc "select count(*) from orders"--wait ждёт, пока база станет healthy. Без него psql может запуститься раньше базы и выдать другую ошибку.
Важно запустить только db. Если выполнить up -d без имени службы, загрузчик создаст таблицу заново, и вы не увидите потери. Metabase тоже начнёт с чистого листа: придётся снова создать администратора.
Задание 3
Заголовок раздела «Задание 3»Верните данные одной командой. Не запускайте up для всех служб: запустите только загрузчик.
Проверка: в orders снова 500 строк.
Решение
docker compose run --rm loaderdocker compose exec db psql -U postgres -d shop -tc "select count(*) from orders"run ждёт, пока загрузчик закончит. Compose сам запустит db, если она не работает (она нужна по depends_on), и дождётся healthy.
Задание 4
Заголовок раздела «Задание 4»Порт 3000 у вас занят? Или просто смените его: пусть Metabase открывается на порту 3001. Что нужно изменить?
Проверка: Metabase отвечает на localhost:3001. Внутри сети ничего не меняется: подключение к базе остаётся db:5432.
Решение
В compose.yaml поменяйте левое число:
ports: - "3001:3000"docker compose up -d metabasecurl localhost:3001/api/healthЛевое число — порт на вашем компьютере. Правое — порт внутри контейнера. Его менять не надо.
Задание 5
Заголовок раздела «Задание 5»Добавьте в конец data/orders.csv новый заказ и загрузите файл ещё раз, не пересоздавая стек:
1501,2024-04-01,5,email,99.00,completedСколько строк теперь в orders? Какая выручка у email?
Проверка: 501 строка. Выручка email: 15558.67 (было 15459.67, плюс 99.00).
Решение
echo "1501,2024-04-01,5,email,99.00,completed" >> data/orders.csvdocker compose run --rm loaderdocker compose exec db psql -U postgres -d shop -tc "select count(*) from orders"docker compose exec db psql -U postgres -d shop -tc "select sum(amount) from orders where channel='email' and status='completed'"В Metabase обновите вопрос: цифра изменилась. Затем удалите последнюю строку из файла и загрузите снова:
sed -i.bak '$d' data/orders.csv && rm data/orders.csv.bakdocker compose run --rm loaderЗадание 6 (по желанию)
Заголовок раздела «Задание 6 (по желанию)»Добавьте в стек службу dbt из урока «Compose для пайплайна». Запустите docker compose run --rm dbt run. В Metabase обновите список таблиц базы shop (Admin → Databases → Sync database schema). Появилась ли схема analytics?
Проверка: dbt пишет PASS=1 WARN=0 ERROR=0. В Metabase в схеме Analytics есть таблица Revenue By Channel. Metabase показывает имена так: с большой буквы и без _.
Решение
Скопируйте папку dbt/ из прошлого урока:
cp -r ~/docker-practice/dbt .printf 'dbt/target/\ndbt/logs/\ndbt/.user.yml\n' >> .gitignoreВставьте службу в блок services: файла compose.yaml, перед строкой volumes::
dbt: build: ./dbt profiles: ["tools"] environment: DB_PASSWORD: ${DB_PASSWORD} DBT_PROFILES_DIR: /project volumes: - ./dbt:/project depends_on: db: condition: service_healthydocker compose run --rm dbt runОстановить стек и сохранить данные:
docker compose downУдалить всё, в том числе тома:
docker compose down -vУдалить образы загрузчика и dbt, если они больше не нужны:
docker image lsdocker rmi shop-stack-loader shop-stack-dbtИмя образа — <папка>-<служба>. Проверьте его в списке docker image ls.
Что вы умеете теперь
Заголовок раздела «Что вы умеете теперь»- Объяснить, чем контейнер отличается от виртуальной машины.
- Запускать готовые образы:
docker run,ps,logs,exec,stop,rm. - Открывать порты и передавать переменные окружения.
- Хранить данные в томах и не терять их.
- Писать
Dockerfileи собирать образ для своего скрипта. - Объединять контейнеры в сеть и ходить по имени службы, а не
localhost. - Описывать стек в
compose.yaml:build,healthcheck,depends_on,.env,profiles. - Поднимать у себя Postgres, BI и dbt одной командой.
Дальше:
- Open source за минуты: Airflow, Jupyter, Kafka и другие инструменты данных.
- Нужен ли Kubernetes: когда хватит контейнеров, а когда нужен оркестратор.
- Шпаргалка: все команды на одной странице.