QUICKSTART
DVT lets you write one SQL model that joins tables living in different databases — Postgres to Oracle to Snowflake — as if they shared an engine. This page takes you there from nothing, and every step is copy-pasteable.
THE FIVE-MINUTE VERSION
pip install dvt-core # install dvt login # sign in dvt init my_project # scaffold a project cd my_project dvt build # ✓ green build — no warehouse needed
That's already a real, working project, running on a local DuckDB file that acts as your warehouse. The rest of this page swaps in your actual databases and ends with a model that joins two engines in one query.
1. INSTALL DVT
pip install dvt-core
Or with uv:
uv add dvt-core
DVT runs on Python 3.10–3.14, on macOS (Intel and Apple Silicon) and Linux x86_64. There is no native Windows wheel — Windows users run DVT under WSL2, and the install & supported platforms page walks through exactly how.
2. LOG IN
DVT asks you to sign in once, with your getdvt.net account:
dvt login
This opens your browser to authenticate. Once you're through, your credentials live at ~/.dvt/credentials.yml and every CLI command is unlocked. Check where you stand anytime with dvt status — the auth commands page has the full details.
3. CREATE YOUR PROJECT
dvt init my_project cd my_project dvt build # ✓ no warehouse needed
dvt init gives you 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 come along too, so dvt build succeeds immediately: dbt's familiar run output, ending green, before you've configured a single connection. And because DuckDB is the default, everything you write is one SQL dialect — federated models included.
Two things worth knowing. Run dvt init bare inside an existing folder and the folder itself becomes the project, named after it. And when you outgrow the laptop, dvt flip-target-to migrates your models to any engine's dialect — deterministically. Starting local isn't a dead end; it's a ramp.
4. ADD YOUR CONNECTIONS
The fastest way is point and click. 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, and 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. The Settings app page has the full tour.
Prefer the terminal? The same connections are plain dbt-style YAML in ~/.dbt/profiles.yml — the profiles.yml reference covers every field:
# ~/.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 — Oracle operational database
oracle_ops:
type: oracle
host: oracle.internal.com
port: 1521
user: readonly
password: "{{ env_var('ORACLE_PASSWORD') }}"
database: operations # Oracle 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. SYNC YOUR ENVIRONMENT
If you clicked SYNC SOURCES in the hub, this already happened. From the terminal, one command reads your profiles.yml and installs everything it implies — database drivers, DuckDB extensions, cloud SDKs, and the Sling binary:
dvt sync
Behind the scenes, dvt sync keeps things tidy: your environment carries only the default target's adapter, and every other adapter lives in its own isolated environment under .dvt/— so version conflicts simply can't happen. The only prerequisite is uv, and sync installs that for you too if it's missing.
Then confirm every connection is reachable:
dvt debug
6. DECLARE YOUR SOURCES
sources.ymltells DVT what lives where. It's standard dbt syntax with one addition: meta.connection names the profiles.yml connection each source sits on. The sources.yml reference has the full picture.
# models/staging/sources.yml
version: 2
sources:
- name: crm
meta:
connection: oracle_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 YOUR MODELS
Now the payoff. You write plain SQL; DVT looks at where each source lives and picks the right execution path on its own.
Pushdown model — every source sits on the default target, so it 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') }}Cross-engine model — sources on different engines, so it runs through DuckDB. Declare it f_table: a plain tablewould be auto-coerced to federated with a warning, and it's better to 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 -- Oracle
JOIN {{ source('warehouse', 'invoices') }} o -- Snowflake
ON c.customer_id = o.customer_id
WHERE o.order_date >= '2024-01-01'That JOIN — Oracle to Snowflake, one query — is the whole point.
8. SEED, 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
Seeds and tests each have a page of their own — including the Sling fast path behind dvt seed and how tests behave on cross-engine sources.
9. BROWSE YOUR DATA
dvt docs generate dvt docs serve
This opens the DVT data catalog — a dark-themed UI with engine-colored lineage graphs, column metadata from all your engines, and cross-engine source documentation. See dvt docs and the catalog page for what's inside.
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)
When you're ready to go deeper, every piece has its own page: dbt_project.yml, snapshots, macros and more.