Manifest storage: где хранить production manifest для Slim CI
Slim CI (которую разберём в следующем уроке) работает на сравнении: «что в моей feature branch отличается от production?». Для этого нужно знать, что есть в production. Эта информация — в файле target/manifest.json, который генерируется при каждом dbt parse / dbt run / dbt build.
Manifest production-build’а нужно где-то хранить между запусками. Это центральный паттерн в dbt CI. В этом уроке разберём три способа: S3 (или совместимое объектное хранилище), GitHub Pages, GitHub Artifacts. У каждого свои plus и минусы.
dbt-iii: manifest.json изнутри — структура и содержимоеЧто такое manifest.json и зачем его хранить
После каждого dbt build (или dbt parse) в target/manifest.json лежит полная картина проекта: все модели, их колонки, конфигурации, hashes, зависимости.
Простая иллюстрация:
{
"metadata": {
"dbt_version": "1.10.0",
"generated_at": "2026-05-19T08:30:00Z",
"invocation_id": "abc-123",
"project_name": "jaffle_shop"
},
"nodes": {
"model.jaffle_shop.stg_customers": {
"raw_code": "SELECT id, name FROM ...",
"checksum": {"name": "sha256", "checksum": "abc123..."},
"config": {"materialized": "view"},
"columns": {"id": {...}, "name": {...}}
},
...
}
}
Slim CI сравнивает manifest.json от текущего PR с production manifest.json. По разности checksums определяет какие модели изменились -> запускает только их.
Поэтому production manifest должен быть:
- Persistent — переживать между запусками.
- Versioned — нужен последний прошедший production build.
- Доступен из CI — для fetch в feature branch.
Три варианта storage
Вариант 1: S3 (или R2/GCS/Azure Blob)
Принцип: prod-build загружает manifest в bucket, PR-build скачивает.
prod workflow
# .github/workflows/dbt-prod.yml
name: dbt Prod Build
on:
push:
branches: [main]
schedule:
- cron: '0 5 * * *' # daily 5am
jobs:
build-prod:
runs-on: ubuntu-latest
env:
DBT_PROFILES_DIR: .
SNOWFLAKE_ACCOUNT: ${'{{'} secrets.SNOWFLAKE_ACCOUNT {'}}'}
SNOWFLAKE_USER: ${'{{'} secrets.SNOWFLAKE_USER_PROD {'}}'}
SNOWFLAKE_PASSWORD: ${'{{'} secrets.SNOWFLAKE_PASSWORD_PROD {'}}'}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install dbt-snowflake==1.10.0
- name: dbt build prod
run: |
dbt deps
dbt build --target prod
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${'{{'} secrets.AWS_ACCESS_KEY_ID {'}}'}
aws-secret-access-key: ${'{{'} secrets.AWS_SECRET_ACCESS_KEY {'}}'}
aws-region: us-east-1
- name: Upload manifest to S3
run: |
aws s3 cp target/manifest.json s3://my-dbt-state/prod/manifest.json
# Сохранить timestamped версию для troubleshooting
aws s3 cp target/manifest.json "s3://my-dbt-state/prod/manifest-$(date +%Y%m%d-%H%M%S).json"
После каждого успешного prod build:
s3://my-dbt-state/prod/manifest.json— latest.s3://my-dbt-state/prod/manifest-20260519-053012.json— timestamped backup.
PR workflow (использование)
# .github/workflows/dbt-ci.yml
jobs:
dbt-build-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${'{{'} secrets.AWS_ACCESS_KEY_ID {'}}'}
aws-secret-access-key: ${'{{'} secrets.AWS_SECRET_ACCESS_KEY {'}}'}
aws-region: us-east-1
- name: Download prod manifest
run: |
mkdir -p ./prod-state/
aws s3 cp s3://my-dbt-state/prod/manifest.json ./prod-state/manifest.json
- name: dbt build slim CI
run: |
dbt build --target ci \
--select state:modified+ \
--defer \
--state ./prod-state/
Ключевое — --state ./prod-state/ указывает на скачанный manifest. dbt сравнит его с текущим и определит, что изменилось.
Вариант 2: GitHub Pages
Если у вас open source или нет S3 / нежелательно платить — manifest можно хранить в orphan ветке gh-pages того же репо.
prod workflow
- name: Publish manifest to gh-pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${'{{'} secrets.GITHUB_TOKEN {'}}'}
publish_dir: ./target
publish_branch: gh-pages
keep_files: false
destination_dir: prod/
После этого https://<your-org>.github.io/<repo>/prod/manifest.json — публичный URL.
PR workflow
- name: Download prod manifest
run: |
mkdir -p ./prod-state/
curl -L -o ./prod-state/manifest.json \
https://your-org.github.io/your-repo/prod/manifest.json
- name: dbt build slim CI
run: dbt build --select state:modified+ --defer --state ./prod-state/
GitHub Pages публичный по умолчанию. Manifest.json содержит структуру вашего проекта (имена моделей, колонки, конфиги, иногда комментарии-метаданные). Если этого не должны видеть посторонние — либо сделайте repo private + Pages private (требует GitHub Pro/Team), либо используйте другой вариант (S3 с private bucket, GitHub Artifacts).
Versioning
GitHub Pages не умеет versioning из коробки. Workaround — публиковать с timestamped имена:
- name: Publish manifest with timestamp
run: |
mkdir -p ./gh-pages-content/
cp target/manifest.json ./gh-pages-content/manifest-latest.json
cp target/manifest.json "./gh-pages-content/manifest-$(date +%Y%m%d-%H%M%S).json"
# ... затем publish
В PR использовать manifest-latest.json для slim CI, timestamped — для historical analysis.
Вариант 3: GitHub Artifacts
Артефакты GitHub Actions — это файлы, которые сохраняются после workflow run и доступны через GitHub API. Подходит для CI-only сценариев.
prod workflow
- name: Upload manifest artifact
uses: actions/upload-artifact@v4
with:
name: prod-manifest
path: target/manifest.json
retention-days: 90 # default 90, можно настроить
PR workflow
- name: Download latest prod manifest
uses: dawidd6/action-download-artifact@v9
with:
name: prod-manifest
workflow: dbt-prod.yml
workflow_conclusion: success
branch: main
path: ./prod-state/
- run: dbt build --select state:modified+ --defer --state ./prod-state/
dawidd6/action-download-artifact — community action для скачивания артефактов из другого workflow (вне родного actions/download-artifact, который умеет только в рамках одного workflow).
Ограничения
- Retention 90 дней (по умолчанию). Старые манифесты автоматически удаляются.
- Размер artifact — до 10GB per artifact, 20-50 GB total на репо.
- Доступны только через GitHub API (не через прямой URL).
Для большинства проектов 90 дней достаточно — manifest нужен только последний для Slim CI.
Сравнение в таблице
| Параметр | S3 | GitHub Pages | GitHub Artifacts |
|---|---|---|---|
| Стоимость | $0.023/GB/мес | бесплатно | бесплатно (с лимитами) |
| Setup сложность | средняя | низкая | низкая |
| Privacy | private (default) | public (default) | private |
| Versioning | да (S3 versioning) | manual | retention 90d |
| Cross-repo | да | да | нет (только в рамках GH) |
| Production-ready | да | условно | да |
Versioning стратегии
Latest + timestamped
s3://bucket/prod/manifest.json # latest, всегда обновляется
s3://bucket/prod/history/manifest-20260519-053012.json # immutable backup
s3://bucket/prod/history/manifest-20260518-053012.json
s3://bucket/prod/history/manifest-20260517-053012.json
Slim CI использует latest. History для troubleshooting («что было неделю назад?»).
По git commit SHA
s3://bucket/prod/manifest-abc123.json # main commit abc123
s3://bucket/prod/manifest-def456.json # main commit def456
s3://bucket/prod/manifest-latest.json # symlink/alias к самому свежему
Преимущество — точная атрибуция: «manifest от commit abc123». Удобно для archaelogical investigation.
По release tag
s3://bucket/prod/v1.5.0/manifest.json
s3://bucket/prod/v1.6.0/manifest.json
s3://bucket/prod/latest/manifest.json
Если у вас релизный процесс (теги по semver). Slim CI всегда от latest.
Stale manifest: критическая гочча
Что если prod build падал последние 3 дня, но не починили? Manifest устаревший — отражает состояние от 3 дней назад. Slim CI с этим манифестом будет неверным.
Stale manifest — самая частая гочча Slim CI. Если prod build не прогонялся (упал, отключили cron, забыли смержить deploy), manifest не обновляется. Slim CI начинает сравнивать с состоянием недельной давности — пропускает реальные изменения или считает изменением то, что уже в проде. Результат — ломаются prod modeled таблицы.
Мониторинг свежести manifest
В CI добавить проверку:
- name: Check manifest freshness
run: |
python -c "
import json
from datetime import datetime, timezone, timedelta
with open('./prod-state/manifest.json') as f:
m = json.load(f)
generated = datetime.fromisoformat(m['metadata']['generated_at'].replace('Z', '+00:00'))
age = datetime.now(timezone.utc) - generated
if age > timedelta(days=2):
print(f'ERROR: prod manifest is {age.days} days old')
exit(1)
print(f'Manifest age: {age.days} days {age.seconds // 3600}h')
"
Если manifest старше 2 дней — workflow падает с понятной ошибкой. Команда знает что нужно перезапустить prod build.
fallback: если нет state, делаем full build
- name: Try download prod manifest
id: download
continue-on-error: true
run: aws s3 cp s3://my-dbt-state/prod/manifest.json ./prod-state/manifest.json
- name: dbt build (slim if manifest available, full otherwise)
run: |
if [ -f ./prod-state/manifest.json ]; then
echo "Using Slim CI with state"
dbt build --target ci --select state:modified+ --defer --state ./prod-state/
else
echo "No manifest — full build"
dbt build --target ci
fi
Graceful degradation: если manifest недоступен (первый раз setup, или S3 down) — делаем full build. Дольше, но не блокируем разработку.
Попробуй сам
В вашем dbt-проекте с GitHub:
- Создайте
.github/workflows/upload-manifest.yml:
name: Upload Manifest
on:
workflow_dispatch: # manual trigger для теста
jobs:
upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install dbt-duckdb==1.10.0
- run: dbt deps
- run: dbt parse --target dev
- name: Upload manifest as artifact
uses: actions/upload-artifact@v4
with:
name: prod-manifest
path: target/manifest.json
retention-days: 30
-
Запустите вручную через GitHub UI (Actions -> Upload Manifest -> Run workflow).
-
После прогона — скачайте artifact через UI, посмотрите содержимое manifest.json.
-
Создайте второй workflow, который скачивает этот artifact:
name: Use Manifest
on:
workflow_dispatch:
jobs:
use:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dawidd6/action-download-artifact@v9
with:
name: prod-manifest
workflow: upload-manifest.yml
path: ./prod-state/
- run: ls -la ./prod-state/
- run: cat ./prod-state/manifest.json | python -c "import json, sys; m = json.load(sys.stdin); print('Generated:', m['metadata']['generated_at'])"
Запустите — увидите как manifest перенесён между workflows.
Бонус: добавьте freshness check (manifest older than 2 days -> fail). Это шаблон, который используется в production CI.
Ключевые выводы
- manifest.json — снимок состояния проекта (модели, колонки, конфиги, hashes). Генерируется
dbt parse/run/build. Нужен для Slim CI — сравнения с production. - Три варианта storage: S3 (production-ready, платный), GitHub Pages (бесплатно, public по дефолту), GitHub Artifacts (бесплатно с лимитами, retention 90d).
- Workflow паттерн: prod build -> upload manifest -> PR build -> download manifest ->
dbt build --state ./prod-state/. - Versioning: latest + timestamped/SHA backup. Latest для Slim CI, history для troubleshooting.
- Stale manifest — критическая гочча. Если prod build упал, manifest устаревший, Slim CI работает неверно. Решение — мониторинг свежести (fail if older than 2 days) и graceful fallback на full build.
- GitHub Pages — public по умолчанию. Manifest может содержать sensitive metadata — нужно учесть.
- GitHub Artifacts — простой вариант для CI-only, ограничен retention и доступен только через GH API.