Your First dbt Project
Getting dbt running for the first time takes about 30 minutes if you know what youโre doing. This guide skips the theory and focuses on the concrete steps: install, configure a profile, write your first model, and see it run.
Prerequisites
Before starting, you need:
- Python 3.9+ โ dbt is a Python package
- A running database โ PostgreSQL, Snowflake, BigQuery, Redshift, DuckDB, or another supported adapter
- Database credentials ready to hand
This walkthrough uses PostgreSQL for the examples, but the pattern is identical for other warehouses โ only the profile configuration changes.
Step 1: Install dbt
Create a virtual environment first. This keeps dbtโs dependencies separate from your system Python.
python -m venv dbt-envsource dbt-env/bin/activate # Windows: dbt-env\Scripts\activate
pip install dbt-postgres # install core + postgres adapterdbt --version # confirm the install workedFor other databases, replace dbt-postgres with the right adapter:
- Snowflake:
dbt-snowflake - BigQuery:
dbt-bigquery - DuckDB:
dbt-duckdb - Redshift:
dbt-redshift
Step 2: Initialize the Project
dbt init my_first_projectdbt will ask you to select an adapter (matching what you installed) and then create a project directory with this structure:
my_first_project/โโโ dbt_project.yml # project configurationโโโ models/โ โโโ example/ # sample models (you can delete these)โโโ tests/โโโ macros/โโโ seeds/โโโ analyses/Move into the project directory:
cd my_first_projectStep 3: Configure Your Profile
dbt connects to your database using a profiles.yml file stored at ~/.dbt/profiles.yml (not inside the project directory). This keeps credentials out of your project repository.
Create or edit ~/.dbt/profiles.yml:
my_first_project: target: dev outputs: dev: type: postgres host: localhost user: your_username password: your_password port: 5432 dbname: your_database schema: dbt_dev threads: 4A few things to note:
- The top-level key (
my_first_project) must match theprofilesetting indbt_project.yml schemais where dbt will create tables and views in your databasethreadscontrols how many models run in parallel
Never commit passwords to source control. For production, use environment variables:
password: "{{ env_var('DBT_DB_PASSWORD') }}"Test the connection:
dbt debugAll checks should show as OK. If you see connection errors, double-check your host, port, and credentials.
Step 4: Write Your First Model
Delete the sample models dbt created, then make your own. Create models/customers.sql:
-- models/customers.sqlWITH source AS ( SELECT * FROM {{ source('raw', 'customers') }}),
cleaned AS ( SELECT id AS customer_id, LOWER(email) AS email, first_name || ' ' || last_name AS full_name, created_at FROM source WHERE email IS NOT NULL)
SELECT * FROM cleanedThe {{ source() }} function references a raw table in your database. You define sources in a YAML file so dbt tracks their lineage:
Create models/sources.yml:
version: 2
sources: - name: raw schema: public # the schema where your raw tables live tables: - name: customers - name: ordersNow create a downstream model that references the first one. Create models/customer_orders.sql:
-- models/customer_orders.sqlWITH customers AS ( SELECT * FROM {{ ref('customers') }}),
orders AS ( SELECT * FROM {{ source('raw', 'orders') }}),
final AS ( SELECT c.customer_id, c.full_name, c.email, COUNT(o.id) AS total_orders, SUM(o.amount) AS total_spent FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY 1, 2, 3)
SELECT * FROM finalStep 5: Add Tests
dbt ships with four built-in tests you can add in a schema YAML file. Create models/schema.yml:
version: 2
models: - name: customers columns: - name: customer_id tests: - unique - not_null - name: email tests: - not_null
- name: customer_orders columns: - name: customer_id tests: - not_null - relationships: to: ref('customers') field: customer_idThe relationships test checks referential integrity โ every customer_id in customer_orders must exist in customers. This is the kind of data quality check that saves you from debugging downstream BI issues.
Step 6: Run Everything
Build models:
dbt runYou should see output like:
Running with dbt=1.8.xFound 2 models, 5 tests, 1 source
1 of 2 START sql view model dbt_dev.customers ........... [RUN] 1 of 2 OK created sql view model dbt_dev.customers ....... [OK in 0.12s] 2 of 2 START sql view model dbt_dev.customer_orders ...... [RUN] 2 of 2 OK created sql view model dbt_dev.customer_orders .. [OK in 0.08s]
Finished running 2 view models in 0.31s.PASS=2 WARN=0 ERROR=0 SKIP=0 TOTAL=2Run tests:
dbt testRun models and tests together (recommended):
dbt builddbt build runs models and their tests in dependency order, stopping immediately if a test fails. This is the command youโll use in CI.
Step 7: Generate and View Documentation
dbt docs generatedbt docs serveThis opens a browser with an auto-generated documentation site. It includes:
- A description of every model and column (from your YAML files)
- The full lineage graph showing how models depend on each other
- Source freshness information if youโve configured it
Controlling Materialization
By default, dbt creates views. You can change this per model in dbt_project.yml:
models: my_first_project: +materialized: view # default for the whole project
marts: +materialized: table # tables for anything in the marts/ folderOr in the model file itself:
{{ config(materialized='table') }}
SELECT ...Use views for staging models (cheap, always fresh), tables for heavy transformations you donโt want re-executed on every query, and incremental for large datasets where rebuilding from scratch is too expensive.
Project Structure That Scales
Once you move beyond two models, organize your project into layers:
models/โโโ staging/ โ one model per source table, minimal transformationโ โโโ stg_customers.sqlโ โโโ stg_orders.sqlโโโ intermediate/ โ joins and business logic, not exposed to BI directlyโ โโโ int_customer_orders.sqlโโโ marts/ โ final tables consumed by BI tools and stakeholders โโโ dim_customers.sql โโโ fct_orders.sqlThis layering โ staging, intermediate, marts โ is the most common pattern on teams that use dbt seriously. It keeps individual models focused, limits blast radius when source schemas change, and makes lineage easy to follow.
What to Learn Next
- Incremental models: process only new rows since the last run instead of rebuilding full tables
- Seeds: load static CSV data into your warehouse as dbt manages tables
- Snapshots: track how slowly-changing dimension values change over time
- dbt packages:
dbt-utilsadds 30+ helper macros youโll reach for constantly - Exposures: document BI dashboards and reports that consume your dbt models
You now have the foundation. The best way to learn the rest is to pick a real dataset and build models that answer questions you actually care about.