dbt

Build your Git-based dbt project against the Contrast Warehouse, on a Contrast schedule or your own runner.

Keep your dbt project and Git repository. The dbt-contrast adapter sends SQL to Contrast's public API. Contrast runs the queries and manages storage and catalog updates. You do not need AWS credentials or physical catalog names.

Install the adapter from PyPI. Access to the private product repository is not required.

Schedule a GitHub project in Contrast

  1. Connect and sync the repository in Sources.
  2. Open Pipelines → Connect dbt and choose the repository. The branch, database and folder containing dbt_project.yml are filled in; change them, or the models to run (a dbt selector such as +orders or tag:daily), under More options. Add the pipeline.
  3. Choose one of your read/write dbt API keys and Run once, or add a schedule. With no key yet, an owner or admin can create one there with Create a key.

The same Pipelines page provides run history, model and test graphs, logs, failure alerts, pause, resume and ownership transfer. Each occurrence resolves the branch to an immutable commit; retries reuse that commit. Runs use the chosen key's workspace and database permissions, without AWS credentials in dbt. Revoking or expiring the key stops further runs.

Before building, each run runs dbt source freshness for the sources the selected models read that declare freshness (with a loaded_at_field). A source past error_after stops the run before anything is built, and the run's error names the source, its age and the limit. A source past warn_after, or one whose freshness could not be checked, adds a warning to the run's log and the build continues. Sources without freshness are not checked.

Hosted projects use the standard dbt resource folders and SQL models and tests. Custom macros, packages, Python models, hooks and destination overrides are not supported by this hosted runner. Use your own runner for projects needing those features. A failed build or test may leave models already built updated; dbt does not make the whole project atomic.

Supported versions and scope

Use dbt Core 1.11 and Python 3.12 for the quickstart. The adapter supports Python 3.10 through 3.13. dbt Cloud and Fusion are not supported by this release.

Supported operations include Iceberg SQL tables, ephemeral models, CSV seeds, data tests, docs generation, and incremental append and merge. Existing ref, source, Jinja, and packages still work subject to supported SQL and adapter macros. SQL or macros specific to another warehouse may need changes.

Persisted models must use materialized='table' or materialized='incremental'. Views are unsupported. Use ephemeral for models compiled into their consumers. If you already have Athena views, convert or relocate them through their original engine before reusing their names or refreshing the warehouse catalog.

1. Prepare the warehouse and credentials

Create your Contrast Warehouse and output databases first, for example analytics_dev and analytics_prod. Writes use the default schema; dbt does not provision databases or custom output schemas.

Each successful build records lineage from the tables a model reads to the table it writes. To see it, ask an agent connected to the Contrast MCP for a table's upstream or downstream tables (catalog action lineage).

API keys

dbt signs in with an API key. A workspace owner or admin creates one in MCP → API access.

  • Access: reads and writes the whole workspace, with no admin access. It stops working if its creator loses editor access.
  • Expiry: 30 to 365 days. The adapter doesn't refresh keys, so replace one before it expires.
  • Storage: the key is shown once. Save it in your secret store as DBT_ENV_SECRET_CONTRAST_TOKEN, and the workspace ID as CONTRAST_WORKSPACE_ID. Never commit the key or print it in logs.
  • Limit writes: for dev runners and coding agents, create a write key in MCP → API access, open Limit warehouse writes and list the databases it may write to.
  • Revoke: in MCP → API access. Runs using a revoked key stop.

API keys are on by default. If an admin turned them off, an owner or admin can turn them back on in Admin settings → Sign-in, or from Pipelines when choosing a key.

2. Point your existing project at Contrast

Install into your project's virtual environment:

python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install 'dbt-contrast==0.1.0'

Keep your existing dependency lock process for reproducible production installs. Set profile: contrast in your existing dbt_project.yml, then save this secret-free profile as .dbt/profiles.yml in the project:

contrast:
  target: dev
  outputs:
    dev: &contrast
      type: contrast
      endpoint: https://api.embrasure.ai
      workspace_id: "{{ env_var('CONTRAST_WORKSPACE_ID') }}"
      token: "{{ env_var('DBT_ENV_SECRET_CONTRAST_TOKEN') }}"
      database: analytics_dev
      schema: default
      threads: 4
      query_timeout: 1800
    prod:
      <<: *contrast
      database: analytics_prod

Inject the two environment variables through your secret manager, then run:

dbt deps
dbt debug --profiles-dir .dbt --target dev
dbt build --profiles-dir .dbt --target dev
dbt docs generate --profiles-dir .dbt --target dev

Use logical names from Contrast's catalog for sources:

version: 2
sources:
  - name: crm
    database: raw
    schema: salesforce
    tables:
      - name: account

{{ source('crm', 'account') }} reads raw.salesforce.account. A model named accounts writes analytics_dev.default.accounts in dev and analytics_prod.default.accounts in prod. Ingestion destinations and outputs owned by managed Contrast pipelines cannot be overwritten by external dbt.

3. Schedule a GitHub Actions job

In your dbt repository, create a GitHub environment called dbt-production. Add the token as an environment secret named DBT_ENV_SECRET_CONTRAST_TOKEN and the workspace UUID as an environment variable named CONTRAST_WORKSPACE_ID. Restrict that environment to your trusted main branch. Required environment reviewers will make scheduled jobs wait for approval, so choose that policy deliberately.

With your dbt project at the repository root, save this as .github/workflows/dbt.yml. Adjust the branch, cron, and paths for your project.

name: Build Contrast models
on:
  workflow_dispatch:
  schedule:
    - cron: '17 6 * * *' # Daily at 06:17 UTC; GitHub may delay scheduled jobs.

permissions:
  contents: read

concurrency:
  group: contrast-analytics-prod
  cancel-in-progress: false

jobs:
  dbt:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    timeout-minutes: 60
    environment: dbt-production
    steps:
      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
        with:
          persist-credentials: false
      - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
        with:
          python-version: '3.12'
      - run: python -m pip install 'dbt-contrast==0.1.0'
      - run: dbt deps
      - name: Build production models
        env:
          CONTRAST_WORKSPACE_ID: ${{ vars.CONTRAST_WORKSPACE_ID }}
          DBT_ENV_SECRET_CONTRAST_TOKEN: ${{ secrets.DBT_ENV_SECRET_CONTRAST_TOKEN }}
        run: |
          dbt debug --profiles-dir .dbt --target prod
          dbt build --profiles-dir .dbt --target prod

GitHub concurrency groups only coordinate jobs in the same repository. Use the same group for every workflow writing these outputs, and coordinate other repositories or schedulers separately. Do not run concurrent full builds against the same destination tables. This example does not run untrusted pull request code with production credentials or upload dbt artifacts automatically; compiled SQL and artifacts can contain sensitive information.

Your runner owns scheduling, Git checkout, job dependencies, overlap prevention, notifications, and retry decisions. Contrast owns each query's authorization, execution, limits, history, and catalog reconciliation.

Rotate credentials without breaking jobs

  1. Before expiration, create a replacement PAT in the same workspace with the same minimal scopes. Record its expiration in your team's normal reminder system.
  2. Update the runner secret. Test dbt debug with the replacement and run a small build in a separate dev database before the next production job.
  3. Allow jobs using the old token to finish, then revoke it in MCP → API access. If it was exposed, revoke it immediately and investigate instead of waiting.

A 401 can mean an invalid, expired, or revoked token. A 403 can mean disabled PATs, insufficient scope, or changed workspace membership. Check these before retrying. Do not print the secret while diagnosing authentication failures.

Failure handling and initial limits

  • Tables and seeds rebuild with drop/create, not an atomic swap. A failed rebuild can leave a missing or partially populated destination.
  • Incremental append is the default; merge requires unique_key. Use --full-refresh for schema changes. Automatic schema evolution and snapshots are not supported.
  • Custom output schemas, renames, Python models, enforced contracts, SQL grants, persist_docs, and multi-statement transactions are not supported. Hooks autocommit.
  • query_timeout defaults to 1,800 seconds, with a server maximum of 3,600 per query. The existing scan limit still applies. A job timeout is separate from a query timeout; stopping a runner does not guarantee an immediate server cancellation.
  • After a lost submission response, inspect Contrast query history before retrying, especially an append. The adapter deliberately does not replay writes automatically, because the first submission may already have succeeded.

Use a dedicated dev database to verify an existing project before moving its production schedule to Contrast.