Build Production-Ready Google Cloud Infrastructure with Terraform in 2025

Complete step-by-step guide to creating VPC networks, subnets, and storage buckets using Infrastructure as Code

Part 1 of our comprehensive 6-part Terraform on Google Cloud series – from beginner setup to advanced DevOps automation


Ready to transform your Google Cloud infrastructure management? This comprehensive guide kicks off our Terraform on Google Cloud series, where you’ll master professional-level cloud automation, GitHub Actions CI/CD, advanced security patterns, and production-ready DevOps practices.

By the end of this tutorial series, you’ll have hands-on experience with:

  • Infrastructure as Code fundamentals and best practices
  • Automated CI/CD pipelines with GitHub Actions and Workload Identity Federation
  • Production security with Secret Manager and key rotation
  • Serverless computing with Cloud Functions and Cloud Run
  • Big data processing with Cloud SQL and Dataproc

🚀 Perfect for cloud engineers, DevOps practitioners, and developers ready to level up their infrastructure automation game.


What You’ll Build in This Series

Part 1 (This Post): Foundation – VPC, Subnets, and Storage
Part 2: Compute Engine VMs with GitHub Actions CI/CD
Part 3: Secure PostgreSQL with Workload Identity Federation
Part 4: Secret Management and Automated Key Rotation
Part 5: Serverless FastAPI with Cloud Run
Part 6: Big Data Processing with Dataproc (Advanced)


Understanding Google Cloud Networking Fundamentals

What is a VPC in Google Cloud?

A Virtual Private Cloud (VPC) serves as your isolated network environment within Google Cloud:

  • 🔒 Network Isolation: Complete separation from other projects and tenants
  • 🛡️ Security Control: Fine-grained access control and firewall rules
  • 🌍 Global Resource: Automatically spans multiple regions worldwide
  • 📍 IP Management: Custom IP addressing, subnets, and routing control
  • 🔗 Connectivity: VPN, interconnect, and peering capabilities

What is a Subnet in Google Cloud?

Subnets are regional network segments within your VPC that:

  • Define IP address ranges (CIDR blocks) for your resources
  • Enable regional resource deployment and isolation
  • Provide traffic segmentation and security boundaries
  • Support private Google API access for enhanced security
  • Allow custom routing and firewall rule application

Pro Tip: Unlike AWS, Google Cloud subnets are regional (not zonal), giving you automatic high availability across zones within a region.


Prerequisites: Setting Up Your Environment

Before diving into Terraform automation, ensure you have:

Google Cloud Platform account (Sign up here)
Active GCP project with billing enabled
Terraform installed (Installation guide) – Version 1.0+ recommended
Google Cloud SDK (Download here) ✅ Basic understanding of cloud networking concepts

Verify Your Installation

# Check Google Cloud SDK
gcloud --version
gcloud init 

# Verify Terraform
terraform --version

# Check your active project
gcloud config get-value project

Google Cloud Authentication Setup

Creating a Service Account (Development Setup)

For this tutorial series, we’ll create a service account with owner permissions. Important: In production environments, always follow the principle of least privilege and use keyless authentication with Workload Identity Federation (covered in Part 3).

# Set your project ID (replace with your actual project ID)
export PROJECT_ID="your-gcp-project-id"
gcloud config set project $PROJECT_ID

# Create service account for Terraform
gcloud iam service-accounts create terraform-automation \
    --description="Service account for Terraform infrastructure automation" \
    --display-name="Terraform Automation SA"

# Grant owner role (development only - we'll improve this in later parts)
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:terraform-automation@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role="roles/owner"

# Generate service account key
gcloud iam service-accounts keys create terraform-sa-key.json \
    --iam-account terraform-automation@${PROJECT_ID}.iam.gserviceaccount.com

Set Environment Variables

# Set credentials for Terraform
export GOOGLE_APPLICATION_CREDENTIALS="terraform-sa-key.json"

# Verify authentication
gcloud auth application-default print-access-token

Project Structure: Organizing Your Terraform Code

Create a clean, maintainable project structure that will scale throughout our series:

mkdir terraform-gcp-foundation && cd terraform-gcp-foundation

# Create core Terraform files
touch main.tf variables.tf outputs.tf providers.tf terraform.tfvars

# Create resource-specific files
touch networking.tf storage.tf

# Create backend configuration
touch backend.tf

# Initialize git for version control
git init
echo "*.tfvars" >> .gitignore
echo "terraform-sa-key.json" >> .gitignore
echo ".terraform/" >> .gitignore
echo "*.tfstate*" >> .gitignore

Why this structure?

  • Separation of concerns: Each file has a specific purpose
  • Scalability: Easy to add new resources in dedicated files
  • Team collaboration: Clear organization for multiple developers
  • Version control ready: Proper .gitignore for sensitive files

Terraform Configuration Files Explained

1. Provider Configuration (providers.tf)

terraform {
  required_version = ">= 1.0"
  
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
    google-beta = {
      source  = "hashicorp/google-beta" 
      version = "~> 5.0"
    }
  }
}

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

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

2. Input Variables (variables.tf)

variable "project_id" {
  type        = string
  description = "Google Cloud Project ID"
  validation {
    condition     = length(var.project_id) > 6
    error_message = "Project ID must be more than 6 characters."
  }
}

variable "region" {
  type        = string
  description = "GCP region for resources"
  default     = "us-central1"
  validation {
    condition = contains([
      "us-central1", "us-east1", "us-west1", "us-west2",
      "europe-west1", "europe-west2", "asia-east1"
    ], var.region)
    error_message = "Region must be a valid GCP region."
  }
}

variable "environment" {
  type        = string
  description = "Environment name (dev, staging, prod)"
  default     = "dev"
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "vpc_name" {
  type        = string
  description = "Name for the VPC network"
  default     = "main-vpc"
}

variable "subnet_name" {
  type        = string
  description = "Name for the subnet"
  default     = "main-subnet"
}

variable "subnet_cidr" {
  type        = string
  description = "CIDR range for the subnet"
  default     = "10.0.1.0/24"
  validation {
    condition     = can(cidrhost(var.subnet_cidr, 0))
    error_message = "Subnet CIDR must be a valid IPv4 CIDR block."
  }
}

3. Networking Resources (networking.tf)

# VPC Network
resource "google_compute_network" "main_vpc" {
  name                    = "${var.environment}-${var.vpc_name}"
  project                 = var.project_id
  auto_create_subnetworks = false
  routing_mode           = "REGIONAL"
  mtu                    = 1460
  
  description = "Main VPC network for ${var.environment} environment"
  
  # Enable deletion protection in production
  delete_default_routes_on_create = false
}

# Subnet
resource "google_compute_subnetwork" "main_subnet" {
  name                     = "${var.environment}-${var.subnet_name}"
  project                  = var.project_id
  region                   = var.region
  network                  = google_compute_network.main_vpc.name
  ip_cidr_range           = var.subnet_cidr
  private_ip_google_access = true
  
  description = "Primary subnet in ${var.region} for ${var.environment}"
  
  # Enable flow logs for security monitoring
  log_config {
    aggregation_interval = "INTERVAL_10_MIN"
    flow_sampling       = 0.5
    metadata           = "INCLUDE_ALL_METADATA"
  }
  
  # Secondary IP ranges for future use (GKE, etc.)
  secondary_ip_range {
    range_name    = "pods"
    ip_cidr_range = "10.1.0.0/16"
  }
  
  secondary_ip_range {
    range_name    = "services"
    ip_cidr_range = "10.2.0.0/16"
  }
}

# Cloud Router for Cloud NAT (we'll use this in Part 2)
resource "google_compute_router" "main_router" {
  name    = "${var.environment}-cloud-router"
  project = var.project_id
  region  = var.region
  network = google_compute_network.main_vpc.id
  
  description = "Cloud Router for NAT gateway"
}

4. Storage Resources (storage.tf)

# Storage bucket for Terraform state (remote backend)
resource "google_storage_bucket" "terraform_state" {
  name     = "${var.project_id}-${var.environment}-terraform-state"
  project  = var.project_id
  location = var.region
  
  # Force destroy for development (disable in production)
  force_destroy = var.environment == "dev" ? true : false
  
  # Enable versioning for state file safety
  versioning {
    enabled = true
  }
  
  # Uniform bucket-level access for better security
  uniform_bucket_level_access = true
  
  # Encryption at rest
  encryption {
    default_kms_key_name = null # We'll add KMS in Part 4
  }
  
  # Lifecycle management to control costs
  lifecycle_rule {
    condition {
      age = 30
    }
    action {
      type = "Delete"
    }
  }
  
  lifecycle_rule {
    condition {
      age                   = 7
      with_state           = "ARCHIVED"
    }
    action {
      type = "Delete"
    }
  }
  
  # Labels for resource management
  labels = {
    environment = var.environment
    purpose     = "terraform-state"
    managed-by  = "terraform"
  }
}

# General purpose storage bucket
resource "google_storage_bucket" "app_storage" {
  name     = "${var.project_id}-${var.environment}-app-storage"
  project  = var.project_id
  location = var.region
  
  # Storage class optimization
  storage_class = "STANDARD"
  
  uniform_bucket_level_access = true
  
  # CORS configuration for web applications
  cors {
    origin          = ["*"]
    method          = ["GET", "HEAD", "PUT", "POST", "DELETE"]
    response_header = ["*"]
    max_age_seconds = 3600
  }
  
  labels = {
    environment = var.environment
    purpose     = "application-storage"
    managed-by  = "terraform"
  }
}

# IAM binding for service account access to state bucket
resource "google_storage_bucket_iam_member" "terraform_state_access" {
  bucket = google_storage_bucket.terraform_state.name
  role   = "roles/storage.admin"
  member = "serviceAccount:terraform-automation@${var.project_id}.iam.gserviceaccount.com"
}

5. Outputs (outputs.tf)

output "vpc_name" {
  value       = google_compute_network.main_vpc.name
  description = "Name of the created VPC"
}

output "vpc_id" {
  value       = google_compute_network.main_vpc.id
  description = "ID of the created VPC"
}

output "vpc_self_link" {
  value       = google_compute_network.main_vpc.self_link
  description = "Self-link of the VPC (useful for other resources)"
}

output "subnet_name" {
  value       = google_compute_subnetwork.main_subnet.name
  description = "Name of the created subnet"
}

output "subnet_cidr" {
  value       = google_compute_subnetwork.main_subnet.ip_cidr_range
  description = "CIDR range of the subnet"
}

output "subnet_gateway_address" {
  value       = google_compute_subnetwork.main_subnet.gateway_address
  description = "Gateway IP address of the subnet"
}

output "terraform_state_bucket" {
  value       = google_storage_bucket.terraform_state.name
  description = "Name of the Terraform state storage bucket"
}

output "app_storage_bucket" {
  value       = google_storage_bucket.app_storage.name
  description = "Name of the application storage bucket"
}

output "cloud_router_name" {
  value       = google_compute_router.main_router.name
  description = "Name of the Cloud Router (for NAT in Part 2)"
}

# Network details for use in subsequent parts
output "network_details" {
  value = {
    vpc_name    = google_compute_network.main_vpc.name
    subnet_name = google_compute_subnetwork.main_subnet.name
    region      = var.region
    project_id  = var.project_id
  }
  description = "Network configuration details for other Terraform configurations"
}

6. Variable Values (terraform.tfvars)

# Project Configuration
project_id  = "your-gcp-project-id"  # Replace with your actual project ID
environment = "dev"

# Network Configuration
region      = "us-central1"
vpc_name    = "main-vpc"
subnet_name = "primary-subnet"
subnet_cidr = "10.0.1.0/24"

7. Backend Configuration (backend.tf)

# Local backend for initial setup
terraform {
  backend "local" {
    path = "terraform.tfstate"
  }
}

# After creating the storage bucket, uncomment below and migrate:
# terraform {
#   backend "gcs" {
#     bucket = "your-project-id-dev-terraform-state"
#     prefix = "foundation/state"
#   }
# }

Understanding Terraform State Management

Terraform state is the crucial component that tracks your infrastructure:

  • 📊 Resource Metadata: Current state, IDs, and configurations
  • 🔗 Dependencies: Relationships and creation order between resources
  • 📝 Performance: Caches resource attributes for faster operations
  • 🎯 Drift Detection: Identifies manual changes made outside Terraform

State Storage Options

Local State (Development):

  • Stored on your local machine
  • Simple for learning and testing
  • Not suitable for team collaboration

Remote State (Production):

  • Google Cloud Storage (recommended for GCP)
  • Terraform Cloud (HashiCorp’s managed solution)
  • Amazon S3 (for multi-cloud scenarios)

Migrating to Remote State

After your first deployment, migrate to remote state:

# 1. Update backend.tf with your bucket name
# 2. Run migration command
terraform init -migrate-state

Deploying Your Infrastructure

Step 1: Initialize Terraform

terraform init

Downloads provider plugins and initializes the working directory

Step 2: Format and Validate

# Format code for consistency
terraform fmt -recursive

# Validate configuration
terraform validate

Step 3: Plan Your Deployment

terraform plan -out=tfplan

Creates an execution plan and saves it to a file

Understanding the Plan Output:

  • + Resources to be created
  • - Resources to be destroyed
  • ~ Resources to be modified
  • <= Resources to be read (data sources)

Step 4: Apply Changes

terraform apply tfplan

Executes the plan – no additional confirmation needed since plan was saved

Step 5: Verify Deployment

# Check outputs
terraform output

# Verify in GCP Console
gcloud compute networks list
gcloud compute networks subnets list --network=dev-main-vpc
gsutil ls gs://your-project-id-dev-terraform-state

Step 6: Clean Up (When Needed)

terraform destroy

⚠️ Removes all managed infrastructure – be extremely careful in production!


Production-Ready Best Practices

🔧 Code Organization & Standards

  • Modularize configurations for reusability across environments
  • Use consistent naming conventions with environment prefixes
  • Implement proper file structure with clear separation of concerns
  • Version pin providers and modules to avoid breaking changes

🔒 Security & Access Management

  • Never commit sensitive data or credentials to version control
  • Use remote state backend with proper access controls and encryption
  • Implement state locking to prevent concurrent modifications
  • Follow least privilege principle for service account permissions

📚 Documentation & Team Collaboration

  • Add meaningful descriptions to all resources and variables
  • Use version control (Git) with proper branching strategies
  • Implement peer reviews for all infrastructure changes
  • Document runbooks for common operations and troubleshooting

🚀 Automation & CI/CD (Coming in Part 2)

  • Integrate with GitHub Actions for automated deployments
  • Use environment-specific variable files and workspaces
  • Implement automated testing for infrastructure code
  • Set up monitoring and alerting for infrastructure changes

💰 Cost Optimization

  • Use labels and tags for resource tracking and cost allocation
  • Implement lifecycle policies for storage resources
  • Choose appropriate machine types and disk sizes
  • Monitor and set up budget alerts for cost control

What’s Coming Next in This Series

🔜 Part 2: Compute Engine VMs with GitHub Actions

  • Deploy secure VM instances with proper networking
  • Set up automated CI/CD pipelines with GitHub Actions
  • Implement firewall rules and Cloud NAT for secure internet access
  • Master service account management and authentication

🔜 Part 3: PostgreSQL with Workload Identity Federation

  • Deploy Cloud SQL PostgreSQL in private networks
  • Implement keyless authentication with Workload Identity Federation
  • Set up VPC peering and private service connections
  • Database security and backup configuration

🔜 Part 4: Secret Management and Key Rotation

  • Secure credential storage with Secret Manager
  • Automated service account key rotation with Cloud Functions
  • Production-grade security patterns
  • Integration with existing infrastructure

🔜 Part 5: Serverless FastAPI with Cloud Run

  • Containerize and deploy Python applications
  • Implement proper artifact registry workflows
  • Configure custom domains and SSL certificates
  • Performance optimization and scaling strategies

🔜 Part 6: Big Data with Dataproc (Advanced)

  • Set up managed Hadoop and Spark clusters
  • Configure preemptible instances for cost optimization
  • Data processing pipelines and job orchestration
  • Integration with Cloud Storage and BigQuery

Key Takeaways

Infrastructure as Code provides consistency, repeatability, and version control
Proper project structure makes maintenance and collaboration easier
Variable validation catches errors early in the development cycle
Remote state management is essential for team collaboration and production use
Security best practices should be implemented from day one, not as an afterthought

Ready to continue? This foundation sets you up for success in the remaining parts of our series. Each subsequent tutorial builds upon this infrastructure, adding more sophisticated patterns and production-ready features.


Troubleshooting Common Issues

Authentication Problems

# Re-authenticate if needed
gcloud auth application-default login
gcloud config set project YOUR_PROJECT_ID

API Enablement

# Enable required APIs
gcloud services enable compute.googleapis.com
gcloud services enable storage-api.googleapis.com

Permission Issues

# Verify service account permissions
gcloud projects get-iam-policy YOUR_PROJECT_ID \
  --flatten="bindings[].members" \
  --filter="bindings.members:serviceAccount:terraform-automation@YOUR_PROJECT_ID.iam.gserviceaccount.com"

Connect with me:

Tags: #Terraform #GoogleCloud #GCP #DevOps #InfrastructureAsCode #CloudAutomation #NetworkingSeries

Akhilesh Mishra

Akhilesh Mishra

I am Akhilesh Mishra, a self-taught Devops engineer with 11+ years working on private and public cloud (GCP & AWS)technologies.

I also mentor DevOps aspirants in their journey to devops by providing guided learning and Mentorship.

Topmate: https://topmate.io/akhilesh_mishra/