PROFILES.YML
profiles.yml is where your connections live: every database, warehouse, and bucket DVT can talk to, each with its credentials, grouped as named outputsunder one profile. It's the same file dbt uses, in the same place — and in DVT it quietly becomes something more.
Your project's code never holds credentials. dbt_project.yml names a profile; the profile holds the connections.
A WORKING PROFILES.YML
Here is a complete, real profile with two outputs — PostgreSQL as the default target and a local DuckDB file alongside it:
# ~/.dbt/profiles.yml
my_project: # ← the profile name
target: pg_dev # ← which output models build on by default
outputs:
pg_dev: # PostgreSQL — the default target
type: postgres
host: localhost
port: 5432
user: analytics
password: "{{ env_var('PG_PASSWORD') }}"
dbname: analytics_db
schema: public
threads: 4
duckdb_local: # DuckDB — a second live connection
type: duckdb
path: ./analytics.duckdb
threads: 4The project finds this profile through one line in dbt_project.yml:
# dbt_project.yml name: my_project profile: my_project # ← must match the top-level key in profiles.yml
Then verify the whole thing before running anything:
dvt debug # test the default target — dbt layer AND Sling layer dvt debug --all # test every output in the profile, both layers dvt debug --target duckdb_local # test one specific output
dvt debug goes further than dbt debug: it runs dbt's own connectivity check and tests the same output through the extraction layer, so you know up front that federation can reach it too.
WHERE DVT LOOKS FOR IT
DVT resolves profiles.yml exactly the way dbt does — deliberately. Your existing dbt profile works untouched, and both tools always read the same file. The first location that actually contains a profiles.yml wins:
--profiles-dir <dir>— the CLI flag, when you pass one$DBT_PROFILES_DIR— the standard dbt environment variable — handy in CI./profiles.yml— a project-local file, right next to dbt_project.yml~/.dbt/profiles.yml— the canonical home, shared with dbt
A project-local profiles.yml beats ~/.dbt — useful for a repo that carries its own connection layout — and DBT_PROFILES_DIR overrides both. One thing that never happens: profiles in ~/.dvt. That directory holds DVT's machine state; connections stay in dbt's home, on purpose.
ANATOMY: ONE PROFILE, A DEFAULT TARGET, MANY OUTPUTS
Three layers, top to bottom:
- ▸Profile name — the top-level key. It must match profile: in dbt_project.yml. One file can hold several profiles — one per project, or one shared.
- ▸target: — names the default output. Plain models (table / view / incremental) build here; federated models land here unless they pin another output. Override per run with dvt run --target <name>.
- ▸outputs: — the connections themselves. Each key is an output name you'll refer to elsewhere — from sources.yml, from config(target=...), from dvt debug --target.
Every output needs a type(which engine it is) plus that engine's own connection fields — host and credentials for a server, a file path for DuckDB, a bucket for S3. Those fields are documented per engine, not here; see the links below.
EVERY OUTPUT IS A LIVE CONNECTION
This is the part that changes how you write the file. In dbt, extra outputs are usually just environments — dev, staging, prod — and you use one at a time. DVT reads the same list differently: every output is a connection the project can use, all in the same run.
In plain dbt, an output is just where models build — you pick one with --target and the rest sit idle. In DVT, every output is a live connection models can read from and write to: sources bind to any output via meta.connection, one federated model can read several outputs at once, and target= lands its result on whichever output you pin.
With the two-output profile above, that looks like this. First, a source bound to the DuckDB output:
# models/staging/sources.yml
version: 2
sources:
- name: local_lake
meta:
connection: duckdb_local # ← binds this source to the duckdb_local output
tables:
- name: raw_eventsThen a federated model that reads it — and lands on pg_dev, the default target, with no destination config at all:
-- models/marts/events_by_user.sql
{{ config(materialized='f_table') }}
select user_id, count(*) as events
from {{ source('local_lake', 'raw_events') }} -- read live from DuckDB
group by user_id -- result lands on pg_devTwo engines, one model, zero glue code — and both connections came straight from the profile you already had. To land the result somewhere other than the default target, pin it with target=; the federated materializations page covers that in full.
SECRETS STAY OUT: ENV_VAR()
Never paste a password into the file. {{ env_var('NAME') }} works in profiles.yml exactly as it does in dbt — the value is read from the environment when the profile is loaded:
pg_dev:
type: postgres
host: "{{ env_var('PG_HOST', 'localhost') }}" # second argument = default
user: analytics
password: "{{ env_var('PG_PASSWORD') }}" # no default: must be set
dbname: analytics_dbQuote the whole expression — YAML needs the braces inside a string. With a default, the variable is optional; without one, loading fails loudly if it's unset, which is what you want for a credential. Pair it with DBT_PROFILES_DIR and CI needs nothing but environment variables.
THREADS
Each output can set threads(default 4). It does double duty: in the dbt lane it's dbt's model concurrency — how many models run in parallel — and in the federation lane it also drives extraction parallelism, how many source extractions run at once. There's no separate knob and no environment variable; the profile is the one place parallelism is set.
ENGINE-SPECIFIC FIELDS
Beyond type and threads, every engine has its own field shape — host/dbname for PostgreSQL, account/warehouse for Snowflake, a plain path for DuckDB, bucketand credentials for S3. This page deliberately doesn't enumerate them. Each engine's docs page has the full field table, a copy-paste profile example, and its quirks:
REFERENCE
| KEY | TYPE | DEFAULT | WHAT IT IS |
|---|---|---|---|
| <profile-name> | mapping | required | Top-level key. Must match profile: in dbt_project.yml. One file can hold several profiles. |
| target | string | required | Name of the default output. Plain models build here; federated models land here unless target= pins another output. Overridable per run with --target. |
| outputs | mapping | required | The named connections. Each key is an output name — what sources.yml meta.connection and config(target=...) refer to. |
| outputs.<name>.type | string | required | Which engine this output is (postgres, duckdb, snowflake, s3, ...). Each engine's docs page states the exact spelling. |
| outputs.<name>.threads | integer | 4 | Parallelism: model concurrency in the dbt lane, extraction concurrency in federation. |
| outputs.<name>.schema | string | engine-dependent | Default schema/dataset for relations on this output (e.g. public on PostgreSQL, main on DuckDB). |
| outputs.<name>.* | varies | — | Engine-specific connection fields (host, password, account, warehouse, path, bucket, ...) — see that engine's page. |