Use dbt Packages
rocky package brings a dbt Hub package into your Rocky project as plain Rocky models. dbt runs once, when you add or update the package, to compile it. After that, Rocky owns the models. It type-checks them, tracks their lineage, selects them, runs them and tests them. You do not need dbt to compile, run or test the project.
This page walks through the fivetran/stripe package on DuckDB. The same steps work for other Fivetran packages, such as fivetran/hubspot and fivetran/salesforce.
If you prefer to keep a package in dbt and only read its tables, see Using Rocky with dbt Packages.
How it works
Section titled “How it works”rocky package add fivetran/stripe [--build-empty] │ ▼ throwaway dbt project in a temp directory dbt deps ─▶ [dbt run --empty] ─▶ dbt compile --full-refresh │ only with │ │ --build-empty ▼ │ target/manifest.json ▼models/packages/stripe/*.sql + *.toml (plain SQL, no Jinja)rocky-packages.lock (versions + file hashes) │ ▼rocky compile ─▶ rocky run ─▶ rocky test --declarativedbt depsdownloads the package and its dependencies from the dbt Hub.dbt run --emptyruns only when you pass--build-empty. It builds every package model with zero rows in a scratch schema,rocky_package_build(androcky_package_build_<custom schema>).dbt compile --full-refreshwrites the final SQL. Rocky imports the models of the package you asked for, plus any model the package reads from another package.
Why some packages need --build-empty
Section titled “Why some packages need --build-empty”Many package macros read the columns of an upstream relation while they compile. Fivetran staging models and dbt_utils.star do this. When the upstream does not exist yet, the macro sees no columns. Fivetran’s macro then turns every column into cast(null as ...). That SQL runs and loads nothing but NULLs.
Rocky never vendors that SQL. It looks for a model that selects only NULL columns from a table, or the dbt_utils.star placeholder *. Without --build-empty, such a model refuses the whole package with E055, and nothing is written. The message offers two ways out:
| Option | What happens |
|---|---|
--build-empty |
dbt runs dbt run --empty first. This writes empty rocky_package_build* schemas to the warehouse with your adapter’s credentials, and runs the package’s on-run-start / on-run-end hooks. Rocky never builds there; you can drop the schemas. |
--compiled <dir> |
Compile the package elsewhere, for example against a scratch warehouse, and import the result. Nothing runs against your warehouse. |
fivetran/stripe needs one of them. Rocky records the mode in rocky-packages.lock as compile-only, build-empty or compiled, and rocky package update reuses it.
Before you start
Section titled “Before you start”You need:
- dbt-core 1.8 or later and the dbt adapter for your warehouse, on
PATH. For DuckDB:uv tool install dbt-core --with dbt-duckdb, orpip install dbt-core dbt-duckdb. - The package’s source tables in the warehouse that
rocky.tomlpoints at. dbt reads their columns at compile time. - A transformation pipeline whose
modelssetting includesmodels/packages/, for examplemodels = "models/**".
rocky package generates a dbt profile from the [adapter] block. It supports duckdb, snowflake, databricks, bigquery and postgres. Secrets go to dbt through environment variables, never through a file. For another adapter, compile the package yourself and pass --compiled <dbt project dir>.
Walkthrough: Fivetran Stripe on DuckDB
Section titled “Walkthrough: Fivetran Stripe on DuckDB”1. Point Rocky at the warehouse
Section titled “1. Point Rocky at the warehouse”rocky.toml
[adapter]type = "duckdb"path = "dev.duckdb"
[pipeline.analytics]type = "transformation"models = "models/**"
[pipeline.analytics.target.governance]auto_create_schemas = trueFivetran writes the Stripe connector tables to the stripe schema: stripe.charge, stripe.customer, stripe.balance_transaction, and so on.
2. Add the package
Section titled “2. Add the package”rocky package add "fivetran/stripe@>=1.0.0,<2.0.0" --build-emptyWithout --build-empty, this package is refused with E055. See Why some packages need --build-empty.
The argument is <namespace>/<name>, then optionally @ and a version requirement. A requirement is one version (1.10.1) or a comma-separated range. Without one, dbt takes the latest version.
Rocky writes 65 models to models/packages/stripe/ and creates rocky-packages.lock:
fivetran/stripe 1.10.1 (stripe) → models/packages/stripe/ 65 models (65 added, 0 removed), 23 sources, 15 tests mapped, 0 tests dropped files: 130 written, 0 unchanged, 0 deleted, 0 kept (edited), 0 .incomingIf your connector writes to another schema, pass the package’s own variable:
rocky package add fivetran/stripe --build-empty --vars stripe_schema=raw_stripe--vars takes key=value and can repeat. Rocky reads the value as YAML, so false, 5 and [a, b] keep their types. The lockfile records the vars, and rocky package update uses them again.
3. Compile and run
Section titled “3. Compile and run”rocky compilerocky run --dagrocky test --declarativerocky compile type-checks the package models like any other model. rocky run --dag builds them in dependency order. rocky test --declarative runs the package’s not_null tests, which Rocky mapped to [[tests]] in each sidecar.
What Rocky writes
Section titled “What Rocky writes”Each package model is a .sql file and a .toml sidecar, like a model you write by hand.
models/packages/stripe/stripe__customer_overview.sql (start)
-- Vendored by `rocky package` from a dbt package. Edit freely: `rocky package update`-- keeps edited files and writes the new upstream version beside them as `.incoming`.with balance_transaction_joined as ( select * from stripe__balance_transactions), ...- Model names stay as the package names them. References between package models are bare names, so Rocky sees them as DAG edges.
- Every package model builds into one schema,
mainon DuckDB. Rocky resolves a bare model name through the connection’s current schema, so a model in another schema would be out of reach. Change it with--target-schema. dbt’s per-folder schemas (stg_stripe,stripe) are not kept. - Source tables stay fully qualified, as dbt resolved them:
"dev"."stripe"."charge". Each model’s sidecar lists them as[[sources]]. - Tests: dbt’s
not_null,unique,accepted_valuesandrelationshipstests become[[tests]], with theirseverityandwhere. Anaccepted_valuestest with numeric values becomes anexpressiontest (col IS NULL OR col IN (1, 2)), so the column is not compared with strings. Awherefilter must be one boolean expression that Rocky’s filter rules accept; otherwise the test is dropped. Rocky reports any dropped test (W055). - Materializations:
tablebecomesfull_refresh,viewstays a view, andephemeralstays ephemeral. Anincrementalmodel goes through the same conversion asrocky import-dbt. Rocky reports one that does not stay incremental (W055).
rocky-packages.lock records, for each package: the Hub name, the version requirement, the resolved version, the dbt version, the adapter, the target schema, the vars and their hash, the build mode, the compile time, the source tables, and a hash of every file Rocky wrote. Commit it with the models.
Extend a package
Section titled “Extend a package”To build on a package, write a model that reads a package model by its bare name:
models/marts/customer_revenue.sql
SELECT customer_id, total_sales, total_refunds, total_sales + total_refunds AS net_salesFROM stripe__customer_overviewWHERE customer_id <> 'No Customer ID'models/marts/customer_revenue.toml
[target]catalog = "dev"schema = "main"stripe__customer_overview is a model in the project, so the read is a DAG edge. rocky run --dag builds customer_revenue after it, and rocky compile checks its columns. A misspelled column fails with E039. This is the pattern to use for most changes.
Override a package model
Section titled “Override a package model”To change a package model, edit its vendored file. rocky package update never overwrites a file you edited.
For example, a type mismatch in your connector data can break one package model. Fix the SQL in models/packages/stripe/<model>.sql. Then rocky run uses your version.
Update a package
Section titled “Update a package”rocky package update # every packagerocky package update stripe # one packageupdate recompiles the package within its version requirement, with the vars and the build mode in the lockfile. --vars adds or replaces vars. --build-empty or --build-empty=false changes the mode. A package added with --compiled needs --compiled <dir> again, or --build-empty. Then Rocky compares three versions of each file: what it wrote last time (the hash in the lockfile), the file on disk, and the new upstream file.
| Your file | Upstream | Result |
|---|---|---|
| Not edited | Changed or new | Written in place |
| Not edited | Removed | Deleted |
| Edited | Not changed | Kept |
| Edited | Changed | Kept. The new version is written to <file>.incoming (W055) |
| Edited | Removed | Kept (W055) |
| Deleted | Any | Written again |
If a model Rocky vendored before no longer imports, update refuses (E055) and writes nothing, instead of deleting that model’s files. rocky package update with no name updates packages in name order and stops at the first failure; the message names the packages it already updated.
Merge an .incoming file by hand, then delete it. Rocky does not load .incoming files as models. The JSON output lists the models the update added and removed.
List and remove
Section titled “List and remove”rocky package listrocky package remove stripelist shows each package with its version, its models, and any files you edited, deleted, or have an .incoming copy for. remove deletes the package’s files and its lock entry. It refuses (E055) when you edited a vendored file. Pass --force to delete those too.
Model names
Section titled “Model names”Package models keep their dbt names. Rocky does not prefix or rename them, because your models read them by bare name. When a package model has the name of a project model or of another package’s model, add and update refuse with E055 and write nothing. Rename or remove the existing model, then try again.
Rocky also refuses a package model that writes the same table as another model: the same [target] catalog.schema.table, ignoring case. That covers a project model with another name, and two models of the package itself.
Rocky compares the name each model resolves to: the name = in its sidecar or frontmatter, else its file name. Names that differ only by case collide too, because warehouses fold unquoted names and some file systems ignore case. A package with two such models of its own is refused as well.
What Rocky refuses
Section titled “What Rocky refuses”Before it writes anything, add and update check that the vendored models make a project rocky compile accepts. Each problem below refuses the package with E055:
- A package model reads a model that was not vendored, for example one that failed to import.
- A package model reads a dbt seed. Rocky does not vendor seeds.
- A package model’s SQL does not parse the way
rocky compileparses it.
A model that fails to import but that nothing reads is skipped with W055, and the rest are vendored.
How Rocky runs dbt
Section titled “How Rocky runs dbt”The package’s macros are third-party code, and dbt runs them. Rocky limits what they can reach:
- Environment. dbt starts with an empty environment plus an allowlist:
PATH,HOME, user and locale variables, temp directories, TLS and proxy settings,GOOGLE_APPLICATION_CREDENTIALS,CLOUDSDK_CONFIGandDBT_PACKAGE_HUB_URL. The credentials the generated profile needs are passed asDBT_ENV_SECRET_ROCKY_*variables, which dbt scrubs from its logs. - Vars. dbt renders vars through Jinja. Rocky refuses
{{,{%and{#in a var, from--varsor from the lockfile. A var whose name looks like a credential (api_key,stripe_token,db_password) is refused unless you pass--allow-secret-var, because vars are stored in clear text inrocky-packages.lock. - Timeout. Each dbt step stops after 30 minutes. Change it with
--dbt-timeout <seconds>. - Files. Rocky writes each vendored file to a temp file and renames it. It refuses to write or delete through a symlink under
models/packages/. - Paths. A relative
--dbt, Snowflakeprivate_key_pathor BigQuerykeyfileis resolved against the directory you ranrockyin.
Without dbt on the machine
Section titled “Without dbt on the machine”In CI, or with no network, compile the package where dbt is available and import the result:
rocky package add fivetran/stripe --compiled path/to/dbt-project--compiled reads <dir>/target/manifest.json and <dir>/package-lock.yml. Compile that project with dbt run --empty --full-refresh and then dbt compile --full-refresh. Use a warehouse with the same source table names. Rocky applies the same NULL-column check, so a project compiled without dbt run --empty is refused.
To use a private dbt Hub mirror, set DBT_PACKAGE_HUB_URL. Rocky passes it through to dbt.
Limits
Section titled “Limits”- The compiled SQL is fixed at add time. dbt resolved its macros against your warehouse and vars on that day. Run
rocky package updatewhen the connector adds columns, or when you change vars. - One schema. All package models build into
--target-schema. - Introspection is detected by its output. Rocky catches all-NULL columns and the
dbt_utils.starplaceholder. A macro that degrades another way without built upstreams is not caught. Use--build-emptyfor packages that introspect. With--build-empty, a model whose upstream failed indbt run --emptyis refused on its own (W055) and the rest are vendored. - dbt-only features are dropped. Hooks,
on_schema_changeand other config that the migration importer does not translate are not translated here either. Tests on sources are not mapped. - Two packages that share a model-carrying dependency collide on that dependency’s model names. Rocky refuses the second package.
--build-emptywrites to the warehouse. It uses the adapter’s real credentials, creates therocky_package_build*schemas with empty relations, and runs the package’s hooks. Rocky does not drop these schemas. Where that is not allowed, use--compiled.- Vars are stored in plain text in
rocky-packages.lock. Do not pass secrets as--vars. - Seeds are not vendored. A package whose models read a dbt seed is refused.
Reference
Section titled “Reference”See rocky package for every flag and the JSON output.