A production-grade dbt template. It features a blazing-fast Python environment managed by uv, unified tooling (sqlfluff), and a self-contained dbt-postgres/ project with profiles.yml supporting multi-environment deployments.
dbt-postgres/ is the reference implementation — kept self-contained so it can be copied as-is into a new repo — demonstrating the industry-standard staging → intermediate → marts layered architecture with a realistic multi-source ecommerce domain (Shopify + Stripe).
- Architecture & DAG
- Project Layout
- Production Features
- Naming Conventions
- Quick Start (Step-by-step)
- Command Reference
- Multi-environment Configuration
- Starting a New Project
This template enforces a strict separation of concerns through three distinct layers:
| Layer | Prefix | Schema | Materialization | Purpose |
|---|---|---|---|---|
| 1. Staging | stg_ |
staging |
view |
1:1 mapped to sources. Renames columns, casts types, trims whitespace. No joins. |
| 2. Intermediate | int_ |
intermediate |
view |
Where the heavy lifting happens. Cross-source joins & complex business logic. Queryable for debugging. |
| 3. Marts | dim_ / fct_ |
marts |
table |
Clean, documented, data-contract-enforced tables ready for BI consumption. |
Shopify (4 tables) Stripe (1 table)
│ │
▼ ▼
[ stg_shopify_* ] [ stg_stripe_* ]
│ │
└──────────────┬─────────────┘
▼
[ int_orders_enriched ] & [ int_customer_orders ]
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
[ dim_* ] [ fct_* ] [ fct_payments ]
│ │ │
└─────────► [ BI Dashboards / Exposures ]
dbt/ ← Template Root
├── pyproject.toml # Python dependencies (dbt, sqlfluff)
├── uv.lock # Deterministic dependency lockfile
├── .gitignore
│
└── dbt-postgres/ ← The dbt Project (self-contained)
├── dbt_project.yml
├── profiles.yml # dev/prod/ci targets
├── .env.example # Template for database credentials
├── packages.yml # dbt_utils + dbt_expectations
├── .sqlfluff # Linting rules
│
├── models/
│ ├── staging/ # Grouped by source system
│ │ ├── shopify/ # ├── _shopify__sources.yml (inputs)
│ │ │ # ├── _shopify__models.yml (outputs)
│ │ │ # └── stg_shopify__*.sql
│ │ └── stripe/
│ │
│ ├── intermediate/ # Flat directory — logic crosses sources
│ │ └── schema.yml
│ │
│ └── marts/ # Grouped by business domain
│ ├── _groups.yml # Defines ownership (core, finance)
│ ├── _exposures.yml # Documents downstream dashboards
│ ├── core/ # ├── _core__models.yml (+contract enforced)
│ │ # └── dim_*, fct_*
│ └── finance/ # ├── _finance__models.yml
│ # └── fct_*
│
├── seeds/ # Static reference data (e.g. country codes)
├── snapshots/ # SCD Type 2 tracking (timestamp & check strategies)
├── tests/ # Singular & Unit tests
├── macros/ # Reusable Jinja snippets (cents_to_dollars)
└── analyses/ # Ad-hoc analytical queries
| Feature | Description |
|---|---|
| Multi-source Integration | Demonstrates cross-joining shopify and stripe cleanly in the intermediate layer. |
| Source Freshness | SLA monitoring configured via loaded_at_field in sources.yml. |
| Data Contracts | contract.enforced: true on all mart models guarantees schema stability for BI tools. |
| Model Groups | _groups.yml establishes strict ownership boundaries (e.g., Core team vs Finance team). |
| Active Snapshots | Includes real-world SCD Type 2 configs using both timestamp and check strategies. |
| Unit Testing | Leverages dbt 1.8+ static-input logic validation in tests/unit_tests.yml. |
| Exposures | Clearly documents which dashboards break if a dim_ or fct_ model is altered. |
| CI/CD Ready | profiles.yml handles isolated target schemas dynamically based on pipeline IDs. |
This project strictly adheres to dbt Labs 2025 conventions:
| Object | Convention | Example |
|---|---|---|
| Source | source('<system>', '<table>') |
source('shopify', 'raw_customers') |
| Staging model | stg_<source>__<entity> |
stg_shopify__customers |
| Intermediate model | int_<description> |
int_orders_enriched |
| Fact table | fct_<noun> |
fct_orders, fct_payments |
| Dimension table | dim_<noun> |
dim_customers, dim_products |
| Snapshot | scd_<entity> |
scd_products |
| Seed | seed_<noun> |
seed_country_codes |
| Staging Configs | _<source>__sources/models.yml |
_shopify__sources.yml |
| Marts Configs | _<domain>__models.yml |
_core__models.yml |
Why two YAML filename conventions? Staging is grouped per source system, so filenames mirror the model names (
stg_shopify__customers↔_shopify__models.yml) — the leading underscore sorts them to the top, and splitting sources/models keeps each file focused. Intermediate and marts have no source namespace, so a genericschema.ymlis simpler. You may rename them all toschema.ymlif preferred — dbt reads any.ymlundermodel-paths.
This project uses uv for dependency management, making environment creation nearly instantaneous.
- Python 3.10–3.12
- uv:
pip install uv(or standalone installer) - Postgres: a local Postgres instance (Docker or native)
uv syncThis reads pyproject.toml, resolves everything against the committed uv.lock, and creates .venv/.
cd dbt-postgres
# 1. Create your local env file from the template
cp .env.example .env
# 2. Edit .env with your actual database credentials
$EDITOR .env
# 3. Load the variables into your active shell (CRITICAL)
set -a; source .env; set +aprofiles.yml reads these env vars — dbt auto-discovers it when run from the project directory.
# Install packages.yml dependencies (dbt_utils, dbt_expectations)
uv run dbt deps
# Test the warehouse connection
uv run dbt debuguv run dbt buildThis runs seeds → snapshots → models → tests in dependency order.
uv run dbt docs generate
uv run dbt docs serveNavigates to http://localhost:8080.
Run uv sync from the dbt/ root to install Python deps, then cd dbt-postgres for everything else:
| Task | Command |
|---|---|
Install deps (from dbt/) |
uv sync |
| dbt packages | uv run dbt deps |
| Load seeds | uv run dbt seed |
| Run models | uv run dbt run |
| Run tests | uv run dbt test |
| Full pipeline (seed + run + test) | uv run dbt build |
| Unit tests only | uv run dbt test --select "test_type:unit" |
| Source freshness | uv run dbt source freshness |
| Generate + serve docs | uv run dbt docs generate && uv run dbt docs serve |
| Lint SQL | uv run sqlfluff lint . |
| Auto-format SQL | uv run sqlfluff fix . |
| Clean build artifacts | uv run dbt clean |
| Slim CI (modified + downstream) | uv run dbt build --select "state:modified+" --defer --state ./prod-manifest |
For slim CI, ./prod-manifest must be the manifest produced by the last successful production run — typically downloaded from S3 or a CI artifact store before the build.
Controlled entirely by your .env variables and the profiles.yml target:
| Target | Usage | Threads | Notes |
|---|---|---|---|
dev |
Local development (default) | 4 | SSL: prefer |
prod |
Production deployment | 8 | SSL: require |
ci |
CI/CD pipelines | 4 | Dynamically targets schema: ci_<pipeline_id> |
Select a non-default target with --target prod or --target ci.
If you are cloning this repository to build your own warehouse:
- Copy
dbt-postgres/(and the surroundingpyproject.toml/uv.lockif you want the same Python env) into your new codebase. - Rename
dbt-postgres/to match your actual project name. - Update
dbt_project.yml: changenameand update theprofilekey. - Update the matching profile name in
profiles.yml. - Inside
models/staging, deleteshopify/andstripe/and replace them with your actual source systems following the naming conventions above. - Run
uv run dbt buildfrom inside the project directory to verify.