Snowflake Platform with Terraform

Provision a Snowflake platform with RBAC, warehouses, and cost controls via Terraform.

Introduction

30 Second Summary

Every company running on data needs guardrails. Without clear rules about who can access what, how much compute to allow, and where data lives, cloud data platforms become expensive and chaotic fast.

In this project, you will build a complete Snowflake platform from scratch using Terraform. You will declare databases, warehouses, a role hierarchy, and cost controls entirely as code, then deploy it all with a single command.

What You'll Build

A production-style Snowflake environment managed entirely through Infrastructure as Code, including databases, workload-isolated warehouses, a role-based access hierarchy, and automated cost controls.

By the end of this project, you'll have:

  • A Terraform project with provider configuration, variables, and modular resource definitions that provisions your entire Snowflake environment.
  • A working RBAC hierarchy with access roles and functional roles (analyst, engineer, admin) following least-privilege principles.
  • Three workload-isolated warehouses with resource monitors that automatically suspend compute when credit thresholds are exceeded.
  • Secret Mission: Add a dynamic data masking policy that protects sensitive columns based on role membership.

Are there any prerequisites?

Basic SQL knowledge and basic command-line familiarity are helpful. No prior Snowflake or Terraform experience is required.

Before We Start

Before We Start...

Set Up Your Tools

Before you can declare your Snowflake platform as code, you need the tools that make it possible. Terraform is the CLI that reads your configuration files and provisions infrastructure from them.

In this step, you will install Terraform, create a free Snowflake trial account, and wire the two together with a provider configuration. By the end, a single terraform init command will download the Snowflake provider plugin and confirm everything is connected.

In this step, get ready to:
  • Install the Terraform CLI and verify it runs.
  • Sign up for a Snowflake Enterprise Edition free trial and log into Snowsight.
  • Create a Terraform project directory with provider configuration and run terraform init.
Install Terraform CLI and verify the installation

Terraform is a single binary you install on your machine. Once installed, the terraform command becomes available in your terminal for all future projects.

  • Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Terminal (macOS) or PowerShell (Windows), and pressing Enter.
  • Install Terraform using your platform's package manager by running the command below:

macOS

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

What does this command do?

The first line adds HashiCorp's official Homebrew tap (a repository of packages). The second line installs the terraform binary from that tap.

Windows

choco install terraform

What does this command do?

Chocolatey downloads and installs the Terraform binary, then adds it to your system PATH automatically.

If you do not have Chocolatey installed, visit chocolatey.org/install to set it up first.

  • Verify the installation by running:
terraform version

You should see a version number printed in your terminal.

✔️ I see a version number

Terraform is installed and ready to use. Move on to the next substep.

ⓧ Command not found

Your terminal cannot find the terraform binary.

  • Close your terminal and open a new session so that PATH changes take effect.
  • If you are on Windows and do not have Chocolatey, download the binary directly from developer.hashicorp.com/terraform/install, unzip it, and add the folder to your system PATH.
  • If you are on macOS, confirm Homebrew is installed by running brew --version. If Homebrew is missing, install it from brew.sh.

After fixing the issue, run terraform version again.

What is Infrastructure as Code?

Infrastructure as Code (IaC) means defining cloud resources in text files instead of clicking through a web console. Terraform reads these files, calculates the changes needed, and applies them in one command.

This makes your infrastructure repeatable, version-controlled, and reviewable, just like application code.

Sign up for a Snowflake free trial and log into Snowsight

Snowflake offers a 30-day free trial with $400 in credits. No payment information is required.

  • Open signup.snowflake.com in your browser.
  • Fill in your name, email, and company (you can enter a placeholder like "Personal").
  • On the edition selection screen, choose Enterprise.
  • Choose any cloud provider and region close to you.
  • Complete the signup. Snowflake sends a confirmation email with an activation link.
  • Click the activation link and set your username and password.

Why Enterprise Edition?

Enterprise Edition unlocks features like managed access schemas and dynamic data masking that you will use later in this project. The trial is fully free regardless of which edition you pick.

  • After activation, navigate to app.snowflake.com and sign in with your new credentials.
  • You should see the Snowsight home page with navigation on the left.

Before you leave Snowsight, note your organization name and account name. You need these for the Terraform provider.

  • In the bottom-left corner of Snowsight, click your account name to open the account selector.
  • Hover over your account entry and select View account details.
  • Copy the Organization and Account values shown in the dialog. Save them somewhere handy (a notes file or sticky note).

Where does the account identifier come from?

Snowflake identifies every account with a two-part format: organization_name-account_name. The Terraform provider needs these as separate values so it knows which Snowflake account to manage.

If you cannot find the account details dialog, check the Welcome email Snowflake sent during signup. It contains the same information.

Create a Terraform project directory and configure the Snowflake provider

With both tools ready, you will create a project folder and write three files that tell Terraform how to connect to your Snowflake account.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir snowflake-platform
cd snowflake-platform
  • Confirm the folder exists by running:
ls

You should see the snowflake-platform folder listed on your Desktop.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Cursor and press Enter to open it.
  • In Cursor, click File in the top menu bar.
  • Click Open Folder.
  • Select the snowflake-platform folder on your Desktop, then click Open.

You will now create the first file, main.tf. This file tells Terraform which provider to use and how to authenticate.

  • Create a new file called main.tf in the snowflake-platform folder and paste the following code:
terraform {
  required_providers {
    snowflake = {
      source  = "snowflakedb/snowflake"
      version = "~> 2.16"
    }
  }
}

provider "snowflake" {
  organization_name = var.snowflake_organization_name
  account_name      = var.snowflake_account_name
  user              = var.snowflake_user
  password          = var.snowflake_password
  role              = "ACCOUNTADMIN"
}

What does this code do?

The terraform block declares that you need the Snowflake provider from the snowflakedb/snowflake namespace, pinned to version ~> 2.16 (any 2.16.x release).

The provider block configures authentication using variables you will define next. The role = "ACCOUNTADMIN" line tells Terraform to operate with full administrative privileges, which is required for creating resource monitors and assigning them to warehouses.

Next, create variables.tf to declare the input variables the provider references.

  • Create a new file called variables.tf in the same folder and paste:
variable "snowflake_organization_name" {
  description = "Snowflake organization name"
  type        = string
}

variable "snowflake_account_name" {
  description = "Snowflake account name"
  type        = string
}

variable "snowflake_user" {
  description = "Snowflake username"
  type        = string
}

variable "snowflake_password" {
  description = "Snowflake password"
  type        = string
  sensitive   = true
}

What does this code do?

Each variable block declares an input that Terraform expects you to provide at runtime. The sensitive = true flag on the password prevents Terraform from printing it in logs or plan output.

Finally, create a terraform.tfvars file to supply your actual credentials. Terraform automatically reads this file when you run commands.

  • Create a new file called terraform.tfvars and paste the following, replacing each placeholder with your real values:
snowflake_organization_name = "your-org-name"
snowflake_account_name      = "your-account-name"
snowflake_user              = "your-username"
snowflake_password          = "your-password-here"

Keep credentials out of version control

If you later push this project to Git, add terraform.tfvars to your .gitignore file so your password is never committed. The terraform.tfvars.example file (which uses dummy values) is safe to commit as a reference template.

  • Save all three files.
  • Return to your terminal (still inside snowflake-platform) and initialize the project by running:
terraform init

What does this command do?

The terraform init command reads your main.tf, finds the snowflakedb/snowflake provider requirement, downloads the matching plugin binary, and stores it in a hidden .terraform directory. It also creates a .terraform.lock.hcl lock file to pin the exact provider version for reproducibility.

You should see output that ends with "Terraform has been successfully initialized!" in your terminal.

Seeing an error during init?

  • If you see a message about not being able to retrieve available versions, check your internet connection. Terraform needs to download the provider from the Terraform Registry.
  • If you see a provider version constraint error, confirm that version = "~> 2.16" in your main.tf matches what is shown above exactly.
  • If you see a "No such file or directory" message, make sure your terminal is inside the snowflake-platform folder. Run pwd to check your current location.

Help me debug my terraform init error

  • Confirm the project files exist by running:
ls

You should see main.tf, variables.tf, terraform.tfvars, and the .terraform.lock.hcl file in the output.

✔️ Awesome, I've got everything!

Great. Double check you have saved all your files.

ⓧ I'd like to double check the full code

Here is the complete content of each file for comparison:

terraform {
  required_providers {
    snowflake = {
      source  = "snowflakedb/snowflake"
      version = "~> 2.16"
    }
  }
}

provider "snowflake" {
  organization_name = var.snowflake_organization_name
  account_name      = var.snowflake_account_name
  user              = var.snowflake_user
  password          = var.snowflake_password
  role              = "ACCOUNTADMIN"
}
variable "snowflake_organization_name" {
  description = "Snowflake organization name"
  type        = string
}

variable "snowflake_account_name" {
  description = "Snowflake account name"
  type        = string
}

variable "snowflake_user" {
  description = "Snowflake username"
  type        = string
}

variable "snowflake_password" {
  description = "Snowflake password"
  type        = string
  sensitive   = true
}
snowflake_organization_name = "your-org-name"
snowflake_account_name      = "your-account-name"
snowflake_user              = "your-username"
snowflake_password          = "your-password-here"

Your tooling is locked and loaded. Next up, you will use Terraform to provision databases and schemas inside your Snowflake account.

Provision Databases and Schemas

Your tools are configured and your provider is initialized. Now it is time to build the data layer that everything else in the platform depends on.

Every Snowflake platform starts with databases and schemas. Databases are top-level containers that group related data. Schemas subdivide databases into logical sections for different stages of your data pipeline.

In this step, get ready to:
  • Define two databases (RAW and ANALYTICS) in Terraform.
  • Create schemas within each database for different pipeline stages.
  • Apply your configuration and verify the resources in Snowsight.
Define the RAW and ANALYTICS databases

A well-structured Snowflake platform separates raw ingested data from transformed analytics-ready data. You will create two databases to enforce this separation at the infrastructure level.

  • In Cursor, create a new file called databases.tf inside your snowflake-terraform project folder.
  • Add the two database resource definitions by pasting this code:
resource "snowflake_database" "raw" {
  name    = "RAW"
  comment = "Landing zone for raw ingested data"
}

resource "snowflake_database" "analytics" {
  name    = "ANALYTICS"
  comment = "Curated and reporting-ready data"
}

What does this code do?

Each snowflake_database resource creates a database in your Snowflake account. The name field sets the database identifier, and comment adds a description visible in Snowsight.

RAW is the landing zone where data arrives from external sources. ANALYTICS holds cleaned, modeled data ready for business reporting.

  • Save databases.tf.
  • You won't see a change in Snowsight yet. The next substep adds schemas, and then you will apply everything together.
Add schemas to each database

Schemas organize objects within a database. You will create four schemas that represent different stages of a data pipeline: ingestion and staging for RAW, curated and reporting for ANALYTICS.

  • In databases.tf, add the following schema resources below the two database blocks:
resource "snowflake_schema" "raw_ingestion" {
  database            = snowflake_database.raw.name
  name                = "INGESTION"
  with_managed_access = true
  comment             = "Raw data landing from external sources"
}

resource "snowflake_schema" "raw_staging" {
  database            = snowflake_database.raw.name
  name                = "STAGING"
  with_managed_access = true
  comment             = "Staged data ready for transformation"
}

resource "snowflake_schema" "analytics_curated" {
  database            = snowflake_database.analytics.name
  name                = "CURATED"
  with_managed_access = true
  comment             = "Cleaned and modeled data"
}

resource "snowflake_schema" "analytics_reporting" {
  database            = snowflake_database.analytics.name
  name                = "REPORTING"
  with_managed_access = true
  comment             = "Business-ready reporting views"
}

What does this code do?

Each snowflake_schema resource creates a schema inside the specified database. The database field references the parent database using Terraform's resource syntax (snowflake_database.raw.name). This creates an explicit dependency so Terraform knows to create the database first.

The with_managed_access = true setting enables managed access on each schema. This centralizes privilege management so that only the schema owner (or a role with MANAGE GRANTS) can grant access to objects inside it. Object owners cannot hand out their own permissions.

Why use managed access schemas?

In a regular schema, any object owner can grant other roles access to their tables. This makes it nearly impossible to audit who has access to what.

Managed access schemas solve this by removing that ability from object owners. Only a designated role can grant privileges, giving your platform team a single chokepoint for access control.

  • Save databases.tf.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

resource "snowflake_database" "raw" {
  name    = "RAW"
  comment = "Landing zone for raw ingested data"
}

resource "snowflake_database" "analytics" {
  name    = "ANALYTICS"
  comment = "Curated and reporting-ready data"
}

resource "snowflake_schema" "raw_ingestion" {
  database            = snowflake_database.raw.name
  name                = "INGESTION"
  with_managed_access = true
  comment             = "Raw data landing from external sources"
}

resource "snowflake_schema" "raw_staging" {
  database            = snowflake_database.raw.name
  name                = "STAGING"
  with_managed_access = true
  comment             = "Staged data ready for transformation"
}

resource "snowflake_schema" "analytics_curated" {
  database            = snowflake_database.analytics.name
  name                = "CURATED"
  with_managed_access = true
  comment             = "Cleaned and modeled data"
}

resource "snowflake_schema" "analytics_reporting" {
  database            = snowflake_database.analytics.name
  name                = "REPORTING"
  with_managed_access = true
  comment             = "Business-ready reporting views"
}
Apply the configuration and verify in Snowsight

With your databases and schemas defined, it is time to provision them in your Snowflake account. The terraform apply command reads your configuration, builds a plan of changes, and executes them after you confirm.

  • Run the apply command in your terminal:
terraform apply

What happens when you run this?

Terraform shows you an execution plan listing all resources it will create. It then displays a confirmation prompt asking you to type yes to proceed. Only typing yes will approve the changes.

  • Review the plan output. You should see 6 resources to add (2 databases and 4 schemas).
  • Type yes at the confirmation prompt and press Enter.

You should see output ending with a line like Apply complete! Resources: 6 added, 0 changed, 0 destroyed. confirming all resources were created.

Seeing an authentication error?

  • Double-check the values in your terraform.tfvars file. Your snowflake_organization_name and snowflake_account_name must match exactly what Snowflake shows in your account URL.
  • Make sure your password is correct and your user has the ACCOUNTADMIN role assigned.

Help me troubleshoot a Terraform Snowflake provider authentication error.

Now verify that the resources actually exist in your Snowflake account.

  • Switch to your Snowsight browser tab.
  • In the left navigation menu, select Catalog, then Database Explorer.
  • You should see both RAW and ANALYTICS in the database list.
  • Click on RAW to expand it. You should see the INGESTION and STAGING schemas.
  • Click on ANALYTICS to expand it. You should see the CURATED and REPORTING schemas.

Don't see your databases?

  • Make sure your active role in Snowsight is set to ACCOUNTADMIN. You can check and switch this in the bottom-left corner of Snowsight.
  • If your terraform apply succeeded but you still see nothing, try refreshing the page.

Help me figure out why my Terraform-provisioned databases are not showing in Snowsight.

Your data layer is in place. Next up, you will create warehouses that give different workloads their own isolated compute resources.

Create Workload-Isolated Warehouses

In the last step, you provisioned two databases and four schemas to organize your data into clear zones.

Now you need compute power to actually run queries against those databases. In Snowflake, compute is separate from storage. You provision virtual warehouses that spin up clusters of compute resources on demand. Different workloads need different amounts of power, so you will create three warehouses, each sized and configured for a specific job.

In this step, get ready to:
  • Define three warehouses in Terraform, each sized for its workload.
  • Apply the configuration and verify the warehouses appear in Snowsight.
  • Test that auto-suspend works by running a query and watching the warehouse state change.
Define three warehouses sized for their purpose

Each warehouse targets a specific workload pattern. A loading warehouse handles small, frequent ingestion jobs. A transform warehouse needs more power for heavier data processing. A reporting warehouse serves analyst queries that are typically lightweight.

  • Create a new file called warehouses.tf in your snowflake-terraform project folder.
  • Add the following code to define all three warehouses:
resource "snowflake_warehouse" "loading" {
  name           = "LOADING_WH"
  warehouse_type = "STANDARD"
  warehouse_size = "XSMALL"
  auto_suspend   = 60
  auto_resume    = true
  comment        = "Warehouse for data loading workloads"
}

resource "snowflake_warehouse" "transform" {
  name           = "TRANSFORM_WH"
  warehouse_type = "STANDARD"
  warehouse_size = "SMALL"
  auto_suspend   = 120
  auto_resume    = true
  comment        = "Warehouse for data transformation workloads"
}

resource "snowflake_warehouse" "reporting" {
  name           = "REPORTING_WH"
  warehouse_type = "STANDARD"
  warehouse_size = "XSMALL"
  auto_suspend   = 60
  auto_resume    = true
  comment        = "Warehouse for analyst queries and reporting"
}

What does this code do?

Each snowflake_warehouse resource creates a virtual warehouse with specific settings tailored to its workload.

  • warehouse_size controls the compute power. XSMALL is the smallest (1 credit/hour when running). SMALL doubles that to 2 credits/hour for heavier processing.
  • auto_suspend is the number of seconds of inactivity before the warehouse shuts down automatically. 60 seconds keeps costs low for bursty workloads.
  • auto_resume set to true means the warehouse starts back up automatically when a query arrives. No manual intervention needed.

Why isolate warehouses by workload?

If all workloads share one warehouse, a heavy transformation job can starve analyst queries of compute resources. Isolating warehouses means each team's work runs independently without competing for the same cluster.

This also lets you set different cost controls per workload in the next step.

  • Save warehouses.tf.
Apply the configuration and verify in Snowsight

With the warehouse definitions saved, you can now apply them to your Snowflake account.

  • Apply the Terraform configuration by running this command in your terminal:
terraform apply
  • Terraform will show a plan to create 3 new resources. Type yes and press Enter to confirm.

What should I see?

Terraform outputs a message like Apply complete! Resources: 3 added, 0 changed, 0 destroyed. confirming all three warehouses were created.

  • Switch to Snowsight in your browser.
  • In the navigation menu on the left, select Compute, then Warehouses.
  • Confirm that LOADING_WH, TRANSFORM_WH, and REPORTING_WH all appear in the list.
  • Check the Size column. LOADING_WH and REPORTING_WH should show X-Small. TRANSFORM_WH should show Small.

Don't see your warehouses?

  • Make sure your active role is ACCOUNTADMIN in Snowsight. Other roles may not have visibility into all warehouses.
  • Check your terminal output for errors. A common issue is a typo in warehouse_size (it must be XSMALL not X-SMALL).
  • help me troubleshoot why my warehouses aren't appearing in Snowsight after terraform apply
Test auto-suspend by running a query

The auto_suspend setting tells the warehouse to shut down after a period of inactivity. You can verify this works by using one of your warehouses, then watching its status change from Started to Suspended.

  • In Snowsight, select Projects in the navigation menu, then Worksheets.
  • Click the + button to create a new SQL worksheet.
  • In the worksheet context selector (top right area), set the warehouse to REPORTING_WH.
  • Run the following query to activate the warehouse:
SELECT CURRENT_TIMESTAMP();
  • Navigate back to Compute, then Warehouses.
  • Find REPORTING_WH in the list. Its Status column should show Started.
  • Wait about 60 seconds without running any more queries.
  • Refresh the page. The Status for REPORTING_WH should change to Suspended.

Why does auto-suspend matter?

Snowflake charges credits only while a warehouse is running. Auto-suspend ensures you stop spending the moment a workload finishes. Combined with auto-resume, your warehouses wake up on demand and sleep when idle. You never pay for idle compute.

Warehouse still showing Started after 60 seconds?

  • Make sure you are not running any other queries on that warehouse. Any activity resets the inactivity timer.
  • Try refreshing the Snowsight page. The status updates when the page reloads.
  • help me understand why my Snowflake warehouse is not auto-suspending after the configured timeout

✔️ Awesome, I've got everything!

Great. Double check you have saved warehouses.tf and confirmed all three warehouses are visible in Snowsight.

ⓧ I'd like to double check the full code

resource "snowflake_warehouse" "loading" {
  name           = "LOADING_WH"
  warehouse_type = "STANDARD"
  warehouse_size = "XSMALL"
  auto_suspend   = 60
  auto_resume    = true
  comment        = "Warehouse for data loading workloads"
}

resource "snowflake_warehouse" "transform" {
  name           = "TRANSFORM_WH"
  warehouse_type = "STANDARD"
  warehouse_size = "SMALL"
  auto_suspend   = 120
  auto_resume    = true
  comment        = "Warehouse for data transformation workloads"
}

resource "snowflake_warehouse" "reporting" {
  name           = "REPORTING_WH"
  warehouse_type = "STANDARD"
  warehouse_size = "XSMALL"
  auto_suspend   = 60
  auto_resume    = true
  comment        = "Warehouse for analyst queries and reporting"
}

Your compute layer is now in place. Three warehouses, each tuned for a specific workload, ready to wake on demand and sleep when idle. Next up, you will build an RBAC hierarchy to control exactly who can use each warehouse and access each database.

Build an RBAC Hierarchy

You have databases, schemas, and warehouses ready. But right now, anyone with ACCOUNTADMIN could access everything. In production, different teams need different levels of access.

In this step, you will build a Role-Based Access Control (RBAC) hierarchy that follows Snowflake best practices. You will create access roles that hold privileges on specific objects, then wrap those into functional roles that map to real job titles like analyst or engineer.

In this step, get ready to:
  • Define access roles and grant schema/table privileges to them.
  • Define functional roles and wire them into a role hierarchy under SYSADMIN.
  • Apply and verify the role hierarchy by switching roles in Snowsight.
Define access roles and grant schema/table privileges

Access roles are the lowest layer in the hierarchy. Each one holds privileges on a specific set of objects. You will create four access roles that separate read and write permissions for each database.

What are access roles vs functional roles?

Access roles are granted privileges directly on database objects (SELECT, INSERT, USAGE). They answer: "what can this role do?"

Functional roles map to job titles (analyst, engineer) and inherit one or more access roles. They answer: "what can this person do?" This two-layer pattern keeps your privilege assignments modular and easy to audit.

  • Create a new file called roles.tf in your Terraform project folder.
  • Add the four access role definitions by pasting this code:
# Access Roles - grant privileges on specific objects
resource "snowflake_account_role" "db_raw_read" {
  name    = "DB_RAW_READ"
  comment = "Read access to RAW database"
}

resource "snowflake_account_role" "db_raw_write" {
  name    = "DB_RAW_WRITE"
  comment = "Write access to RAW database"
}

resource "snowflake_account_role" "db_analytics_read" {
  name    = "DB_ANALYTICS_READ"
  comment = "Read access to ANALYTICS database"
}

resource "snowflake_account_role" "db_analytics_write" {
  name    = "DB_ANALYTICS_WRITE"
  comment = "Write access to ANALYTICS database"
}

What does this code do?

Each snowflake_account_role resource creates a custom role in your Snowflake account. You have two per database: one for read-only access and one for write access.

These roles do not have any privileges yet. The next file will grant specific privileges to each one.

  • Save roles.tf.

Now you need to grant actual privileges to these access roles. You will create a separate file for all privilege grants to keep your project organized.

  • Create a new file called grants.tf in your project folder.
  • Add the database USAGE and schema USAGE grants by pasting this code:
# Database USAGE grants for access roles
resource "snowflake_grant_privileges_to_account_role" "raw_read_db_usage" {
  account_role_name = snowflake_account_role.db_raw_read.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "raw_write_db_usage" {
  account_role_name = snowflake_account_role.db_raw_write.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_read_db_usage" {
  account_role_name = snowflake_account_role.db_analytics_read.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.analytics.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_write_db_usage" {
  account_role_name = snowflake_account_role.db_analytics_write.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.analytics.name
  }
}

# Schema USAGE grants
resource "snowflake_grant_privileges_to_account_role" "raw_read_schema_usage" {
  account_role_name = snowflake_account_role.db_raw_read.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "raw_write_schema_usage" {
  account_role_name = snowflake_account_role.db_raw_write.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_read_schema_usage" {
  account_role_name = snowflake_account_role.db_analytics_read.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.analytics.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_write_schema_usage" {
  account_role_name = snowflake_account_role.db_analytics_write.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.analytics.name
  }
}

What does this code do?

Before a role can query tables inside a database, it needs USAGE on the database itself and USAGE on the schemas inside it. Think of USAGE as the key that unlocks the door to see what is inside.

The on_schema block with all_schemas_in_database grants USAGE on every schema in one go. This covers existing schemas and any new ones you add later.

  • Add the table-level grants for future tables below the schema USAGE section in grants.tf:
# SELECT grants for read roles (future tables)
resource "snowflake_grant_privileges_to_account_role" "raw_read_select" {
  account_role_name = snowflake_account_role.db_raw_read.name
  privileges        = ["SELECT"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.raw.name
    }
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_read_select" {
  account_role_name = snowflake_account_role.db_analytics_read.name
  privileges        = ["SELECT"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.analytics.name
    }
  }
}

# Write grants for write roles (future tables)
resource "snowflake_grant_privileges_to_account_role" "raw_write_tables" {
  account_role_name = snowflake_account_role.db_raw_write.name
  privileges        = ["INSERT", "UPDATE", "DELETE", "TRUNCATE"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.raw.name
    }
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_write_tables" {
  account_role_name = snowflake_account_role.db_analytics_write.name
  privileges        = ["INSERT", "UPDATE", "DELETE", "TRUNCATE"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.analytics.name
    }
  }
}

Why grant on future tables?

The future block means these privileges apply automatically to any table created in the database from now on. Without it, you would need to run terraform apply every time someone adds a table.

Read roles get SELECT only. Write roles get INSERT, UPDATE, DELETE, and TRUNCATE for full data manipulation.

  • Save grants.tf.

Seeing reference errors in your editor?

If your editor highlights snowflake_account_role.db_raw_read.name as unresolved, that is expected. Terraform resolves cross-file references at plan time, not in the editor. These will resolve when you run terraform apply later.

Help me fix cross-file references in Terraform

Define functional roles and wire up the hierarchy

Functional roles map to real people. An analyst needs read access to analytics data. An engineer needs read and write access across both databases. A platform admin inherits everything the engineer has.

  • Return to roles.tf and add the functional role definitions, the access-role-to-functional-role grants, and the SYSADMIN hierarchy below the access roles:
# Functional Roles - assigned to users
resource "snowflake_account_role" "analyst" {
  name    = "ANALYST"
  comment = "Business analyst role with read access to analytics"
}

resource "snowflake_account_role" "engineer" {
  name    = "ENGINEER"
  comment = "Data engineer role with read/write access"
}

resource "snowflake_account_role" "platform_admin" {
  name    = "PLATFORM_ADMIN"
  comment = "Platform administrator role"
}

# Grant access roles to functional roles
resource "snowflake_grant_account_role" "analyst_analytics_read" {
  role_name        = snowflake_account_role.db_analytics_read.name
  parent_role_name = snowflake_account_role.analyst.name
}

resource "snowflake_grant_account_role" "engineer_raw_read" {
  role_name        = snowflake_account_role.db_raw_read.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "engineer_raw_write" {
  role_name        = snowflake_account_role.db_raw_write.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "engineer_analytics_read" {
  role_name        = snowflake_account_role.db_analytics_read.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "engineer_analytics_write" {
  role_name        = snowflake_account_role.db_analytics_write.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "platform_admin_inherits_engineer" {
  role_name        = snowflake_account_role.engineer.name
  parent_role_name = snowflake_account_role.platform_admin.name
}

# Grant functional roles to SYSADMIN for hierarchy
resource "snowflake_grant_account_role" "analyst_to_sysadmin" {
  role_name        = snowflake_account_role.analyst.name
  parent_role_name = "SYSADMIN"
}

resource "snowflake_grant_account_role" "engineer_to_sysadmin" {
  role_name        = snowflake_account_role.engineer.name
  parent_role_name = "SYSADMIN"
}

resource "snowflake_grant_account_role" "platform_admin_to_sysadmin" {
  role_name        = snowflake_account_role.platform_admin.name
  parent_role_name = "SYSADMIN"
}

What does this code do?

  • ANALYST gets DB_ANALYTICS_READ. Analysts can query reporting data but cannot touch raw ingestion tables.
  • ENGINEER gets all four access roles. Engineers can read and write across both databases.
  • PLATFORM_ADMIN inherits ENGINEER directly. Anything ENGINEER can do, PLATFORM_ADMIN can do too.
  • All functional roles grant up to SYSADMIN. This follows Snowflake best practice so that SYSADMIN can manage all objects created by custom roles.
  • Save roles.tf.

Finally, functional roles need access to specific warehouses so they can actually run queries.

  • Return to grants.tf and add the warehouse USAGE grants at the bottom of the file:
# Warehouse USAGE grants
resource "snowflake_grant_privileges_to_account_role" "analyst_reporting_wh" {
  account_role_name = snowflake_account_role.analyst.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "WAREHOUSE"
    object_name = snowflake_warehouse.reporting.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "engineer_loading_wh" {
  account_role_name = snowflake_account_role.engineer.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "WAREHOUSE"
    object_name = snowflake_warehouse.loading.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "engineer_transform_wh" {
  account_role_name = snowflake_account_role.engineer.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "WAREHOUSE"
    object_name = snowflake_warehouse.transform.name
  }
}

What does this code do?

Without warehouse USAGE, a role cannot run any queries. The ANALYST role gets access to REPORTING_WH only. The ENGINEER role gets access to LOADING_WH and TRANSFORM_WH for data pipeline work.

This is workload isolation in action. Analysts cannot accidentally consume compute on the loading warehouse, and engineers have dedicated warehouses for their ETL jobs.

  • Save grants.tf.

✔️ Awesome, I've got everything!

Great. Double check you have saved both roles.tf and grants.tf.

ⓧ I'd like to double check the full code

Here is the full roles.tf:

# Access Roles - grant privileges on specific objects
resource "snowflake_account_role" "db_raw_read" {
  name    = "DB_RAW_READ"
  comment = "Read access to RAW database"
}

resource "snowflake_account_role" "db_raw_write" {
  name    = "DB_RAW_WRITE"
  comment = "Write access to RAW database"
}

resource "snowflake_account_role" "db_analytics_read" {
  name    = "DB_ANALYTICS_READ"
  comment = "Read access to ANALYTICS database"
}

resource "snowflake_account_role" "db_analytics_write" {
  name    = "DB_ANALYTICS_WRITE"
  comment = "Write access to ANALYTICS database"
}

# Functional Roles - assigned to users
resource "snowflake_account_role" "analyst" {
  name    = "ANALYST"
  comment = "Business analyst role with read access to analytics"
}

resource "snowflake_account_role" "engineer" {
  name    = "ENGINEER"
  comment = "Data engineer role with read/write access"
}

resource "snowflake_account_role" "platform_admin" {
  name    = "PLATFORM_ADMIN"
  comment = "Platform administrator role"
}

# Grant access roles to functional roles
resource "snowflake_grant_account_role" "analyst_analytics_read" {
  role_name        = snowflake_account_role.db_analytics_read.name
  parent_role_name = snowflake_account_role.analyst.name
}

resource "snowflake_grant_account_role" "engineer_raw_read" {
  role_name        = snowflake_account_role.db_raw_read.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "engineer_raw_write" {
  role_name        = snowflake_account_role.db_raw_write.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "engineer_analytics_read" {
  role_name        = snowflake_account_role.db_analytics_read.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "engineer_analytics_write" {
  role_name        = snowflake_account_role.db_analytics_write.name
  parent_role_name = snowflake_account_role.engineer.name
}

resource "snowflake_grant_account_role" "platform_admin_inherits_engineer" {
  role_name        = snowflake_account_role.engineer.name
  parent_role_name = snowflake_account_role.platform_admin.name
}

# Grant functional roles to SYSADMIN for hierarchy
resource "snowflake_grant_account_role" "analyst_to_sysadmin" {
  role_name        = snowflake_account_role.analyst.name
  parent_role_name = "SYSADMIN"
}

resource "snowflake_grant_account_role" "engineer_to_sysadmin" {
  role_name        = snowflake_account_role.engineer.name
  parent_role_name = "SYSADMIN"
}

resource "snowflake_grant_account_role" "platform_admin_to_sysadmin" {
  role_name        = snowflake_account_role.platform_admin.name
  parent_role_name = "SYSADMIN"
}

Here is the full grants.tf:

# Database USAGE grants for access roles
resource "snowflake_grant_privileges_to_account_role" "raw_read_db_usage" {
  account_role_name = snowflake_account_role.db_raw_read.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "raw_write_db_usage" {
  account_role_name = snowflake_account_role.db_raw_write.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_read_db_usage" {
  account_role_name = snowflake_account_role.db_analytics_read.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.analytics.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_write_db_usage" {
  account_role_name = snowflake_account_role.db_analytics_write.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "DATABASE"
    object_name = snowflake_database.analytics.name
  }
}

# Schema USAGE grants
resource "snowflake_grant_privileges_to_account_role" "raw_read_schema_usage" {
  account_role_name = snowflake_account_role.db_raw_read.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "raw_write_schema_usage" {
  account_role_name = snowflake_account_role.db_raw_write.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.raw.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_read_schema_usage" {
  account_role_name = snowflake_account_role.db_analytics_read.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.analytics.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_write_schema_usage" {
  account_role_name = snowflake_account_role.db_analytics_write.name
  privileges        = ["USAGE"]
  on_schema {
    all_schemas_in_database = snowflake_database.analytics.name
  }
}

# SELECT grants for read roles (future tables)
resource "snowflake_grant_privileges_to_account_role" "raw_read_select" {
  account_role_name = snowflake_account_role.db_raw_read.name
  privileges        = ["SELECT"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.raw.name
    }
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_read_select" {
  account_role_name = snowflake_account_role.db_analytics_read.name
  privileges        = ["SELECT"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.analytics.name
    }
  }
}

# Write grants for write roles (future tables)
resource "snowflake_grant_privileges_to_account_role" "raw_write_tables" {
  account_role_name = snowflake_account_role.db_raw_write.name
  privileges        = ["INSERT", "UPDATE", "DELETE", "TRUNCATE"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.raw.name
    }
  }
}

resource "snowflake_grant_privileges_to_account_role" "analytics_write_tables" {
  account_role_name = snowflake_account_role.db_analytics_write.name
  privileges        = ["INSERT", "UPDATE", "DELETE", "TRUNCATE"]
  on_schema_object {
    future {
      object_type_plural = "TABLES"
      in_database        = snowflake_database.analytics.name
    }
  }
}

# Warehouse USAGE grants
resource "snowflake_grant_privileges_to_account_role" "analyst_reporting_wh" {
  account_role_name = snowflake_account_role.analyst.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "WAREHOUSE"
    object_name = snowflake_warehouse.reporting.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "engineer_loading_wh" {
  account_role_name = snowflake_account_role.engineer.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "WAREHOUSE"
    object_name = snowflake_warehouse.loading.name
  }
}

resource "snowflake_grant_privileges_to_account_role" "engineer_transform_wh" {
  account_role_name = snowflake_account_role.engineer.name
  privileges        = ["USAGE"]
  on_account_object {
    object_type = "WAREHOUSE"
    object_name = snowflake_warehouse.transform.name
  }
}
Apply and verify the role hierarchy
  • Apply your new roles and grants by running this command in your terminal:
terraform apply

Terraform will show a plan to create 26 new resources (7 roles, 6 role grants, and 13 privilege grants). Type yes when prompted to confirm.

  • Wait for the apply to complete. You should see a message confirming all 26 resources were added with 0 changed and 0 destroyed.

Seeing permission errors?

If you see an error about insufficient privileges, confirm that the role field in your main.tf provider block is set to ACCOUNTADMIN. Creating roles and granting privileges requires the ACCOUNTADMIN role.

Help me debug Terraform permission errors

Now verify that the roles appear correctly in Snowsight and that the hierarchy works.

  • Switch to your Snowsight browser tab.
  • In the navigation menu, select your username in the lower-left corner to open the user menu.
  • Select your current role (likely ACCOUNTADMIN) to open the role selector.
  • You should see your new roles listed: ANALYST, ENGINEER, and PLATFORM_ADMIN.
  • Select ANALYST as your active role.
  • Navigate to Catalog in the navigation menu. You should see the ANALYTICS database but not be able to write to it.
  • Switch back to ACCOUNTADMIN using the same user menu flow.

Why grant functional roles to SYSADMIN?

Snowflake recommends that all custom roles ultimately roll up to SYSADMIN. This ensures SYSADMIN (and ACCOUNTADMIN above it) can manage all objects created by any custom role.

Without this hierarchy link, objects created by a custom role become orphaned from the admin hierarchy. No one except the role owner could manage them.

Your RBAC hierarchy is in place. Every functional role has exactly the access it needs, nothing more. Next up, you will add resource monitors to cap credit usage and prevent surprise bills.

Attach Resource Monitors for Cost Control

Your RBAC hierarchy is in place. Every role has the right access, and everything rolls up to SYSADMIN cleanly.

There is one gap remaining. Without spending limits, any warehouse can consume credits indefinitely. Resource monitors solve this by tracking credit usage per warehouse and automatically suspending compute when thresholds are exceeded.

In this step, get ready to:
  • Define resource monitors with credit quotas and tiered alert triggers.
  • Assign each monitor to its corresponding warehouse.
  • Verify the monitors appear in Snowsight with correct thresholds.
Define resource monitors with credit quotas and tiered triggers

Each resource monitor sets a monthly credit budget for a warehouse. When usage hits a threshold, Snowflake can notify you, suspend the warehouse gracefully, or suspend it immediately.

  • Create a new file called resource_monitors.tf in your snowflake-terraform project folder.
  • Paste the following code into resource_monitors.tf:
resource "snowflake_resource_monitor" "loading_monitor" {
  name         = "LOADING_MONITOR"
  credit_quota = 100
  frequency    = "MONTHLY"
  start_timestamp = "IMMEDIATELY"

  notify_triggers         = [75]
  suspend_trigger         = 90
  suspend_immediate_trigger = 100
}

resource "snowflake_resource_monitor" "transform_monitor" {
  name         = "TRANSFORM_MONITOR"
  credit_quota = 200
  frequency    = "MONTHLY"
  start_timestamp = "IMMEDIATELY"

  notify_triggers         = [75]
  suspend_trigger         = 90
  suspend_immediate_trigger = 100
}

resource "snowflake_resource_monitor" "reporting_monitor" {
  name         = "REPORTING_MONITOR"
  credit_quota = 100
  frequency    = "MONTHLY"
  start_timestamp = "IMMEDIATELY"

  notify_triggers         = [75]
  suspend_trigger         = 90
  suspend_immediate_trigger = 100
}

What does this code do?

Each monitor defines a monthly credit budget and three escalating actions:

  • notify_triggers = [75] sends an alert when 75% of credits are consumed.
  • suspend_trigger = 90 suspends the warehouse at 90%, letting running queries finish.
  • suspend_immediate_trigger = 100 kills all queries and suspends immediately at 100%.

The TRANSFORM_MONITOR has a higher credit_quota of 200 because transformation workloads typically require more compute than loading or reporting.

  • Save resource_monitors.tf.

Seeing a syntax error?

Make sure each notify_triggers value is wrapped in square brackets (it expects a list). The suspend_trigger and suspend_immediate_trigger fields are plain integers without brackets.

Help me fix a syntax error in my resource_monitors.tf file.

Assign resource monitors to warehouses

Resource monitors only begin tracking when they are attached to a warehouse. You will add a resource_monitor field to each warehouse resource in warehouses.tf.

  • Open warehouses.tf in your editor.
  • In the snowflake_warehouse.loading resource block, find the auto_resume line:
  auto_resume    = true
  • Add this line directly below it:
  resource_monitor = snowflake_resource_monitor.loading_monitor.name
  • In the snowflake_warehouse.transform resource block, find the auto_resume line and add this below it:
  resource_monitor = snowflake_resource_monitor.transform_monitor.name
  • In the snowflake_warehouse.reporting resource block, find the auto_resume line and add this below it:
  resource_monitor = snowflake_resource_monitor.reporting_monitor.name

Why attach monitors at the warehouse level?

Resource monitors can be set at the account level or the warehouse level. Warehouse-level monitors give you granular control. Each workload type gets its own budget, so a runaway loading job cannot eat into your reporting budget.

  • Save warehouses.tf.

Not sure you got the placement right?

The resource_monitor field must sit inside the resource block braces, at the same indentation level as the other fields. If Terraform complains about an unexpected attribute, check that the line is not outside the closing }.

Help me fix the resource_monitor placement in my warehouses.tf.

✔️ Awesome, I've got everything!

Great. Double check you have saved both resource_monitors.tf and warehouses.tf.

ⓧ I'd like to double check the full code

Here is the complete resource_monitors.tf:

resource "snowflake_resource_monitor" "loading_monitor" {
  name         = "LOADING_MONITOR"
  credit_quota = 100
  frequency    = "MONTHLY"
  start_timestamp = "IMMEDIATELY"

  notify_triggers         = [75]
  suspend_trigger         = 90
  suspend_immediate_trigger = 100
}

resource "snowflake_resource_monitor" "transform_monitor" {
  name         = "TRANSFORM_MONITOR"
  credit_quota = 200
  frequency    = "MONTHLY"
  start_timestamp = "IMMEDIATELY"

  notify_triggers         = [75]
  suspend_trigger         = 90
  suspend_immediate_trigger = 100
}

resource "snowflake_resource_monitor" "reporting_monitor" {
  name         = "REPORTING_MONITOR"
  credit_quota = 100
  frequency    = "MONTHLY"
  start_timestamp = "IMMEDIATELY"

  notify_triggers         = [75]
  suspend_trigger         = 90
  suspend_immediate_trigger = 100
}

Here is the complete warehouses.tf:

resource "snowflake_warehouse" "loading" {
  name             = "LOADING_WH"
  warehouse_type   = "STANDARD"
  warehouse_size   = "XSMALL"
  auto_suspend     = 60
  auto_resume      = true
  resource_monitor = snowflake_resource_monitor.loading_monitor.name
  comment          = "Warehouse for data loading workloads"
}

resource "snowflake_warehouse" "transform" {
  name             = "TRANSFORM_WH"
  warehouse_type   = "STANDARD"
  warehouse_size   = "SMALL"
  auto_suspend     = 120
  auto_resume      = true
  resource_monitor = snowflake_resource_monitor.transform_monitor.name
  comment          = "Warehouse for data transformation workloads"
}

resource "snowflake_warehouse" "reporting" {
  name             = "REPORTING_WH"
  warehouse_type   = "STANDARD"
  warehouse_size   = "XSMALL"
  auto_suspend     = 60
  auto_resume      = true
  resource_monitor = snowflake_resource_monitor.reporting_monitor.name
  comment          = "Warehouse for analyst queries and reporting"
}
Apply and verify resource monitors in Snowsight
  • Apply both changes at once by running this command in your terminal:
terraform apply
  • When prompted to confirm, type yes and press Enter.

Terraform creates the three resource monitors and updates each warehouse to reference its assigned monitor. You should see output showing 3 resources added and 3 resources modified.

Seeing a permission error?

Resource monitors require the ACCOUNTADMIN role to create and assign. Check that your main.tf provider block has role = "ACCOUNTADMIN" set.

Help me fix a permission error when creating resource monitors in Snowflake with Terraform.

  • Switch to your Snowsight browser tab.
  • In the left sidebar, click Admin.
  • Click Cost Management.
  • Click Resource Monitors.

You should see LOADING_MONITOR, TRANSFORM_MONITOR, and REPORTING_MONITOR listed. Each monitor displays its credit quota, frequency, and the warehouse it is assigned to.

What do the trigger percentages mean in practice?

At 75% of the monthly quota, Snowflake sends a notification (visible in the Snowsight notification bell and any configured email). At 90%, running queries finish but no new queries start. At 100%, all activity stops immediately.

This tiered approach gives teams a warning window to investigate before compute is cut off entirely.

Your platform now has automated guardrails that prevent credit overruns. Next up, you will tackle the Secret Mission and add data masking to protect sensitive columns from unauthorized roles.

Secret mission

Add a Data Masking Policy

Your RBAC hierarchy controls who can access which databases and schemas. But what about protecting sensitive data within the tables themselves? In this secret mission, add a dynamic data masking policy that hides email addresses from analysts while letting engineers see the full values.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your Snowflake trial includes $400 in free credits for 30 days, after which standard charges apply.

Cost warning

Your Snowflake trial account is free for 30 days. After the trial expires, any remaining resources will incur charges. Warehouses auto-suspend when idle, so costs stay minimal while the trial is active.

If you do not plan to continue using Snowflake after this project, choose Delete below to remove all resources and close the account.

Resources you used:

  • Snowflake objects (databases, schemas, warehouses, roles, resource monitors, masking policies) managed by Terraform.
  • Snowflake Enterprise Edition trial account (30-day free trial with $400 in credits).
  • Local Terraform project files on your Desktop.

Keep everything running

No action needed. Choose this if you are still actively building on your Snowflake platform or plan to continue experimenting with Terraform and Snowflake.

  • Your warehouses auto-suspend after 60-120 seconds of inactivity, so credit usage stays minimal while idle.
  • The trial includes $400 in credits, which is more than enough for typical learning workloads across 30 days.
  • Your local Terraform files remain on your Desktop for future use.

Pause - I'll come back to this later

Shut down active compute to save credits while keeping your configuration intact.

  • Your warehouses already auto-suspend after 60-120 seconds of inactivity. No manual action is required to pause compute costs.
  • All Terraform state files and configuration remain on your Desktop. You can run terraform apply again at any time to reconcile changes.
  • Your Snowflake trial clock continues to run. You still have 30 days from the date you signed up before the trial expires.

Delete - I don't want to use this again

Remove all project resources and start fresh. This cannot be undone.

  • Destroy all Snowflake objects by running this command in your terminal from your project folder:
terraform destroy

What does this command do?

This command removes every resource Terraform created in your Snowflake account. It deletes the databases, schemas, warehouses, roles, resource monitors, grants, and masking policies. Terraform prompts you to confirm before proceeding.

  • Type yes when Terraform asks you to confirm the destroy action.
  • Delete your local project files by running:
cd ~/Desktop
rm -rf [[PROJECT_FOLDER="your Terraform project folder name"]]

On Windows, use this command instead:

cd ~\Desktop
Remove-Item -Recurse -Force [[PROJECT_FOLDER="your Terraform project folder name"]]
  • To close your Snowflake trial account entirely, contact Snowflake Support. Account cancellation is not available through the Snowflake web interface.

No rush to close the account

If you ran terraform destroy successfully, all billable resources are gone. The empty trial account does not cost anything and will automatically expire after 30 days.

Nice Work!

Nice Work!

Nice work! You just built a production-grade Snowflake platform from scratch using Infrastructure as Code. One terraform apply to create your entire environment, one terraform destroy to tear it all down.

You've learned how to:

  • Provision databases, managed access schemas, and workload-isolated warehouses with auto-suspend for cost efficiency.
  • Build a proper RBAC hierarchy with access roles and functional roles following least-privilege principles.
  • Attach resource monitors with tiered triggers to automatically suspend warehouses before credit overruns.
  • Added dynamic data masking to protect sensitive data based on role membership.

Ready to quiz yourself?