Terraform for Data Infrastructure cheat sheet
Managing warehouses, buckets, and Snowflake objects as code — state, modules, and the workflow that avoids drift.
Workflow
terraform init -backend-config=env/prod.hcl- Per-environment backend config keeps one codebase managing multiple state files without copy-pasted directories.
terraform plan -out=tfplan- Always save the plan and apply that artifact. Applying without a saved plan can execute a different change than reviewed.
terraform apply tfplan- Applies exactly what was reviewed. This is the difference between infrastructure as code and infrastructure as vibes.
terraform fmt -recursive && terraform validate- Cheap pre-commit checks that eliminate most review noise.
terraform plan -detailed-exitcode- Exit code 2 means changes pending. Use it in CI to detect drift on a schedule.
State
terraform { backend "s3" { bucket = "tf-state" key = "data-platform/prod.tfstate" use_lockfile = true encrypt = true } }- Remote state with locking. Local state on a laptop is how two engineers destroy each other's warehouses.
terraform state list- Inventory of what Terraform believes it owns. First command when plan wants to recreate something that exists.
terraform import snowflake_database.analytics ANALYTICS- Adopt existing objects instead of recreating them. Essential when codifying a platform built by hand.
terraform state rm module.old_warehouse- Stop managing a resource without destroying it. The safe path when splitting one state file into several.
moved blocks over state mv- Declarative refactoring in code, reviewable in the PR, instead of imperative state surgery nobody can audit.
Structure
modules/ snowflake-database/ s3-lake-bucket/ envs/ prod/ dev/- Modules for repeated patterns, thin env roots that pass variables. Avoid one giant root module.
for_each over a map, not count- count reindexes on removal and destroys unrelated resources. for_each keys are stable — always prefer it.
locals { layers = { bronze = 30, silver = 90, gold = 365 } }- Drive resource creation from data so adding a layer is a one-line change.
terraform_remote_state or data sources over hardcoded ARNs- Reference real outputs across stacks rather than pasting identifiers that silently go stale.
Snowflake provider
resource "snowflake_warehouse" "etl" { name = "ETL_WH" warehouse_size = "SMALL" auto_suspend = 60 auto_resume = true }- Warehouses as code makes auto_suspend a reviewed decision rather than an inherited default that burns credits.
resource "snowflake_grant_privileges_to_account_role" "read" { account_role_name = snowflake_account_role.analyst.name privileges = ["SELECT"] on_schema_object { future { object_type_plural = "TABLES", in_schema = "..." } } }- Future grants so new tables inherit access. Without them every new model needs a manual grant.
Manage roles and warehouses in Terraform; leave tables to dbt- The clean split. Terraform owns the container and permissions; dbt owns the objects inside it.
Provider version pinning: ~> 2.0- The Snowflake provider has made breaking changes across majors. Unpinned versions break CI without a code change.
Pitfalls
Secrets in .tfvars committed to Git- Use environment variables or a secret store. State files also contain secrets — encrypt the backend and restrict it.
Manual console changes- Drift makes the next plan destructive. Either import the change or revert it; never leave it unreconciled.
One state file for the whole company- A single blast radius and serialized applies. Split by lifecycle — network, warehouse, and per-team resources.
prevent_destroy on stateful resources- Lifecycle guard on buckets and databases. Turns an accidental destroy into a plan-time error.
From DataLane — tutorials at/blog, practice SQL live in theplayground.