Home DevOps Terraform on GCP: State Management, Provider Config, and Modular Design
Intermediate 5 min · July 12, 2026

Terraform on GCP: State Management, Provider Config, and Modular Design

A production-focused guide to Terraform on GCP: State Management, Provider Config, and Modular Design on Google Cloud Platform..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 25 min
  • Terraform 1.5+, Google Cloud SDK (gcloud) installed and authenticated, a GCP project with billing enabled, basic knowledge of HCL syntax, familiarity with GCP resources (VPC, Compute Engine, IAM).
Quick Answer

Terraform on GCP requires remote state in Cloud Storage with versioning for team collaboration, explicit provider configuration with project/region to avoid cross-project accidents, and modular design with versioned modules for reusability. Use additive IAM resources (iam_member), store secrets in Secret Manager, and integrate with CI/CD for plan-on-PR / apply-on-merge workflows.

✦ Definition~90s read
What is Terraform on GCP?

Terraform on GCP is a declarative infrastructure-as-code approach that manages Google Cloud resources via HCL configuration files, state tracking, and modular design. It matters because it enforces reproducible, version-controlled infrastructure, eliminating manual drift and enabling team collaboration.

Terraform is like a blueprint and an automated construction crew for your cloud infrastructure.

Use it when you need to provision, update, or tear down GCP resources consistently across environments.

Plain-English First

Terraform is like a blueprint and an automated construction crew for your cloud infrastructure. Instead of clicking around in the GCP console to create servers and databases (which is like building a house by hand), you write a blueprint file and Terraform builds everything exactly as specified, every time, without forgetting anything.

You've just spent three hours debugging a production outage caused by a manual gcloud compute instances delete that orphaned a load balancer. The root cause? No state file, no audit trail, and a teammate who 'just needed to clean up quickly.' This is why Terraform on GCP isn't optional—it's survival. State management, provider configuration, and modular design aren't academic concepts; they're the difference between a self-healing pipeline and a pager storm. In this article, I'll show you how to structure Terraform for GCP so your infrastructure doesn't bite you at 3 AM.

Why Terraform on GCP Demands Rigorous State Management

Terraform's state file is the single source of truth for your infrastructure. On GCP, where resources like Cloud Storage buckets and Compute Engine instances have globally unique names, a corrupted or missing state file can lead to duplicate resources or accidental deletions. The default local state works for a single developer, but in a team environment, you need remote state locking to prevent concurrent modifications. GCP's Cloud Storage backend provides native state locking via object versioning and generation numbers. Without it, two team members running terraform apply simultaneously can overwrite each other's changes, leading to drift that's nearly impossible to reconcile. A production incident I witnessed involved a team using local state on a shared filesystem—a network hiccup caused a stale state file, and the next apply deleted a production database. Always use remote state with locking.

backend.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
terraform {
  backend "gcs" {
    bucket = "my-org-terraform-state"
    prefix = "prod/network"
  }
}

# Enable state locking via object versioning
# Bucket must have versioning enabled
resource "google_storage_bucket" "terraform_state" {
  name     = "my-org-terraform-state"
  location = "US"
  versioning {
    enabled = true
  }
  uniform_bucket_level_access = true
}
Output
Initializing the backend...
Successfully configured the backend "gcs"! Terraform will automatically use this backend for state operations.
⚠ State File Security
State files contain plaintext resource IDs and sometimes sensitive data like database passwords. Encrypt the bucket with CMEK and restrict access via IAM. Never commit state files to version control.
📊 Production Insight
A missing state lock caused a production database to be deleted when two engineers ran terraform apply simultaneously. Use force-unlock only as a last resort after verifying no operations are in progress.
🎯 Key Takeaway
Always use remote state with locking to prevent corruption and enable team collaboration.
gcp-terraform-iac THECODEFORGE.IO Terraform State Management Workflow on GCP Steps to manage state securely and avoid corruption Configure Remote Backend Set up GCS bucket for state storage Enable State Locking Use GCS object versioning and locks Isolate Environments Separate state per env via workspaces Share Outputs Safely Use remote state data sources Handle Sensitive Data Store secrets in Secret Manager ⚠ Default local state leads to corruption in teams Always use remote backend with locking THECODEFORGE.IO
thecodeforge.io
Gcp Terraform Iac

Provider Configuration: Avoiding the Default Trap

The Google provider requires a project, region, and credentials. Many tutorials use the default provider block without specifying these, relying on environment variables or the gcloud CLI's default application credentials. This works until you need to manage multiple projects or use service accounts with different permissions. Always explicitly configure the provider with a project and region to avoid accidental cross-project modifications. Use alias to manage multiple providers for different projects or regions within the same configuration. A common mistake is forgetting to set project in the provider block, causing Terraform to use the project from the gcloud config, which may not have the required APIs enabled. This leads to confusing 403 errors. Also, pin the provider version to avoid breaking changes—Google's API evolves fast.

provider.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

# Alias for multi-project management
provider "google" {
  alias   = "shared_services"
  project = "shared-prod-123"
  region  = "us-central1"
}
Output
No output. Provider configuration is validated during `terraform init`.
💡Service Account Best Practice
Create a dedicated service account for Terraform with minimal permissions (e.g., roles/compute.admin, roles/iam.serviceAccountUser). Use workload identity federation for CI/CD instead of static keys.
📊 Production Insight
A team used default credentials from gcloud CLI, which pointed to a dev project. When they ran terraform apply in CI, it deployed production resources into the dev project, causing a security audit failure.
🎯 Key Takeaway
Explicitly set project and region in the provider block to prevent cross-project accidents.

Modular Design: Structuring for Reusability and Clarity

Monolithic Terraform configurations become unmanageable as your GCP footprint grows. Modules encapsulate related resources (e.g., a VPC module, a GKE module) with input variables and outputs. This promotes reusability across environments and teams. A well-designed module has a clear interface: required inputs, optional inputs with sensible defaults, and outputs that expose only what consumers need. Avoid modules that are too granular (e.g., a module for a single firewall rule) or too broad (e.g., a module that creates an entire project). Use the source argument to reference local paths, the Terraform Registry, or Git repositories. For GCP, common modules include VPC, subnets, firewall rules, and GKE clusters. Always version your modules with Git tags and reference them by version.

modules/vpc/main.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
variable "network_name" {
  description = "Name of the VPC network"
  type        = string
}

variable "subnets" {
  description = "List of subnet configurations"
  type = list(object({
    name          = string
    ip_cidr_range = string
    region        = string
  }))
}

resource "google_compute_network" "vpc" {
  name                    = var.network_name
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "subnet" {
  for_each = { for s in var.subnets : s.name => s }
  name          = each.value.name
  ip_cidr_range = each.value.ip_cidr_range
  region        = each.value.region
  network       = google_compute_network.vpc.id
}

output "network_id" {
  value = google_compute_network.vpc.id
}

output "subnet_ids" {
  value = { for k, s in google_compute_subnetwork.subnet : k => s.id }
}
Output
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
network_id = "projects/my-project/global/networks/my-vpc"
subnet_ids = {
"subnet-a" = "projects/my-project/regions/us-central1/subnetworks/subnet-a"
"subnet-b" = "projects/my-project/regions/us-east1/subnetworks/subnet-b"
}
🔥Module Versioning
Always pin module versions using Git tags or registry versions. Avoid using source = "./modules/vpc" in production—use a remote source like git::https://github.com/org/terraform-gcp-vpc.git?ref=v1.2.0.
📊 Production Insight
A team used a local module path that was accidentally overwritten by a git pull, causing a production apply to use an older module version. Always use remote sources with version tags.
🎯 Key Takeaway
Modules should be reusable, versioned, and have a clear interface with inputs and outputs.
gcp-terraform-iac THECODEFORGE.IO GCP Terraform Provider Configuration Stack Layered approach to provider setup and modular design Provider Configuration Project ID | Region | Credentials State Management GCS Backend | State Locking | Workspaces Modular Design Root Module | Child Modules | Outputs Service APIs & Quotas Enable APIs | Quota Management | IAM Roles Secrets & Security Secret Manager | IAM Policies | Sensitive Variables THECODEFORGE.IO
thecodeforge.io
Gcp Terraform Iac

State Isolation: Environments and Workspaces

Managing multiple environments (dev, staging, prod) requires state isolation to prevent cross-environment contamination. Terraform workspaces provide a lightweight way to manage multiple states with the same configuration, but they have limitations: all resources share the same backend configuration, and workspace names become part of resource addresses. For GCP, a better approach is to use separate state files per environment, typically via different backend prefixes or entirely separate buckets. This allows different IAM policies per environment and prevents accidental prod modifications from a dev workspace. Use a directory structure like environments/dev/, environments/prod/ with their own backend.tf pointing to different prefixes. Avoid workspaces for environments with different configurations—use separate directories with symlinked modules.

environments/prod/backend.tfHCL
1
2
3
4
5
6
7
8
9
terraform {
  backend "gcs" {
    bucket = "my-org-terraform-state"
    prefix = "prod"
  }
}

# In dev environment, prefix would be "dev"
# This ensures complete state isolation
Output
Initializing the backend...
Backend configuration changed! Terraform will migrate state from local to GCS.
Successfully configured the backend "gcs"!
⚠ Workspace Pitfalls
Workspaces share the same backend configuration. If you use terraform workspace select prod, you're still using the same bucket and prefix. Use separate directories for true isolation.
📊 Production Insight
A team used workspaces and accidentally ran terraform destroy in the prod workspace because the CLI prompt didn't clearly indicate the current workspace. Separate directories prevent this.
🎯 Key Takeaway
Use separate state files per environment, not workspaces, to enforce isolation and different IAM policies.

Remote State Data Sources: Sharing Outputs Across Stacks

When you split infrastructure into multiple Terraform configurations (e.g., network, services, databases), you need a way to reference outputs from one stack in another. The terraform_remote_state data source reads the state file of another configuration, giving you access to its outputs. This is essential for GCP where resources like VPC networks and subnets are created in one stack and consumed by others. However, this creates a dependency chain: if the network stack's state is corrupted, all downstream stacks fail. To mitigate, use well-defined outputs and consider using a configuration management tool like Terragrunt for dependency ordering. Always use the config path with a backend configuration that matches the source stack.

services/main.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data "terraform_remote_state" "network" {
  backend = "gcs"
  config = {
    bucket = "my-org-terraform-state"
    prefix = "prod/network"
  }
}

resource "google_compute_instance" "web" {
  name         = "web-server"
  machine_type = "e2-medium"
  zone         = "us-central1-a"
  network_interface {
    network    = data.terraform_remote_state.network.outputs.network_id
    subnetwork = data.terraform_remote_state.network.outputs.subnet_ids["subnet-a"]
  }
}
Output
data.terraform_remote_state.network: Reading...
data.terraform_remote_state.network: Read complete after 1s
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
💡Output Naming Convention
Use consistent output names across stacks. Prefix outputs with the resource type (e.g., vpc_id, subnet_ids) to avoid collisions. Document outputs in a README.
📊 Production Insight
A downstream stack failed to apply because the network stack's state was locked by a long-running plan. Implement a retry mechanism or use Terragrunt's dependency management to handle this.
🎯 Key Takeaway
Use terraform_remote_state to share outputs between stacks, but be aware of dependency coupling.

Handling GCP Service APIs and Quotas

Before you can create GCP resources, the corresponding service APIs must be enabled. Terraform can enable APIs via the google_project_service resource, but this creates a chicken-and-egg problem: you need the API enabled to create resources, but you want Terraform to manage the API enablement. The solution is to enable required APIs outside of Terraform (e.g., via a bootstrap script) or use a separate Terraform configuration for API enablement. Additionally, GCP enforces quotas on API requests. Terraform's default parallelism can hit these quotas, causing 429 RESOURCE_EXHAUSTED errors. Reduce parallelism with the -parallelism flag or configure the provider's request_timeout and batching settings. For large deployments, use google_project_service with disable_on_destroy = false to avoid accidental API disablement.

api_enablement.tfHCL
1
2
3
4
5
6
7
8
9
10
11
resource "google_project_service" "compute" {
  service            = "compute.googleapis.com"
  disable_on_destroy = false
}

resource "google_project_service" "container" {
  service            = "container.googleapis.com"
  disable_on_destroy = false
}

# Run this configuration first, then apply the main infrastructure
Output
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
⚠ API Disablement Danger
Setting disable_on_destroy = true (default) will disable the API when you run terraform destroy, potentially breaking other resources that depend on it. Always set it to false for production.
📊 Production Insight
A team accidentally disabled the Compute Engine API during a terraform destroy of a test environment, which took down a production service that shared the same project. Always use disable_on_destroy = false.
🎯 Key Takeaway
Enable APIs outside of Terraform or in a separate configuration to avoid circular dependencies.

Managing IAM with Terraform on GCP

IAM is the backbone of GCP security, and Terraform provides several resources to manage it: google_project_iam_binding, google_project_iam_member, and google_project_iam_policy. The binding resource sets a role for a list of members, but it is authoritative—it overwrites any existing members for that role. The member resource is additive and safer for incremental changes. The policy resource is fully authoritative for the entire project, which is dangerous. For production, prefer google_project_iam_member for individual assignments and google_project_iam_binding only when you want full control over a role. Use google_service_account and google_service_account_iam_binding for service account management. Always use depends_on to ensure IAM resources are created before resources that need them.

iam.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
resource "google_service_account" "terraform_sa" {
  account_id   = "terraform-runner"
  display_name = "Terraform Runner Service Account"
}

resource "google_project_iam_member" "terraform_sa_compute_admin" {
  project = var.project_id
  role    = "roles/compute.admin"
  member  = "serviceAccount:${google_service_account.terraform_sa.email}"
}

resource "google_project_iam_member" "terraform_sa_storage_admin" {
  project = var.project_id
  role    = "roles/storage.admin"
  member  = "serviceAccount:${google_service_account.terraform_sa.email}"
}
Output
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
🔥IAM Policy Drift
Avoid google_project_iam_policy as it overwrites all IAM bindings. Use google_project_iam_member for additive changes to prevent accidental removal of manually added permissions.
📊 Production Insight
A team used google_project_iam_binding and accidentally removed a break-glass admin account that was manually added. This locked them out of the project during an incident.
🎯 Key Takeaway
Use additive IAM resources (iam_member) to avoid overwriting existing permissions.

Handling Secrets and Sensitive Data

Terraform state files contain all resource attributes in plaintext, including database passwords, API keys, and service account keys. Never store secrets in variables or output them. Use GCP's Secret Manager to store secrets and reference them via the google_secret_manager_secret_version data source. For service account keys, generate them outside Terraform and store the private key in Secret Manager. Alternatively, use workload identity federation to avoid static keys altogether. Mark outputs as sensitive = true to prevent them from being displayed in CLI output, but remember they are still stored in state. Encrypt the state bucket with CMEK and restrict access to the state file.

secrets.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data "google_secret_manager_secret_version" "db_password" {
  secret = "db-password"
}

resource "google_sql_database_instance" "main" {
  name             = "main-db"
  database_version = "POSTGRES_14"
  region           = "us-central1"
  settings {
    tier = "db-f1-micro"
    user_labels = {
      environment = "production"
    }
  }
  # Use the secret value
  root_password = data.google_secret_manager_secret_version.db_password.secret_data
}

output "db_instance_name" {
  value     = google_sql_database_instance.main.name
  sensitive = false
}
Output
data.google_secret_manager_secret_version.db_password: Reading...
data.google_secret_manager_secret_version.db_password: Read complete after 1s
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
db_instance_name = "main-db"
⚠ Secrets in State
Even with sensitive = true, secrets are stored in the state file. Use Secret Manager and restrict access to the state bucket to authorized personnel only.
📊 Production Insight
A developer accidentally committed a state file containing a production database password to a public GitHub repo. The password was rotated within minutes, but the incident highlighted the need for state file encryption and access controls.
🎯 Key Takeaway
Never hardcode secrets; use Secret Manager and mark outputs as sensitive.

Testing Terraform Configurations with Terratest

Testing infrastructure code is critical for catching errors before they hit production. Terratest is a Go library that allows you to write automated tests for Terraform. You can test that resources are created with the correct properties, that outputs are as expected, and that destruction works cleanly. For GCP, you can also test that instances are reachable or that IAM bindings are correct. Write tests for each module and run them in CI. Use terraform plan in tests to validate syntax and logic without creating resources. For integration tests, use a dedicated test project and clean up resources after each test run to avoid cost and quota issues.

vpc_test.goGO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package test

import (
	"testing"
	"github.com/gruntwork-io/terratest/modules/terraform"
	"github.com/stretchr/testify/assert"
)

func TestVPCModule(t *testing.T) {
	t.Parallel()

	terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
		TerraformDir: "../modules/vpc",
		Vars: map[string]interface{}{
			"network_name": "test-vpc",
			"subnets": []map[string]interface{}{
				{
					"name":          "test-subnet",
					"ip_cidr_range": "10.0.1.0/24",
					"region":        "us-central1",
				},
			},
		},
	})

	defer terraform.Destroy(t, terraformOptions)
	terraform.InitAndApply(t, terraformOptions)

	networkID := terraform.Output(t, terraformOptions, "network_id")
	assert.Contains(t, networkID, "test-vpc")

	subnetIDs := terraform.OutputMap(t, terraformOptions, "subnet_ids")
	assert.Contains(t, subnetIDs, "test-subnet")
}
Output
=== RUN TestVPCModule
--- PASS: TestVPCModule (45.23s)
PASS
ok github.com/myorg/terraform-gcp/tests 45.23s
💡Test Project Isolation
Use a separate GCP project for testing with limited quotas and automatic cleanup. Set GOOGLE_APPLICATION_CREDENTIALS to a test service account.
📊 Production Insight
A missing firewall rule was caught by a Terratest integration test that verified connectivity between instances. Without the test, the production deployment would have been unreachable.
🎯 Key Takeaway
Automate testing with Terratest to catch errors early and ensure infrastructure behaves as expected.

CI/CD Integration for Terraform on GCP

Running Terraform locally is fine for development, but production changes should go through CI/CD. Use a pipeline tool like Cloud Build, GitHub Actions, or GitLab CI to run terraform plan on pull requests and terraform apply on merges to the main branch. Store the service account key securely in the CI/CD secrets manager. Use terraform fmt and terraform validate as linting steps. For Cloud Build, you can use the hashicorp/terraform builder image. Implement approval gates for production applies. Always run terraform plan first and require a human to review the output before applying. Use terraform workspace or separate directories to target specific environments.

cloudbuild.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
steps:
- name: 'hashicorp/terraform:1.5.0'
  entrypoint: 'sh'
  args:
  - '-c'
  - |
    terraform init
    terraform validate
    terraform fmt -check
    terraform plan -out=tfplan
- name: 'hashicorp/terraform:1.5.0'
  entrypoint: 'sh'
  args:
  - '-c'
  - |
    terraform apply tfplan
  waitFor: ['-']
  # Only run on merge to main
  # Use Cloud Build triggers with branch filter
options:
  logging: CLOUD_LOGGING_ONLY
Output
Step #0: Terraform has been successfully initialized!
Step #0: Success! The configuration is valid.
Step #0: Plan: 1 to add, 0 to change, 0 to destroy.
Step #1: Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
🔥Plan Artifacts
Save the plan file as a build artifact so it can be reviewed later. Use terraform show tfplan to output the plan in a human-readable format.
📊 Production Insight
A team applied a change directly from a developer's laptop, bypassing CI/CD. The change introduced a misconfiguration that took down the production website for 30 minutes. Always enforce CI/CD.
🎯 Key Takeaway
Integrate Terraform into CI/CD with plan on PRs and apply on merges, with approval gates for production.

Troubleshooting Common GCP Terraform Errors

Even with best practices, errors happen. Common GCP-specific errors include: Error 403: Required 'compute.instances.create' permission (missing IAM), Error 409: Already exists (resource name collision), and Error 400: Invalid value (wrong API version or field). For IAM errors, verify the service account has the correct roles. For name collisions, use a naming convention that includes a unique suffix like a random ID. For API version issues, check the provider documentation for supported fields. Use terraform plan to preview changes and terraform state list to inspect existing resources. If state becomes inconsistent, use terraform import to bring resources under management. For debugging, enable verbose logging with TF_LOG=DEBUG.

debug.shBASH
1
2
3
4
5
6
7
8
9
10
# Enable debug logging
export TF_LOG=DEBUG
terraform apply

# Import an existing resource
export GOOGLE_PROJECT=my-project
terraform import google_compute_instance.my_instance projects/my-project/zones/us-central1-a/instances/my-instance

# Force unlock state (use with caution)
terraform force-unlock <LOCK_ID>
Output
2024/07/12 10:00:00 [DEBUG] [aws-sdk-go] DEBUG: Request...
...
Error: Error creating instance: googleapi: Error 403: Required 'compute.instances.create' permission for 'projects/my-project/zones/us-central1-a/instances/my-instance'
⚠ Force Unlock Danger
Only use force-unlock if you are certain no Terraform operation is running. Unlocking a locked state can cause state corruption if another process is active.
📊 Production Insight
A 403 error during a production deployment was caused by a missing compute.instances.create permission on the service account. The team had recently rotated the service account key without updating the IAM bindings.
🎯 Key Takeaway
Use debug logs and import to troubleshoot state and permission issues.
Local vs Remote State Management on GCP Trade-offs in state handling for Terraform on GCP Local State Remote State (GCS) Storage Location Local filesystem GCS bucket Team Collaboration Manual sharing, conflicts Automatic sync, locking State Locking Not supported Supported via GCS Security Exposed in repo Encrypted at rest Environment Isolation Manual directories Workspaces or folders Disaster Recovery No backup Versioned backups THECODEFORGE.IO
thecodeforge.io
Gcp Terraform Iac

Advanced: Using Terragrunt for DRY Configurations

Terragrunt is a thin wrapper around Terraform that helps keep configurations DRY (Don't Repeat Yourself). It allows you to define common configurations (backend, provider, variables) in a single terragrunt.hcl file and reuse them across multiple Terraform modules. For GCP, this is particularly useful for managing multiple environments or projects with similar infrastructure. Terragrunt also handles dependency ordering between modules, ensuring that the network module is applied before the services module. It supports remote state configuration automatically, reducing boilerplate. However, Terragrunt adds another tool to your stack and can be overkill for small projects. Use it when you have more than 5 Terraform configurations or multiple environments.

terragrunt.hclHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
locals {
  project_id = "my-project"
  region     = "us-central1"
}

remote_state {
  backend = "gcs"
  config = {
    bucket = "my-org-terraform-state"
    key    = "${path_relative_to_include()}/terraform.tfstate"
    prefix = "${local.project_id}"
  }
}

generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
provider "google" {
  project = "${local.project_id}"
  region  = "${local.region}"
}
EOF
}

# In each module directory, create a terragrunt.hcl that includes this root configuration
include {
  path = find_in_parent_folders()
}

# Dependency example
dependency "network" {
  config_path = "../network"
}

inputs = {
  network_id = dependency.network.outputs.network_id
}
Output
terragrunt apply
[terragrunt] 2024/07/12 10:00:00 Running command: terraform apply
...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
🔥Terragrunt Version Compatibility
Ensure Terragrunt version is compatible with your Terraform version. Check the Terragrunt documentation for compatibility matrix.
📊 Production Insight
A team with 20+ Terraform configurations used Terragrunt to centralize backend and provider configuration. This reduced configuration errors and made environment promotion consistent.
🎯 Key Takeaway
Use Terragrunt to reduce duplication in multi-module or multi-environment setups.
⚙ Quick Reference
12 commands from this guide
FileCommand / CodePurpose
backend.tfterraform {Why Terraform on GCP Demands Rigorous State Management
provider.tfterraform {Provider Configuration
modulesvpcmain.tfvariable "network_name" {Modular Design
environmentsprodbackend.tfterraform {State Isolation
servicesmain.tfdata "terraform_remote_state" "network" {Remote State Data Sources
api_enablement.tfresource "google_project_service" "compute" {Handling GCP Service APIs and Quotas
iam.tfresource "google_service_account" "terraform_sa" {Managing IAM with Terraform on GCP
secrets.tfdata "google_secret_manager_secret_version" "db_password" {Handling Secrets and Sensitive Data
vpc_test.go"testing"Testing Terraform Configurations with Terratest
cloudbuild.yamlsteps:CI/CD Integration for Terraform on GCP
debug.shexport TF_LOG=DEBUGTroubleshooting Common GCP Terraform Errors
terragrunt.hcllocals {Advanced

Key takeaways

1
Remote State with Locking
Always use a remote backend (GCS) with versioning enabled to prevent state corruption and enable team collaboration.
2
Explicit Provider Configuration
Set project and region in the provider block to avoid cross-project accidents; use aliases for multi-project management.
3
Modular Design
Encapsulate related resources into versioned modules with clear inputs and outputs to promote reusability and maintainability.
4
Secrets Management
Never store secrets in state or variables; use GCP Secret Manager and encrypt the state bucket with CMEK.

Common mistakes to avoid

3 patterns
×

Ignoring gcp terraform iac best practices

Symptom
Production incidents and unexpected behavior
Fix
Follow GCP documentation and implement proper monitoring and testing
×

Over-provisioning without rightsizing analysis

Symptom
Wasted cloud spend and poor resource utilization
Fix
Use committed use discounts and rightsize based on actual usage metrics
×

Skipping IAM least-privilege principles

Symptom
Security vulnerabilities and compliance violations
Fix
Implement custom roles with minimum required permissions and use conditions
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Why should you use a remote backend for Terraform state instead of local...
Q02SENIOR
What is the difference between `google_project_iam_binding`, `google_pro...
Q03SENIOR
How do you handle the chicken-and-egg problem of enabling GCP APIs with ...
Q04SENIOR
How would you structure Terraform directories for multiple environments ...
Q05SENIOR
How do you prevent secrets from leaking in Terraform state files?
Q01 of 05JUNIOR

Why should you use a remote backend for Terraform state instead of local state?

ANSWER
Local state prevents team collaboration and risks corruption from concurrent applies. A GCS backend with versioning enables state locking (via object generation numbers), audit history, and team access. Without it, two engineers running terraform apply simultaneously can overwrite each other's changes.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the difference between `google_project_iam_binding` and `google_project_iam_member`?
02
How do I handle state locking in GCP?
03
Can I use Terraform to enable GCP APIs?
04
How do I manage secrets in Terraform on GCP?
05
What is the best way to structure Terraform for multiple environments on GCP?
06
How do I test Terraform configurations for GCP?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
🔥

That's Google Cloud. Mark it forged?

5 min read · try the examples if you haven't

Previous
Deployment Manager (IaC)
48 / 55 · Google Cloud
Next
Cloud Monitoring