Terraform on GCP -- Complete Guide to Provisioning Google Cloud Infrastructure
Terraform on Google Cloud enables you to define and provision the full GCP infrastructure stack -- compute, networking, storage, databases, Kubernetes, and serverless -- using declarative HCL configuration files.
What You'll Learn
In this tutorial, you will learn how to configure the GCP provider, provision GCE instances with custom images, create VPC networks with firewall rules, manage Cloud Storage buckets with IAM, deploy Cloud SQL databases, set up GKE clusters, and implement GCP IAM with Terraform.
Why It Matters
GCP's console and gcloud CLI do not scale for multi-team, multi-project organizations. Terraform provides a unified workflow across all GCP services, with plan preview, state tracking, and automation through CI/CD pipelines.
Real-World Use
DodaTech uses Terraform on GCP for data analytics pipelines and machine learning workloads. Durga Antivirus Pro's malware detection engine runs on GKE, with Cloud Storage for sample storage and Cloud SQL for threat intelligence, all provisioned through Terraform.
GCP Provider Configuration
graph TD
A[Terraform GCP Config] --> B[Google Provider]
B --> C[Compute Engine]
B --> D[VPC Network]
B --> E[Cloud Storage]
B --> F[Cloud SQL]
B --> G[GKE]
B --> H[IAM]
C --> I[Instances]
C --> J[Instance Groups]
C --> K[Disks]
D --> L[Subnets]
D --> M[Firewall Rules]
D --> N[Cloud NAT]
E --> O[Buckets]
E --> P[IAM Policies]
style A fill:#4285f4,color:#fff
style B fill:#4285f4,color:#fff
Provider Setup with Workload Identity
# provider.tf
terraform {
required_version = ">= 1.6"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 6.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
# Workload Identity Federation for CI/CD
access_token = data.google_service_account_access_token.default.access_token
}
data "google_service_account_access_token" "default" {
target_service_account = var.terraform_sa_email
scopes = ["https://www.googleapis.com/auth/cloud-platform"]
lifetime = "600s"
}
gcloud auth application-default login
terraform init
Expected output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/google versions matching "~> 6.0"...
- Installing hashicorp/google v6.10.0...
- Installed hashicorp/google v6.10.0 (signed by HashiCorp)
GCE Instance with Custom VPC
# compute.tf
resource "google_compute_network" "main" {
name = "vpc-${var.environment}"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "main" {
name = "subnet-${var.environment}-${var.region}"
network = google_compute_network.main.id
region = var.region
ip_cidr_range = var.subnet_cidr
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/20"
}
private_ip_google_access = true
}
resource "google_compute_firewall" "allow_http" {
name = "fw-${var.environment}-allow-http"
network = google_compute_network.main.name
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["web-server"]
}
resource "google_compute_instance" "web" {
name = "web-${var.environment}-001"
machine_type = var.machine_type
zone = "${var.region}-a"
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts"
size = 50
type = "pd-standard"
}
}
network_interface {
subnetwork = google_compute_subnetwork.main.self_link
access_config {
# Ephemeral public IP
}
}
tags = ["web-server"]
metadata = {
environment = var.environment
managed-by = "terraform"
}
}
terraform plan
Expected output:
Terraform will perform the following actions:
# google_compute_instance.web will be created
+ resource "google_compute_instance" "web" {
+ name = "web-production-001"
+ machine_type = "e2-standard-2"
+ zone = "us-central1-a"
+ tags = ["web-server"]
+ boot_disk {
+ initialize_params {
+ image = "ubuntu-os-cloud/ubuntu-2204-lts"
+ size = 50
}
}
}
Plan: 3 to add, 0 to change, 0 to destroy.
Cloud Storage with IAM Conditions
# storage.tf
resource "google_storage_bucket" "data" {
name = "${var.project_id}-${var.environment}-data"
location = var.region
storage_class = var.environment == "production" ? "STANDARD" : "NEARLINE"
versioning {
enabled = true
}
lifecycle_rule {
condition {
age = 90
}
action {
type = "SetStorageClass"
storage_class = "ARCHIVE"
}
}
lifecycle_rule {
condition {
age = 365
}
action {
type = "Delete"
}
}
encryption {
default_kms_key_name = google_kms_crypto_key.bucket_key.id
}
uniform_bucket_level_access = true
}
resource "google_storage_bucket_iam_binding" "data_readers" {
bucket = google_storage_bucket.data.name
role = "roles/storage.objectViewer"
members = [
"serviceAccount:${google_service_account.data_processor.email}",
"group:data-engineers"@dodatech".com",
]
condition {
title = "business_hours"
description = "Only allow access during business hours"
expression = "request.time.getHours(\"America/New_York\") >= 9 && request.time.getHours(\"America/New_York\") <= 17"
}
}
GKE Cluster with Autopilot
# gke.tf
resource "google_container_cluster" "main" {
name = "gke-${var.environment}"
location = var.region
enable_autopilot = true
network = google_compute_network.main.id
subnetwork = google_compute_subnetwork.main.id
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
release_channel {
channel = var.environment == "production" ? "STABLE" : "REGULAR"
}
master_authorized_networks_config {
cidr_blocks {
cidr_block = var.admin_cidr
display_name = "Admin network"
}
}
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = false
master_ipv4_cidr_block = "172.16.0.0/28"
}
resource_labels = {
environment = var.environment
managed-by = "terraform"
}
}
gcloud container clusters get-credentials $(terraform output -raw cluster_name) --region us-central1
kubectl get nodes
Expected output:
NAME STATUS ROLES AGE VERSION
gk3-gke-production-default-pool-8a2f1c5c-2x3d Ready <none> 5m v1.30.3-gke.1234000
gk3-gke-production-default-pool-9b3e2d6d-1y4e Ready <none> 5m v1.30.3-gke.1234000
Cloud SQL for PostgreSQL
# database.tf
resource "google_sql_database_instance" "main" {
name = "sql-${var.environment}"
database_version = "POSTGRES_16"
region = var.region
settings {
tier = var.environment == "production" ? "db-custom-8-32768" : "db-custom-2-7680"
activation_policy = "ALWAYS"
disk_size = var.environment == "production" ? 500 : 100
disk_type = "PD_SSD"
disk_autoresize = true
backup_configuration {
enabled = true
start_time = "03:00"
point_in_time_recovery_enabled = true
transaction_log_retention_days = 7
retained_backups = var.environment == "production" ? 30 : 7
}
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.main.id
require_ssl = true
}
insights_config {
query_insights_enabled = true
record_application_tags = true
record_client_address = true
query_string_length = 4096
}
}
deletion_protection = var.environment == "production"
}
Common Mistakes
1. Forgetting to Enable GCP APIs
GCP APIs must be enabled per project. Use the google_project_service resource to enable required APIs in Terraform.
2. Not Using Service Accounts
Running Terraform with user credentials breaks in CI. Use dedicated service accounts with workload identity federation.
3. Missing VPC Subnet Mode
GCP VPCs default to auto-mode which creates subnets in every region. Use custom-mode VPCs for production control.
4. GKE Autopilot Resource Limits
Autopilot clusters impose resource limits on pods. Check the resource quota before deploying workloads.
5. Cloud SQL Public IP Exposure
Cloud SQL instances with public IPs are security risks. Always configure private_network and disable ipv4_enabled.
Practice Questions
1. How do you enable GCP APIs using Terraform?
Use the google_project_service resource with the API name (e.g., container.googleapis.com) and disable_on_destroy = false.
2. What is the difference between Autopilot and Standard GKE clusters? Autopilot manages node infrastructure automatically. Standard gives full control over node pools but requires more management.
3. How do you conditionally set Cloud SQL backup retention based on environment?
Use a conditional expression: var.environment == "production" ? 30 : 7 in the retained_backups argument.
4. Challenge: Write a Terraform configuration for a GKE cluster with workload identity, a Cloud Storage bucket with object lifecycle policies, and a Cloud SQL PostgreSQL database connected through a private VPC.
Mini Project: GCP Data Analytics Platform
Provision a VPC with private subnet and Cloud NAT, a GKE cluster with workload identity, a Cloud Storage bucket with versioning and lifecycle rules, a Cloud SQL PostgreSQL instance with private IP, and a service account with least-privilege IAM roles. Enable VPC Service Controls for data perimeter security.
Related Concepts
What's Next
Deploy GCP infrastructure with Terraform for your workloads, then implement Remote State for team collaboration. Study DevOps best practices for multi-cloud infrastructure management.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro