QUICKSTART
1. INSTALL DVT
pip install dvt-core
Or with uv:
uv add dvt-core
Requires Python 3.9+ on macOS (x86_64, arm64) or Linux (x86_64).
2. LOG IN
DVT requires authentication. Run dvt login to open your browser and sign in with your getdvt.net account:
dvt login
This opens a browser window for authentication. Once complete, your credentials are stored at ~/.dvt/credentials.yml and all CLI commands are unlocked.
Check your auth status anytime with dvt status.
3. CREATE YOUR PROJECT — GREEN BUILD IN SECONDS
dvt init my_project cd my_project dvt build # ✓ no warehouse needed
dvt init scaffolds a complete project with a DuckDB default target out of the box: a profile entry is merged into ~/.dbt/profiles.yml(dbt's own file, never clobbered), pointing at a visible my_project.duckdb at the project root — that file IS your warehouse. A starter seed and model mean dvt build succeeds immediately, and because DuckDB is the default, everything you write is one SQL dialect — federated models included. Run it bare inside an existing folder and the folder itself becomes the project, named after it.
When you outgrow the laptop, dvt flip-target-to migrates your models to any engine's dialect — deterministically. Starting local is not a dead end; it's a ramp.
4. CONFIGURE CONNECTIONS — POINT AND CLICK
The fastest way is the GUI. Start the hub and add every connection from a form — no YAML, no indentation accidents:
dvt serve # opens the hub → Settings → CONNECTIONS
+ ADD CONNECTION lists every engine and bucket DVT supports; picking one renders its own pre-filled field set. CREATE lands it as a card, set the default target from the dropdown, and SYNC SOURCESbootstraps everything (that's step 5, as a button). Secrets are stored write-only and every save keeps a timestamped backup.
Prefer the terminal? The same connections are plain dbt-style YAML in ~/.dbt/profiles.yml — see the profiles.yml guide for full details:
# ~/.dbt/profiles.yml
my_project:
target: pg_dev # default target
outputs:
# Default target — all pushdown models run here
pg_dev:
type: postgres
host: localhost
port: 5432
user: analyst
password: "{{ env_var('PG_PASSWORD') }}"
dbname: analytics
schema: public
threads: 4
# External connection — MySQL operational database
mysql_ops:
type: mysql
host: mysql.internal.com
port: 3306
user: readonly
password: "{{ env_var('MYSQL_PASSWORD') }}"
database: operations # MySQL schema = database
# External connection — Snowflake warehouse
sf_warehouse:
type: snowflake
account: xy12345.us-east-1
user: DVT_USER
password: "{{ env_var('SF_PASSWORD') }}"
database: PROD_DB
schema: RAW
warehouse: COMPUTE_WH5. BOOTSTRAP YOUR ENVIRONMENT
If you clicked SYNC SOURCES in the hub, this already happened. From the terminal, run dvt sync to install all required database drivers, DuckDB extensions, cloud SDKs, and the Sling binary based on your profiles.yml:
dvt sync
Behind the scenes, sync keeps adapter environments tidy: your environment carries only the default target's dbt adapter, and every other adapter lives in its own isolated environment under .dvt/ — no version conflicts, ever. The only prerequisite is uv, and sync installs that for you too if it's missing.
Then verify all connections are reachable:
dvt debug
6. DEFINE SOURCES
Create a sources.yml file in your models directory to map external databases to source references. See the sources.yml guide for full details.
# models/staging/sources.yml
version: 2
sources:
- name: crm
meta:
connection: mysql_ops # external — points to profiles.yml
tables:
- name: customers
- name: orders
- name: warehouse
meta:
connection: sf_warehouse # external — points to profiles.yml
tables:
- name: invoices
- name: analytics
meta:
connection: pg_dev # on the default target — declared anyway
schema: public
tables:
- name: seed_categories7. WRITE MODELS
Create SQL models that reference your sources. DVT automatically detects which execution path to use based on where each source lives.
Pushdown model — all sources on the default target, runs as native PostgreSQL:
-- models/staging/stg_categories.sql
-- All sources on default target → adapter pushdown
-- SQL dialect: PostgreSQL
{{ config(materialized='table') }}
SELECT id, name, parent_id
FROM {{ source('analytics', 'seed_categories') }}Extraction model — cross-engine sources, runs in DuckDB. Declare it f_table (a plain table would be auto-coerced to federated with a warning — say what you mean):
-- models/marts/cross_engine_report.sql
-- Sources on different engines → extraction via Sling + DuckDB
-- SQL dialect: DuckDB (Postgres-like)
{{ config(materialized='f_table') }}
SELECT
c.customer_name,
c.email,
o.order_date,
o.total_amount
FROM {{ source('crm', 'customers') }} c -- MySQL
JOIN {{ source('warehouse', 'invoices') }} o -- Snowflake
ON c.customer_id = o.customer_id
WHERE o.order_date >= '2024-01-01'8. LOAD SEEDS, RUN, TEST
# Load CSV seed data (Sling-based, 10-100x faster than dbt) dvt seed # Run all models dvt run # Run tests dvt test # Or do everything in DAG order dvt build
9. GENERATE DOCUMENTATION
dvt docs generate dvt docs serve
Opens the DVT data catalog — a futuristic dark-themed UI with engine-colored lineage graphs, column metadata from all engines, and cross-engine source documentation.
PROJECT STRUCTURE
my_project/ ├── dbt_project.yml # Project configuration ├── models/ │ ├── staging/ # Source-aligned models + sources.yml │ ├── intermediate/ # Business logic │ └── marts/ # Final tables/views ├── seeds/ # CSV files ├── tests/ # Custom tests ├── snapshots/ # SCD2 snapshots ├── macros/ # Reusable SQL └── target/ # Build artifacts (gitignored)