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_WH

5. 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_categories

7. 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)