#052: dbt Environments vs Targets | What's the Difference?
Oct 04, 2023In dbt Core, a target is one named set of connection credentials in profiles.yml, and you choose which one a run uses with the --target flag. dbt Cloud takes those same credentials and manages them through an interface called environments, split into a development environment for each user and deployment environments for shared runs like CI and production. They are the same idea surfaced two different ways, which is why the word target still shows up inside dbt Cloud as a name you set on a job. Environment variables are a third concept that works on both: values set outside the code and read in with the env_var function. Once you see that Cloud is wrapping the Core mechanics, moving between the two stops being confusing.
Key takeaways
- A target is a named block of credentials under
outputsinprofiles.yml. Thetargetkey at the top sets which one runs by default. - Switching environments in dbt Core is one flag:
dbt build --target prod. The same project code deploys anywhere by changing that flag. - dbt Cloud calls the same credentials environments and stores them in its interface, because you don't have access to
profiles.ymlon the Cloud server. - Cloud has two environment types. Development is your personal space with credentials on your profile page. Deployment environments are for shared runs like CI and production.
- Target name and credentials are separate concepts in Cloud. A job uses an environment for its connection and gets its own target name, which your code can read with
target.name. - Environment variables keep passwords out of
profiles.ymland let one setting, like materialization, change per environment. Cloud adds a precedence order: project default, then development, then production. - Learn dbt Core first. If you understand targets in code, the Cloud interface is a friendlier view of something you already know.
Targets in dbt Core
Where targets live
When you initialize a dbt Core project you get a .dbt directory, and inside it a profiles.yml. This file holds your connection credentials, and each named set of credentials is a target.
A fresh project usually has one. Here it is called dev, and the target key at the top says to use it by default:
my_project:
target: dev
outputs:
dev:
type: snowflake
account: my_account
user: mkahan
password: my_password
role: developer
warehouse: transforming
database: analytics
schema: dev_mkahan
Every time you run dbt without saying otherwise, it connects with this information and deploys to this database and schema.
Adding more targets
Environments exist to keep concepts separate. At some point you want production, and maybe a QA or UAT environment too. In dbt Core you add them as more entries under outputs, using the same shape as dev:
prod:
type: snowflake
# same connection fields as dev
database: analytics_99
schema: production
ci:
type: snowflake
# same connection fields as dev
database: analytics_100
schema: ci
Now dev runs go to analytics, and production runs go to analytics_99. The project code is identical. Only the destination changes.
Switching with the target flag
The switch is a command line flag. dbt compile on its own uses the default target, so the compiled SQL in the target folder points at the development database and schema.
Add the flag and the output changes in real time:
dbt compile --target prod
dbt build --target prod
dbt build --target ci
Compile the same model with --target prod and the compiled SQL now references analytics_99 and the production schema. Nothing else moved.
That is the whole mechanism, and it is what makes one codebase serve many environments. A few places you lean on it:
- Scheduled runs in a tool like Airflow or Prefect, where the command specifies the production target
- Continuous integration, where a CI target exists only for testing and points at its own database or schema
- Local development, where you never pass the flag and the default keeps you safely in dev
Using target.name in your code
The target does more than pick credentials. Its name is available in Jinja as target.name, so your code can behave differently per environment. A common use is limiting data outside of production:
select *
from {{ ref('stg_orders') }}
{% if target.name == 'dev' %}
limit 1000
{% endif %}
The same value works in your YAML files, so a configuration can switch based on which target is running. It all keys off the one flag.
Environments in dbt Cloud
Same credentials, different surface
Move to dbt Cloud and the word environment appears, which trips people up. The way I see it, an environment in Cloud is one of those outputs blocks from profiles.yml, organized through an interface instead of a file.
You don't really have access to profiles.yml on the Cloud server, so Cloud is serving the same thing a different way. There are two types.
Development environments
Cloud creates a default development environment for you as a user. Its credentials live on your profile page: account, role, username, and the rest, the same fields that were in the file.
The design is a nudge. Your development environment is not meant for other people to interact with. By separating it, Cloud is telling you not to build changes in the same place as your live production objects.
Deployment environments
The second type is a deployment environment, and it is what you create for shared runs. Say you want a CI environment that doesn't exist yet:
- Create a new environment and name it
ci. The type defaults to deployment. - Pick the dbt version it runs on.
- Set the connection and the deployment credentials. This is where a production specific username or key goes if you want to lock down security.
- Set the schema it deploys to, exactly as you would in
profiles.yml.
A production environment is the same form filled out with production credentials and a production schema, separate from the schema your development environment uses.
Where target shows up in Cloud
Even though Cloud handles credentials through environments, target has not gone away. It appears when you create a job.
A job runs inside an environment, which supplies the connection. The job also has a target name field that you can set to anything. The two are separate concepts: the environment decides where the run connects, the target name is a label your code can read.
That matters because the target.name logic from dbt Core still works:
- A daily production job in the production environment with target name
prod, so code that checks forprodbehaves accordingly - A second job in the same environment, same credentials, with a different target name and its own branch of logic in the code
In dbt Core you would get the same effect by copying the credentials under a new output name. Cloud stores them once and lets you label the run.
Environment variables
In dbt Core
An environment variable is a value set on the machine, not in the code, and dbt reads it with the env_var Jinja function. The first reason to use one is good practice: don't hard code credentials and passwords in profiles.yml.
dev:
type: snowflake
user: "{{ env_var('DBT_USER') }}"
password: "{{ env_var('DBT_PASSWORD') }}"
The value gets passed in when you invoke the run. On a CI/CD job you pass it at run time so nothing sensitive is stored permanently or committed to the repository.
They work for more than secrets. A materialization with a default value looks like this:
models:
my_project:
+materialized: "{{ env_var('DBT_MATERIALIZATION', 'table') }}"
If the variable isn't set, every model is a table. Set it and the whole project switches.
In dbt Cloud
Cloud adds environment variables through the interface with a precedence order. Taking the materialization example:
- Project default:
table - Development environment:
view, so your personal builds are fast - Production environment:
tableagain
Save it, open a job in the production environment, and you can see the value it will use for that run. Open your development profile and the same variable reads view. One name, resolved by context.
Under the hood it is the same machinery. Cloud manages targets, environments and variables through an interface, and Core manages them in code and commands. Learn it in code first and the interface explains itself.
Key terms
Target
A named set of connection credentials under outputs in profiles.yml, selected by the --target flag, that tells dbt Core where a run connects and deploys.
profiles.yml
The dbt Core credentials file, kept in the .dbt directory outside the project, that holds every target and the default to use when no flag is passed.
Development environment
The dbt Cloud environment created for each user, with credentials on their profile page, meant for building changes that no one else interacts with.
Deployment environment
A dbt Cloud environment for shared runs such as CI or production, with its own connection, deployment credentials and schema, that jobs run inside of.
Environment variable
A value set outside the code and read with the env_var function, used to keep secrets out of files and to change settings per environment.
Common questions
Is a dbt Cloud environment the same as a target?
Close enough to think of it that way. A Cloud environment holds the credentials, database and schema that a target block holds in profiles.yml. The difference is that Cloud also keeps a separate target name on each job, so the credentials and the label your code reads are two settings instead of one.
How do I switch between dev and prod in dbt Core?
Define both as outputs in profiles.yml, set target: dev as the default, and pass --target prod on the command line when you want production. Schedulers like Airflow or Prefect pass the flag in the command they run.
Why does dbt Cloud still ask for a target name on a job?
Because your code may contain logic that checks target.name, and that logic needs a value to read. The environment supplies the connection, the target name supplies the label. Two jobs can share an environment and still take different code paths.
Should I store passwords in profiles.yml?
No. Reference them with env_var and set the variable on your machine or pass it in at run time on CI/CD. The file stays safe to share and the secret is never committed anywhere.
What is the difference between target.name and an environment variable?
target.name is one value tied to the run and is best for environment branching. Environment variables are arbitrary named values you define yourself, which makes them better for secrets and for settings like materialization that you want to override per environment.
Related reading
- The 3-Environment Design for Your Database (DEV vs CI vs PROD)
- How Data Teams Maintain Production (with high quality)
- Data Automation (CI/CD) with a Real Life Example
- Start Using Jinja in dbt Macros (3 Examples)
Final takeaway
Targets, environments and environment variables are the three settings I configure on every dbt project I help a team stand up, and the confusion almost always comes from meeting Cloud first. Learn the mechanics in profiles.yml and the Cloud interface becomes a friendlier view of something you already understand.
Additional Free Resources
Starter Guides & Checklists
Explore additional free resources built on the same patterns I use with real clients so you can build your own with structure and confidence. Topics include data architecture, modeling and more specifically for small data teams.