Tutorial: Set up CI/CD with Slim CI and per-PR databases for dbt Projects on Snowflake¶
Introduction¶
This tutorial is a continuation of the Getting started with dbt Projects on Snowflake tutorial. It assumes you have a dbt project in a Git repository and a working Snowflake environment with your database, schemas, warehouse, and source data set up. You can use the Tasty Bytes objects from the getting-started tutorial or substitute your own objects.
This tutorial provides all of the additional environment, authentication, repository, and workflow setup needed to implement advanced CI/CD with GitHub Actions, GitLab CI/CD, or Azure DevOps. You don’t need to complete the introductory CI/CD tutorial first. If you already completed it, you can reuse its service user, network policy, secrets, and variables.
Note
Some features described on this page require a dbt project object that uses the mutable live version. To get a live-version object, opt in to the 2026_06 behavior change bundle or ask your Snowflake account representative to enable the separate single live version feature. Then create or replace the object, or migrate an existing versioned object with SYSTEM$MIGRATE_DBT_PROJECT. For details, see dbt Projects on Snowflake: dbt project objects migrate to a single mutable live version.
For the underlying artifact comparison and defer concepts, and for a SQL alternative, see Use dbt artifacts for Slim CI and defer to production.
Overview¶
This tutorial walks you through the following steps:
- Set up the Snowflake environment and configure the dbt profile to accept a per-pull-request database override.
- Create a CI/CD service user that uses OpenID Connect (OIDC) authentication.
- Configure an optional network policy.
- Store the required secrets and variables in your CI/CD platform.
- Create an incoming-pull-request workflow that:
- Creates a zero-copy clone for the pull request. Cloning is optional unless tests need a realistic, writable copy of existing production data and objects; this tutorial includes it.
- Deploys a tester dbt project object with
--no-auto-compile. - Imports state from the latest qualifying successful production execution.
- Uses
dbt buildto run and test only changed models and their downstream dependencies in DAG order. - Defers unchanged upstream references to production relations.
- Create a production deployment workflow that:
- Deploys code pushed to
mainto the production dbt project object. - Optionally recreates or updates scheduled Snowflake tasks.
- Deploys code pushed to
- Remove the tester object and per-pull-request database after validation or when the pull request closes, depending on the CI/CD platform.
At the end of the tutorial, you’ll have a repeatable workflow that isolates each pull request or merge request, processes only the affected portion of the dbt DAG, and keeps production state available for future Slim CI runs.
Prerequisites¶
- An existing production dbt project object.
- A CI role with the
MONITORprivilege on the production dbt project object. - At least one successful execution of the production dbt project object within the previous 7 days.
Set up your environment¶
Set up where your dbt project reads and writes in Snowflake, then configure env.yml and your profile file so the incoming-pull-request
workflow can override the target database.
Create a warehouse, database, and schemas¶
Note
If you completed the
Getting started with dbt Projects on Snowflake tutorial
and ran tasty_bytes_setup.sql, the tasty_bytes_dbt_db database, dev and prod schemas, and TASTY_BYTES_DBT_WH warehouse already
exist. You can skip this step.
If you use the Tasty Bytes project but haven’t created its warehouse, database, and schemas, run the following commands:
If you use your own project, substitute the production database, schemas, and warehouse that your production dbt project object uses.
Configure environment and profile files¶
The env.yml file defines named execution environments for a dbt project. In this workflow, CI selects the staging environment and
overrides DBT_DATABASE so that selected models write to the zero-copy clone for the pull request or merge request. The production workflow
uses the default prod environment and database.
Create env.yml in the root of your dbt project, next to dbt_project.yml:
To manage CI/CD for a dbt project object, include dbt_projects_profiles.yml or profiles.yml in the root directory of your dbt project.
Snowflake uses dbt_projects_profiles.yml if both files are present.
This tutorial uses profiles.yml. Configure its database fields to read the DBT_DATABASE environment variable:
The account and user values are placeholders because the CI/CD platform variables configure the temporary Snowflake CLI connection. Replace the role, warehouse, database, and schema names with the objects for your project.
For more information about env.yml, including value precedence, see
Using SQL environment variables and private Git packages for dbt Projects on Snowflake.
Configure CI role access¶
Grant the CI role monitoring access to the production dbt project object:
Grant the role the account-level privilege required to create the per-pull-request database:
The CI role also needs the following access:
USAGEon the database and schema that contain the production dbt project object.- The privileges required to clone the source database. For more information, see Access control requirements.
- Access to the warehouse and production relations that deferred references resolve to.
- The privileges required to create, execute, and drop the tester dbt project object.
For dbt project object privileges, see Access control for dbt projects on Snowflake.
Create a CI/CD service user in Snowflake (recommended)¶
Your CI/CD platform runs Snowflake CLI commands as a Snowflake service user. OIDC authentication is recommended because it uses a short-lived token for each workflow run instead of a long-lived credential.
Recommended: OIDC-based service user¶
Create an OIDC-based service user for your platform. Replace DBT_CI_ROLE, the warehouse, and the platform identifiers with your values.
The GitHub environment in the subject must match the environment value in both workflow files:
By default, GitLab includes the branch in the OIDC subject. To use one service user for merge-request and main pipelines, customize the
GitLab sub claim so it remains the same across branches, then use that value for <gitlab_custom_subject>. For details, see
.
For more information, see GitLab CI/CD component.
The subject uses an Azure DevOps service connection identifier. Replace <azure-ad-tenant-id> with your Microsoft Entra tenant ID.
For more information, see Azure DevOps extension.
The DEFAULT_ROLE property selects a default role but doesn’t grant it, so each example includes a separate GRANT ROLE command.
For more information, see Workload identity federation.
Alternative: Use PAT-based authentication (less secure)¶
If you can’t use OIDC, you can authenticate the service user with a programmatic access token (PAT). This alternative is less secure because it relies on a long-lived credential that requires manual rotation.
Create a service user and an authentication policy that permits PAT authentication. Replace <service_user> with a platform-specific name,
such as github_actions_service_user, gitlab_cicd_service_user, or ado_cicd_service_user:
Generate a PAT for the service user and store it in your CI/CD platform as described later. For more information, see Using programmatic access tokens for authentication.
(Optional) Set up a network policy for your CI/CD platform¶
Now that you’ve created the service user that Snowflake CLI uses, configure this user to connect to your Snowflake account from within your CI/CD platform.
Note
Creating or modifying network policies requires ACCOUNTADMIN or an equivalent role.
Determine whether you need a network policy¶
- If your account restricts inbound access, you must create or update a network policy to add your CI/CD platform’s runner IPs to your allowlist. Snowflake provides managed network rules for some platforms. For more information, see Network rules.
- If your account doesn’t restrict inbound access, no network policy changes are required.
If you’re unsure, skip this step for now and return only if you see an error like:
Incoming request with IP/Token <IP> is not allowed to access Snowflake.
To create and apply a network policy to a user, choose one of the following options:
- Create a new network policy and assign it to the service user.
- Add a network rule to an existing network policy that the user already uses.
Note
Before doing this, consult your Snowflake account administrator. They must ensure that the policy includes the CI/CD platform network rule and any other IP ranges your organization requires.
After a network policy is applied, Snowflake restricts user access based on its allowed and blocked IP ranges. Your account administrator might need to adjust the policy or apply it account-wide to avoid unintentionally blocking essential access.
Option 1: Create a new network policy and apply it to the user¶
A Snowflake user can have only one network policy at a time. If the user doesn’t have one or you want to replace the existing policy, complete the following steps:
Snowflake provides a managed network rule for GitHub-hosted runners:
GitLab doesn’t have a Snowflake-managed network rule. GitLab.com hosted runners don’t have static, allowlistable IP addresses. If your Snowflake account restricts inbound IPs, use a self-hosted GitLab Runner or route runner traffic through a fixed egress IP:
Azure DevOps doesn’t have a Snowflake-managed network rule. For Microsoft-hosted agents, allow the Azure DevOps service IP ranges. For self-hosted agents, add the agent’s static egress IP:
Option 2: Add a network rule to an existing network policy¶
If the user already has a network policy, add the appropriate rule to it. First, check the user’s current network policy:
Note
If the network policy is applied at the account level or shared by many users, updating it affects everyone.
Add the platform’s network rule to the existing policy:
The user inherits the update automatically because they’re already assigned to this policy.
For more information, see Network rules.
Configure CI/CD secrets and variables¶
Your CI/CD pipelines use Snowflake CLI to connect to your Snowflake account, so you must configure platform secrets and variables first. Secrets identify the account and, when required, the user credentials. Variables supply Snowflake object names and the dbt project path.
Configure secrets¶
Add secrets to securely store the information that Snowflake CLI needs to identify your Snowflake account and, if required, the user it should authenticate as.
- In your GitHub repository, go to Settings.
- From the left-hand navigation, select Secrets and variables » Actions.
- Under Secrets, select New repository secret.
- Add a secret to connect to your Snowflake account:
- Name:
SNOWFLAKE_ACCOUNT - Value: Your Snowflake account identifier, for example,
org_name-account_name.
- Name:
- Select Add secret.
- If you use PAT authentication instead of OIDC, add these repository secrets:
- Name:
SNOWFLAKE_USER- Value:
github_actions_service_user, or the name of your service user.
- Value:
- Name:
SNOWFLAKE_PAT- Value: The PAT generated for the service user.
- Name:
Don’t add SNOWFLAKE_USER or SNOWFLAKE_PAT when you use OIDC. Snowflake automatically matches the GitHub Actions subject to the OIDC
service user.
- In your GitLab project, go to Settings » CI/CD.
- Expand Variables, then select Add variable.
- Add a variable for your Snowflake account:
- Key:
SNOWFLAKE_ACCOUNT - Value: Your Snowflake account identifier, for example,
org_name-account_name. - Enable Mask variable.
- Key:
- Add a variable for your Snowflake user:
- Key:
SNOWFLAKE_USER - Value: The service user you created, for example,
gitlab_cicd_service_user. - Enable Mask variable.
- Key:
- If you use PAT authentication instead of OIDC, add a variable for the PAT:
- Key:
SNOWFLAKE_PASSWORD - Value: The PAT generated for the service user.
- Enable Mask variable. Enable Protect variable only if the merge-request branches are protected and the pipeline can access protected variables.
- Key:
- In your Azure DevOps project, go to Pipelines » Library.
- Select + Variable group or open an existing variable group named
dbt-cicd-variables. - Add a variable for your Snowflake account:
- Name:
SNOWFLAKE_ACCOUNT - Value: Your Snowflake account identifier, for example,
org_name-account_name. - Select the lock icon to mark it as secret.
- Name:
- Add a variable for your Snowflake user:
- Name:
SNOWFLAKE_USER - Value: The service user you created, for example,
ado_cicd_service_user. - Select the lock icon to mark it as secret.
- Name:
- If you use PAT authentication instead of OIDC, add a variable for the PAT:
- Name:
SNOWFLAKE_PASSWORD - Value: The PAT generated for the service user.
- Select the lock icon to mark it as secret.
- Name:
- Select Save, then authorize both pipelines to use the variable group.
Configure variables¶
| Variable | Description | Example |
|---|---|---|
SNOWFLAKE_DATABASE | Database that contains the production and tester dbt project objects. | TASTY_BYTES_DBT_DB |
SNOWFLAKE_SCHEMA | Schema that contains the production and tester dbt project objects. | PROJECTS |
Add these values to your platform:
- In your GitHub repository, go to Settings.
- From the left-hand navigation, select Secrets and variables » Actions.
- Under Variables, select New repository variable.
- Add each value from the table as a repository variable, selecting Add variable after each one.
- In your GitLab project, go to Settings » CI/CD.
- Expand Variables, then add each value from the table as a project variable.
These object names and paths don’t need to be masked unless your organization treats them as sensitive.
- In your Azure DevOps project, go to Pipelines » Library.
- Open the variable group used by your pipelines.
- Add each value from the table, then select Save.
Create your Continuous Integration (CI) pipeline¶
This step is where automation starts. The CI pipeline runs whenever a pull request or merge request targets main. It creates isolated
resources for the request, imports artifacts from production, and uses dbt build to run and test changed models and their downstream
dependencies in DAG order.
Create your CI workflow file¶
Warning
Don’t use --force unless you intentionally want to recreate the dbt project object. In snow dbt deploy, --force runs CREATE OR REPLACE DBT PROJECT, which may remove run history.
-
In your GitHub repository, go to Actions.
-
From the left-hand side navigation, select New workflow.
-
Select set up a workflow yourself to create an empty workflow.
-
Name the file
incoming_pr_slim_ci.yml. -
Copy and paste the following into the file:
-
Select Commit changes.
-
Select Create a new branch for this commit and start a pull request.
-
Select Propose changes.
-
After you finish submitting the pull request, you should see your
incoming_pr_slim_ci.ymlaction start to run. -
After it’s merged, the file will be saved to
.github/workflows/incoming_pr_slim_ci.yml.
If you use PAT authentication, remove the with block that contains use-oidc: true, and uncomment the SNOWFLAKE_USER and
SNOWFLAKE_PASSWORD lines in the workflow.
Create a .gitlab-ci.yml file at the root of your repository (or add the following to your existing one):
Commit the file to a branch and open a merge request targeting main. The cleanup job runs after validation, including when validation
fails.
If you use PAT authentication, set the component’s use-oidc input to false and configure SNOWFLAKE_USER and SNOWFLAKE_PASSWORD.
Create azure-pipelines-ci-slim-ci.yml at the root of your repository:
In Azure DevOps, create a pipeline named Incoming PR - Slim CI that uses this YAML file and configure it as a build-validation policy for
pull requests targeting main. The cleanup steps remove the CI resources after each validation run.
If you use PAT authentication, set useWorkloadIdentity to false and add SNOWFLAKE_USER and SNOWFLAKE_PASSWORD to each script’s env
mapping.
When snow dbt deploy runs in GitHub Actions, Snowflake CLI automatically captures the commit and branch. The GitLab and
Azure DevOps workflows must explicitly pass --git-commit and --git-branch. These flags record the source commit and
branch in the dbt project object’s deployment metadata.
Key pieces from the workflow file¶
Regardless of which platform you chose, the pipeline follows the same pattern:
- Trigger: Runs on incoming pull requests (or merge requests) targeting
main. - OIDC authentication: Each platform requests a short-lived token that Snowflake validates against your service user’s WORKLOAD_IDENTITY configuration.
- Snowflake CLI with
-xflag: The-xflag (--temporary-connection) tells Snowflake CLI to build the connection from environment variables (such asSNOWFLAKE_ACCOUNTandSNOWFLAKE_DATABASE) rather than reading a named profile fromconfig.toml, which isn’t available in a CI/CD runner. - Steps in the job:
- Check out repository code.
- Install Snowflake CLI with OIDC enabled.
- Check the Snowflake CLI version and verify the connection with
snow connection test -x. - Create a zero-copy clone whose name includes the pull-request or merge-request number.
- Deploy a tester dbt project object using
snow dbt deploy ... --no-auto-compile -x(with--sourceif the dbt project is in a subfolder). - List the dbt project objects with
snow dbt list -x. - Build and test the changed portion of the dbt project in DAG order with production state, defer, and
state:modified+. - Remove the tester object and clone when the platform’s cleanup workflow runs.
This workflow creates a zero-copy clone for each pull request or merge request so that CI has a realistic database it can modify without changing production. The clone is an isolation choice, not a prerequisite for Slim CI. You don’t need to clone production for every CI workflow unless your tests need a realistic, writable copy of existing production data and objects. With defer to production, CI can read unchanged upstream tables from production and write changed models to a separate test database or schema.
Artifact comparison and database cloning serve different purposes. The state:modified+ selector identifies which nodes to execute, and
--defer resolves references to unchanged production relations. The per-pull-request clone determines where CI writes and provides an
independently writable snapshot when tests depend on existing data or database state.
When Snowflake creates a zero-copy clone, the clone initially shares the source database’s existing micro-partitions instead of physically copying all table data. The source and clone consume additional storage as their data changes.
The incoming workflow combines the Slim CI operations in one selective dbt build command. This runs models and tests in DAG order:
--no-auto-compileskipsdbt depsanddbt compileduring deployment. The project compiles later, during execution, with the environment override.SYSTEM$DBT_GET_LAST_SUCCESSFUL_RUN_TARGETretrieves artifacts from a recent successful execution of the production dbt project object.--import "... as state"mounts those artifacts at./imports/state.--state ./imports/state --select state:modified+selects changed nodes and their downstream dependencies.--deferresolves unselected upstream references to existing production relations.--env staging --env-vars ...overridesDBT_DATABASEso selected models write to the per-pull-request database.
Create your Continuous Deployment (CD) pipeline¶
The production workflow deploys code pushed to main and optionally recreates or updates scheduled tasks. Resource cleanup differs by
platform:
- GitHub Actions uses a second event in the production workflow to clean up when a pull request closes.
- The GitLab and Azure incoming workflows clean up after each validation run.
Create your CD workflow file¶
Warning
Don’t use --force unless you intentionally want to recreate the dbt project object. In snow dbt deploy, --force runs CREATE OR REPLACE DBT PROJECT, which may remove run history.
-
In your GitHub repository, go to Actions.
-
From the left-hand side navigation, select New workflow.
-
Select set up a workflow yourself to create an empty workflow.
-
Name the file
pr_merged_slim_ci.yml. -
Copy and paste the following into the file:
-
Select Commit changes to save the file to
.github/workflows/pr_merged_slim_ci.yml. -
Navigate to the Actions tab of your repository to see your
pr_merged_slim_ci.ymlaction start to run.
If you use PAT authentication, remove each with block that contains use-oidc: true, and uncomment the SNOWFLAKE_USER and
SNOWFLAKE_PASSWORD lines in both jobs.
Pull-request cleanup runs from the separate closed event, so it doesn’t depend on the production deployment job succeeding.
Add the following deploy job to your .gitlab-ci.yml file (after the CI test and cleanup jobs you created earlier):
Commit and push to main to trigger the deployment.
Create a file named azure-pipelines-cd-slim-ci.yml at the root of your repository:
Create a second pipeline named PR Accepted Deployment - Slim CI in Azure DevOps pointing to this YAML file.
Key pieces from the workflow file¶
- Trigger: Runs on updates to
mainafter a merge or direct push. - Same OIDC authentication and
-xflag as the CI pipeline. - Steps:
- Check out the repository code.
- Install Snowflake CLI with OIDC.
- Check the Snowflake CLI version and verify the connection with
snow connection test -x. - Deploy or update the production dbt project object with
snow dbt deploy ... --default-target prod --no-auto-compile -x. - List the dbt project objects with
snow dbt list -x. - Optionally run a
schedules.sqlfile to manage tasks.
- Cleanup: GitHub cleans up resources when the pull request closes. GitLab and Azure clean up at the end of their CI pipelines.
The production dbt project object must complete at least one successful scheduled or otherwise initiated execution every 7 days so that future Slim CI jobs can import its state.
(Optional) Add orchestration with Snowflake tasks¶
To manage task orchestration from source control, create schedules.sql in the dbt project folder, then uncomment the optional
schedules.sql step in the production workflow.
The following example suspends an existing task, recreates or updates it, and resumes it:
Replace the warehouse, task, and dbt project object names with your objects. Keep the optional schedules.sql step after deployment so that
tasks are updated only after deployment succeeds. A successful scheduled build also keeps production state available for future pull
requests.
Verify the workflows¶
Open a pull request or merge request that changes a model. In the CI run, verify that:
- The incoming workflow creates a database whose name contains the pull-request or merge-request number.
- Snowflake CLI deploys a tester dbt project object with
--no-auto-compile. - The workflow lists the tester dbt project object with
snow dbt list -x. - The build step imports production state.
- dbt selects changed models and their downstream dependencies.
- Selected models write to the per-pull-request database.
Merge the pull request or merge request and verify that:
- The pipeline triggered by the update to
maindeploys the production dbt project object. - The workflow lists the production dbt project object with
snow dbt list -x. - If task deployment is enabled, the workflow recreates or updates the tasks.
Verify cleanup for your platform:
Verify that the pull-request closed workflow run removes the tester object and per-pull-request database. Also close an unmerged test pull
request and verify that cleanup and the final snow dbt list -x step run without starting a production deployment.
Verify that the cleanup-slim-ci job removes the tester object and per-merge-request database after validation, including when a validation
command fails, and that the final list command runs.
Verify that the cleanup steps remove the tester object and per-pull-request database after validation, including when a validation command fails, and that the final list step runs.
In Snowflake, use SHOW DBT PROJECTS and dbt project execution history to confirm the tester and production deployments. Deployment metadata
should identify the repository, branch, and commit.
Adapt the cloning decision¶
Keep the per-pull-request clone when CI needs an independently writable snapshot of existing database state, such as when you test:
- DDL, migrations, or destructive operations.
- Incremental models against realistic existing target data.
For ordinary transformation changes, you can omit the clone and use an empty isolated database or schema as the CI write target. State selection and defer continue to work as long as the CI role can read the required production relations.
Clean up manually¶
The CI/CD workflows normally remove CI resources. If a workflow was disabled or interrupted, remove the resources manually: