There are three ways to install a dbt package: from the Hub, from a Git repository, or from a local filesystem path. Each has distinct trade-offs. Most teams use all three at some point — Hub for open-source packages, Git for internal libraries, local for development and testing.
Hub Packages
Hub packages install from hub.getdbt.com using semantic version ranges:
packages: - package: dbt-labs/dbt_utils version: [">=1.0.0", "<2.0.0"] - package: metaplane/dbt_expectations version: [">=0.10.0", "<1.0.0"]The namespace is {publisher}/{package_name}. The version range lets dbt pick the best compatible version rather than locking to a specific release.
The key advantage: automatic transitive dependency resolution. If both dbt_expectations and fivetran/ad_reporting depend on dbt_utils, the Hub reconciles their version requirements automatically. It finds the highest version that satisfies all constraints and installs exactly one copy. This is what makes large package ecosystems manageable.
Use Hub packages whenever the package is publicly available there. The deduplication alone justifies it.
Git Packages
Git packages install from any Git repository, pinned to a tag, branch, or commit SHA:
packages: - git: "https://github.com/my-org/internal-utils.git" revision: v0.4.0For private repositories, use the private key with native provider authentication instead of embedding tokens:
packages: - private: my-org/internal-utils provider: github revision: v0.4.0The private + provider approach uses whatever Git credentials are already configured in your environment (GitHub App, deploy key, SSH key) rather than embedding a token in your config file. See dbt Private Packages via Git for the full authentication options.
The key limitation: no transitive dependency resolution. If your Git package depends on dbt-utils and the user’s project also declares dbt-utils with a conflicting version range, you get a resolution error. You have to manually ensure version compatibility across all your Git packages.
This is manageable when you control all the consumers (internal packages shared across your own projects). It becomes painful if you’re distributing a package to many users — they’ll hit version conflicts and have to debug them manually. If a package is ready for broader use, publishing it to the Hub is worth the effort.
Local Packages
Local packages install from filesystem paths via symlinks:
packages: - local: ../my-local-packageThe path is relative to your project root. This creates a symlink into dbt_packages/, so changes to the local package are immediately reflected without running dbt deps again.
Local packages are specifically useful for:
- Monorepo development — multiple dbt projects in one repository, referencing shared packages from sibling directories
- Package development — testing your package against a real project before publishing it. The
integration_tests/sub-project pattern used by Fivetran and dbt Labs references the parent package vialocal: ../exactly this way.
Don’t use local packages in production. If your CI pipeline resolves a local path, the package won’t be present on the CI runner. Local is for development, not deployment.
Choosing Between Them
| Situation | Use |
|---|---|
| Package is on the Hub | Hub |
| Internal package, private repo | Git with private + provider |
| Developing or testing a package locally | Local |
| External package not yet on the Hub | Git |
| Contributing to a package before it’s published | Local |
The progression for a new internal package is usually: local while developing → Git once stable enough to share with your team → Hub once it’s ready for the broader community.
Version Conflict Patterns and How to Avoid Them
The most common error new dbt users hit is a version conflict that looks like this:
Could not find a compatible version for package dbt-labs/dbt_utilsThis happens when two packages declare incompatible version requirements for a shared dependency. The fix is almost always the same: remove the shared dependency from your root packages.yml.
Here’s the mistake:
# packages.yml - this causes problemspackages: - package: dbt-labs/dbt_utils version: [">=1.1.0", "<1.2.0"] # pinned too tightly - package: fivetran/ad_reporting version: [">=2.0.0", "<3.0.0"] # also requires dbt-utils >=1.0.0, <2.0.0The range >=1.1.0, <1.2.0 is too narrow. When ad_reporting also pulls in dbt-utils with a wider range, dbt has to satisfy both constraints simultaneously, which may be impossible if the latest dbt-utils version exceeds 1.2.0.
The fix: only list packages you directly use in your models. Don’t list their transitive dependencies.
# packages.yml - correctpackages: - package: fivetran/ad_reporting version: [">=2.0.0", "<3.0.0"]# dbt_utils gets installed automatically as a transitive dependency of ad_reportingIf you genuinely need to use dbt-utils macros in your own code, add it with a wide range:
packages: - package: dbt-labs/dbt_utils version: [">=1.0.0", "<2.0.0"] # wide range, not pinned tightly - package: fivetran/ad_reporting version: [">=2.0.0", "<3.0.0"]Fivetran explicitly warns about this in their package READMEs, which is how common it is. The anti-pattern trips up experienced engineers who know their project “needs” dbt-utils and add it explicitly without realizing a downstream package already brings it in.
The same principle applies to bundles: when installing a cross-platform bundle like fivetran/ad_reporting, don’t also install individual platform packages like fivetran/google_ads or fivetran/facebook_ads. The bundle installs those as dependencies. Installing them separately creates the same conflict pattern.