← All Bucket Storages

SFTP

type: sftp

Any SSH-reachable server as a data endpoint — exchange folders, partner drop zones, legacy hosts. Files move through the same Sling lane as the cloud buckets.

PREREQUISITES

SDK: (none — built-in via Sling) — installed automatically by:

dvt sync

CONFIGURATION FIELDS

FIELDREQUIREDDEFAULTDESCRIPTION
typeyesMust be `sftp`
hostyesSFTP server hostname or IP
useryesSSH user
portno22SSH port
passwordnoPassword auth. Omit when using a private key
private_keynoPath to the SSH private key (or the inline PEM). One of password / private_key is required
passphrasenoPrivate key passphrase, when the key has one
pathnoBase directory on the server (e.g. /exports)
formatnoparquetDefault file format: csv, parquet, json, jsonl

PROFILES.YML EXAMPLE

my_project:
  target: prod_pg
  outputs:
    prod_pg:
      type: postgres
      host: db.internal
      user: dvt
      password: "{{ env_var('PG_PASSWORD') }}"
      dbname: analytics
      schema: public

    partner_drop:
      type: sftp
      host: files.partner.example
      user: exchange
      private_key: ~/.ssh/partner_ed25519
      path: /exports
      format: csv

SOURCES.YML EXAMPLE

sources:
  - name: partner_files
    meta:
      connection: partner_drop         # must match profiles.yml output name
    tables:
      - name: daily_positions          # /exports/daily_positions.csv
      - name: reference_rates

MODEL EXAMPLE — BUCKET AS SOURCE

Sling extracts files from the bucket into the DuckDB cache. Model SQL executes in DuckDB (Postgres-like dialect).

-- models/staging/stg_positions.sql
-- Read a partner's SFTP drop as a federated source
{{ config(materialized='f_table') }}

select
    position_id,
    account_code,
    cast(notional as decimal(18,2)) as notional
from {{ source('partner_files', 'daily_positions') }}

MODEL EXAMPLE — BUCKET AS TARGET

Model executes on the default target. Sling streams the result to the bucket in the specified format. Use config(format='...') to override the default format set in profiles.yml.

-- models/exports/positions_out.sql
-- Land results ON the SFTP server (bucket-style target)
{{ config(materialized='f_table', target='partner_drop', format='csv') }}

select account_code, sum(notional) as total_notional
from {{ ref('stg_positions') }}
group by account_code

FILE FORMAT CONFIGURATION

The output format is resolved in order of priority:

1config(format='delta') in model file— highest priority
2format: in profiles.yml output— project default
3parquet— built-in default
FORMATVALUENOTES
ParquetparquetColumnar, compressed. Best for analytics. Default.
Delta LakedeltaSchema evolution, time travel, ACID transactions.
CSVcsvUniversal compatibility. No schema enforcement.
JSONjsonNested structures, human-readable.
JSON LinesjsonlOne record per line. Streamable.
AvroavroSchema embedded, compact binary. Kafka-friendly.
IPC / ArrowipcArrow IPC format. Zero-copy reads.

INCREMENTAL EXTRACTION

Incremental models work with cloud storage sources. DVT resolves the watermark from the target database, extracts only changed rows, and merges them in the DuckDB cache.

-- models/exports/positions_incr.sql
{{ config(
    materialized='f_incremental',
    target='partner_drop',
    format='parquet',
    unique_key='position_id',
    watermark_column='updated_at'
) }}

select position_id, account_code, notional, updated_at
from {{ ref('stg_positions') }}

NOTES

  • No SQL engine: SFTP connections hold files, so they cannot be a default target, run hooks, or answer dvt exec — DVT refuses those honestly.
  • Key-based auth supports a key path or inline PEM (+ optional passphrase); agent-forwarding setups should use a key file.
  • Retraction is manual for now — dvt retract skips SFTP targets and tells you so.

DEBUGGING

Use dvt debug to test bucket connectivity. DVT verifies read/write access for each cloud storage connection.

dvt debug