ServicesAboutNotesContact Get in touch →
EN FR
Note

dbt deps and the Package Lock File

How dbt resolves and installs packages — the difference between packages.yml and dependencies.yml, how the lock file works, and the flags worth knowing.

Planted
dbtdata engineering

dbt deps resolves and installs package dependencies. There are two config files for declaring those dependencies — packages.yml and dependencies.yml — and they behave differently in ways that affect private packages and Mesh setups. The lock file produced by dbt deps controls reproducibility.

Two Config Files, Different Purposes

packages.yml

The traditional home for package dependencies. It supports Jinja rendering, which means you can use env_var() to inject tokens for private repositories:

packages:
- git: "https://{{ env_var('GIT_TOKEN') }}@github.com/my-org/private-package.git"
revision: v1.2.0
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]

If you’re working with private packages that require authentication tokens, this is where they belong.

dependencies.yml

Introduced for dbt Mesh, this file consolidates two things in one place: package dependencies and cross-project references. You declare which other dbt Cloud projects your project references via ref():

packages:
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]
projects:
- name: finance
dbt_cloud:
project_id: 12345

The catch: dependencies.yml does not support Jinja rendering. No env_var(). If your packages need authentication tokens injected at install time, they have to stay in packages.yml.

The practical rule: use packages.yml for packages when you have private repositories. Use dependencies.yml when you’re in a Mesh setup and want cross-project references to live alongside your package declarations. For most projects without Mesh, they’re interchangeable — pick one and stick with it.

What Happens When You Run dbt deps

dbt deps does three things in sequence:

  1. Reads all package declarations from packages.yml and/or dependencies.yml
  2. Resolves the dependency graph — if package A requires dbt-utils >=1.0.0 and package B requires dbt-utils >=1.1.0, it finds the version satisfying all constraints
  3. Installs resolved packages into a dbt_packages/ directory at the project root

The installed packages are fully functional dbt projects. Their macros, models, tests, and seeds become available to your project exactly as if you’d written them yourself. When you run dbt compile, dbt processes both your project and all installed packages as a single combined project.

The Lock File

Since dbt 1.7, dbt deps automatically writes a package-lock.yml file recording the exact resolved versions, including Git commit SHAs for any Git packages:

packages:
- package: dbt-labs/dbt_utils
version: 1.3.0
install_git: https://github.com/dbt-labs/dbt-utils.git
commit: abc123def456...
- git: https://github.com/my-org/internal-utils.git
revision: v0.4.0
commit: 789ghi...

Commit this file to version control. The lock file is what makes dbt deps reproducible — without it, two developers running dbt deps at different times might get different versions if a new release has dropped. With the lock file, everyone gets identical installs. Your CI pipeline also gets identical installs to what you tested locally.

If you want everyone to rebuild from the declared version constraints (ignoring the locked versions), use dbt deps --upgrade. This forces fresh resolution and writes a new lock file.

Useful Flags

A few flags that change how dbt deps behaves:

dbt deps --upgrade Forces resolution from scratch, ignoring the existing lock file. Use this when you want to pick up new versions of packages and update the lock file.

dbt deps --lock Updates the lock file based on current declarations without actually installing anything into dbt_packages/. Useful for checking what would be resolved before installing, or for updating the lock file in CI without triggering a full install.

dbt deps --add-package Adds a new package from the CLI without manually editing your config file. Supports --source hub, --source git, or --source local:

Terminal window
dbt deps --add-package dbt-labs/dbt_utils@1.3.0 --source hub

This writes the entry to packages.yml and runs the full resolution cycle.

The dbt_packages/ Directory

The installed packages live in dbt_packages/ at your project root. This directory should be gitignored — it’s generated output, not source code. Anyone cloning your repo runs dbt deps to recreate it.

The directory is flat: each package gets its own subdirectory named after the package. If you’re debugging a macro that isn’t behaving as expected, you can read the installed source directly:

dbt_packages/
├── dbt_utils/
│ └── macros/
│ └── sql/
│ └── generate_surrogate_key.sql
├── dbt_expectations/
└── fivetran_utils/

The package source you’re reading is the exact code that runs when your project compiles. If a macro is doing something unexpected, reading it directly is faster than reading the GitHub repository, which might be on a different version.

What dbt deps Doesn’t Do for Git Packages

One important limitation: dbt deps resolves version conflicts automatically for Hub packages but not for Git packages. If two of your Hub packages both depend on dbt-utils, dbt finds one version that satisfies both. If one of your packages is a Git dependency that also depends on dbt-utils, you have to manually ensure version compatibility.

This is the main reason to prefer Hub packages over Git packages when publishing open-source packages — users benefit from automatic deduplication. For internal packages where you control all the consumers, Git is fine. For packages used by people you don’t know, Hub distribution makes their lives meaningfully better.