
Terraform Gcp
- 136 installs
- 44 repo stars
- Updated May 22, 2026
- bagelhole/devops-security-agent-skills
Author and apply Terraform for GCP IAM, VPC networking, GKE, Cloud SQL, storage, and remote state backends with security-hardened module patterns under agent assistance.
About
Terraform skill for Google Cloud Platform guiding module structure, provider setup, IAM bindings, networking, managed services like GKE and Cloud SQL, and reproducible IaC workflows for agent-assisted cloud provisioning.
- GCP module layout
- IAM least privilege
- Remote state backends
- GKE cluster provisioning
- Network and SQL resources
Terraform Gcp by the numbers
- 136 all-time installs (skills.sh)
- Ranked #479 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill terraform-gcpAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 136 |
|---|---|
| repo stars | ★ 44 |
| Last updated | May 22, 2026 |
| Repository | bagelhole/devops-security-agent-skills ↗ |
What it does
Author and apply Terraform for GCP IAM, VPC networking, GKE, Cloud SQL, storage, and remote state backends with security-hardened module patterns under agent assistance.
Files
Terraform GCP
Provision and manage Google Cloud Platform infrastructure using Terraform with the hashicorp/google provider.
When to Use
- Defining GCP infrastructure as code for repeatable, auditable deployments
- Managing multi-environment setups (dev, staging, production) from a single codebase
- Provisioning complex resource graphs (VPC + GKE + Cloud SQL + IAM) in one plan
- Integrating infrastructure changes into CI/CD pipelines with plan/apply stages
Prerequisites
- Terraform >= 1.5 installed
- Google Cloud SDK or a service account key for CI
- A GCP project with billing enabled
gcloud auth application-default login # local dev
export GOOGLE_APPLICATION_CREDENTIALS="sa.json" # CI/CD
terraform versionProvider Configuration
# versions.tf
terraform {
required_version = ">= 1.5"
required_providers {
google = { source = "hashicorp/google"; version = "~> 5.0" }
google-beta = { source = "hashicorp/google-beta"; version = "~> 5.0" }
}
backend "gcs" { bucket = "my-project-tf-state"; prefix = "terraform/state" }
}
provider "google" { project = var.project_id; region = var.region }
provider "google-beta" { project = var.project_id; region = var.region }# variables.tf
variable "project_id" { type = string }
variable "region" { type = string; default = "us-central1" }
variable "environment" {
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Must be dev, staging, or production."
}
}Project Setup and State Bucket
gcloud storage buckets create gs://my-project-tf-state \
--location=us-central1 --uniform-bucket-level-access --public-access-prevention
gcloud storage buckets update gs://my-project-tf-state --versioning
terraform init
terraform plan -var="project_id=my-project" -var="environment=production" -out=tfplan
terraform apply tfplanresource "google_project_service" "apis" {
for_each = toset([
"compute.googleapis.com", "container.googleapis.com",
"sqladmin.googleapis.com", "servicenetworking.googleapis.com",
"cloudfunctions.googleapis.com", "run.googleapis.com",
"secretmanager.googleapis.com", "artifactregistry.googleapis.com",
])
project = var.project_id
service = each.value
disable_dependent_services = false
disable_on_destroy = false
}Networking Module
# modules/networking/main.tf
resource "google_compute_network" "vpc" {
name = "${var.environment}-vpc"
auto_create_subnetworks = false
routing_mode = "REGIONAL"
}
resource "google_compute_subnetwork" "main" {
name = "${var.environment}-main-subnet"
ip_cidr_range = var.subnet_cidr
region = var.region
network = google_compute_network.vpc.id
private_ip_google_access = true
log_config { aggregation_interval = "INTERVAL_5_SEC"; flow_sampling = 0.5 }
}
resource "google_compute_subnetwork" "gke" {
name = "${var.environment}-gke-subnet"
ip_cidr_range = var.gke_subnet_cidr
region = var.region
network = google_compute_network.vpc.id
private_ip_google_access = true
secondary_ip_range { range_name = "pods"; ip_cidr_range = var.pods_cidr }
secondary_ip_range { range_name = "services"; ip_cidr_range = var.services_cidr }
}
resource "google_compute_firewall" "allow_iap" {
name = "${var.environment}-allow-iap"
network = google_compute_network.vpc.name
allow { protocol = "tcp"; ports = ["22", "3389"] }
source_ranges = ["35.235.240.0/20"]
}
resource "google_compute_router" "router" {
name = "${var.environment}-router"
region = var.region
network = google_compute_network.vpc.id
}
resource "google_compute_router_nat" "nat" {
name = "${var.environment}-nat"
router = google_compute_router.router.name
region = var.region
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
log_config { enable = true; filter = "ERRORS_ONLY" }
}
output "vpc_id" { value = google_compute_network.vpc.id }
output "gke_subnet_id" { value = google_compute_subnetwork.gke.id }GKE Cluster Module
# modules/gke/main.tf
resource "google_container_cluster" "primary" {
name = "${var.environment}-cluster"
location = var.region
release_channel { channel = var.release_channel }
workload_identity_config { workload_pool = "${var.project_id}.svc.id.goog" }
network = var.vpc_name
subnetwork = var.gke_subnet_name
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
private_cluster_config {
enable_private_nodes = true
master_ipv4_cidr_block = "172.16.0.0/28"
}
network_policy { enabled = true }
logging_config { enable_components = ["SYSTEM_COMPONENTS", "WORKLOADS"] }
monitoring_config {
enable_components = ["SYSTEM_COMPONENTS", "WORKLOADS"]
managed_prometheus { enabled = true }
}
remove_default_node_pool = true
initial_node_count = 1
}
resource "google_container_node_pool" "primary" {
name = "primary-pool"
cluster = google_container_cluster.primary.name
location = var.region
initial_node_count = var.initial_node_count
autoscaling { min_node_count = var.min_nodes; max_node_count = var.max_nodes }
management { auto_repair = true; auto_upgrade = true }
node_config {
machine_type = var.machine_type
disk_size_gb = 100
oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
shielded_instance_config { enable_secure_boot = true; enable_integrity_monitoring = true }
metadata = { disable-legacy-endpoints = "true" }
}
}
output "cluster_name" { value = google_container_cluster.primary.name }
output "cluster_endpoint" { value = google_container_cluster.primary.endpoint; sensitive = true }Cloud SQL Module
# modules/cloud-sql/main.tf
resource "google_sql_database_instance" "main" {
name = "${var.environment}-db"
database_version = var.database_version
region = var.region
settings {
tier = var.tier
availability_type = var.environment == "production" ? "REGIONAL" : "ZONAL"
disk_type = "PD_SSD"
disk_size = var.disk_size
disk_autoresize = true
backup_configuration {
enabled = true
start_time = "02:00"
point_in_time_recovery_enabled = true
backup_retention_settings { retained_backups = var.environment == "production" ? 30 : 7 }
}
ip_configuration {
ipv4_enabled = false
private_network = var.vpc_id
require_ssl = true
}
database_flags { name = "max_connections"; value = var.max_connections }
}
deletion_protection = var.environment == "production"
depends_on = [var.private_vpc_connection]
}
resource "google_sql_database" "app" { name = var.database_name; instance = google_sql_database_instance.main.name }
resource "google_sql_user" "app" { name = var.db_user; instance = google_sql_database_instance.main.name; password = random_password.db.result }
resource "random_password" "db" { length = 32; special = true }
output "connection_name" { value = google_sql_database_instance.main.connection_name }
output "private_ip" { value = google_sql_database_instance.main.private_ip_address }IAM and Service Accounts
resource "google_service_account" "gke_nodes" {
account_id = "${var.environment}-gke-nodes"
display_name = "GKE Node Pool SA"
}
resource "google_project_iam_member" "gke_nodes" {
for_each = toset([
"roles/logging.logWriter", "roles/monitoring.metricWriter",
"roles/artifactregistry.reader",
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.gke_nodes.email}"
}
resource "google_service_account" "app" {
account_id = "${var.environment}-app"
display_name = "Application SA"
}
resource "google_service_account_iam_member" "workload_identity" {
service_account_id = google_service_account.app.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[myapp/app-ksa]"
}Root Module Composition
module "networking" {
source = "./modules/networking"
project_id = var.project_id
environment = var.environment
region = var.region
}
module "gke" {
source = "./modules/gke"
project_id = var.project_id
environment = var.environment
region = var.region
vpc_name = module.networking.vpc_id
gke_subnet_name = module.networking.gke_subnet_id
node_sa_email = google_service_account.gke_nodes.email
depends_on = [module.networking]
}
module "database" {
source = "./modules/cloud-sql"
project_id = var.project_id
environment = var.environment
region = var.region
vpc_id = module.networking.vpc_id
database_version = "POSTGRES_16"
tier = "db-custom-4-16384"
private_vpc_connection = module.networking.private_vpc_connection
depends_on = [module.networking]
}Environment Configuration
# environments/production.tfvars
project_id = "my-company-prod"
environment = "production"
region = "us-central1"terraform plan -var-file=environments/production.tfvars -out=tfplan
terraform apply tfplanCI/CD Integration
terraform init -input=false
terraform validate && terraform fmt -check
terraform plan -var-file=environments/${ENV}.tfvars -out=tfplan -input=false
terraform apply -input=false tfplan
# Import existing resources
terraform import google_compute_network.vpc projects/${PROJECT_ID}/global/networks/prod-vpc
# State management
terraform state list
terraform state mv google_compute_instance.old google_compute_instance.newTroubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Error 403: Access Not Configured | API not enabled | Add API to google_project_service resources |
Error acquiring the state lock | Concurrent run or stale lock | Run terraform force-unlock LOCK_ID after verification |
Resource already exists | Created outside Terraform | Import with terraform import |
Quota exceeded | Project quota too low | Request increase in Cloud Console > Quotas |
| Plan shows destroy/recreate | Changed force-new attribute | Use moved blocks or terraform state mv |
Backend initialization required | Changed backend config | Run terraform init -migrate-state |
| Cycle in resource graph | Circular references | Refactor with data sources; split applies |
Related Skills
- gcp-networking - VPC and firewall resources managed by Terraform
- gcp-gke - GKE cluster provisioning with Terraform modules
- gcp-cloud-sql - Cloud SQL instance management via Terraform
- gcp-compute - Compute Engine resources defined in Terraform
- gcp-cloud-functions - Serverless function deployment with Terraform
# GCP VPC Module Template
variable "network_name" {
description = "VPC network name"
type = string
default = "main"
}
# VPC Network
resource "google_compute_network" "main" {
name = "${var.project_name}-${var.environment}-vpc"
auto_create_subnetworks = false
project = var.project_id
}
# Public Subnet
resource "google_compute_subnetwork" "public" {
name = "${var.project_name}-${var.environment}-public"
ip_cidr_range = "10.0.1.0/24"
region = var.region
network = google_compute_network.main.id
project = var.project_id
secondary_ip_range {
range_name = "gke-pods"
ip_cidr_range = "10.1.0.0/16"
}
secondary_ip_range {
range_name = "gke-services"
ip_cidr_range = "10.2.0.0/20"
}
}
# Private Subnet
resource "google_compute_subnetwork" "private" {
name = "${var.project_name}-${var.environment}-private"
ip_cidr_range = "10.0.2.0/24"
region = var.region
network = google_compute_network.main.id
project = var.project_id
private_ip_google_access = true
}
# Cloud Router (for NAT)
resource "google_compute_router" "main" {
name = "${var.project_name}-${var.environment}-router"
region = var.region
network = google_compute_network.main.id
project = var.project_id
}
# Cloud NAT
resource "google_compute_router_nat" "main" {
name = "${var.project_name}-${var.environment}-nat"
router = google_compute_router.main.name
region = var.region
project = var.project_id
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}
# Firewall - Allow SSH
resource "google_compute_firewall" "allow_ssh" {
name = "${var.project_name}-${var.environment}-allow-ssh"
network = google_compute_network.main.name
project = var.project_id
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["ssh"]
}
# Firewall - Allow Internal
resource "google_compute_firewall" "allow_internal" {
name = "${var.project_name}-${var.environment}-allow-internal"
network = google_compute_network.main.name
project = var.project_id
allow {
protocol = "icmp"
}
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
source_ranges = ["10.0.0.0/8"]
}
output "network_name" {
value = google_compute_network.main.name
}
output "network_id" {
value = google_compute_network.main.id
}
output "subnet_ids" {
value = {
public = google_compute_subnetwork.public.id
private = google_compute_subnetwork.private.id
}
}
#!/bin/bash
# Terraform GCP Project Initialization Script
# Usage: ./tf-init-gcp.sh <project-name> <gcp-project-id> [region]
set -euo pipefail
PROJECT_NAME="${1:-}"
GCP_PROJECT="${2:-}"
REGION="${3:-us-central1}"
if [ -z "$PROJECT_NAME" ] || [ -z "$GCP_PROJECT" ]; then
echo "Usage: $0 <project-name> <gcp-project-id> [region]"
exit 1
fi
echo "========================================="
echo "Terraform GCP Project Setup"
echo "Project: $PROJECT_NAME"
echo "GCP Project: $GCP_PROJECT"
echo "Region: $REGION"
echo "========================================="
echo ""
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
# Create main.tf
cat > main.tf << EOF
terraform {
required_version = ">= 1.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
# Uncomment for remote state
# backend "gcs" {
# bucket = "${PROJECT_NAME}-tfstate"
# prefix = "terraform/state"
# }
}
provider "google" {
project = var.project_id
region = var.region
}
locals {
common_labels = {
project = var.project_name
environment = var.environment
managed-by = "terraform"
}
}
EOF
# Create variables.tf
cat > variables.tf << EOF
variable "project_name" {
description = "Project name for resource naming"
type = string
default = "${PROJECT_NAME}"
}
variable "project_id" {
description = "GCP Project ID"
type = string
default = "${GCP_PROJECT}"
}
variable "environment" {
description = "Environment (dev, staging, prod)"
type = string
default = "dev"
}
variable "region" {
description = "GCP region"
type = string
default = "${REGION}"
}
EOF
# Create outputs.tf
cat > outputs.tf << EOF
output "project_id" {
description = "GCP Project ID"
value = var.project_id
}
output "region" {
description = "GCP region"
value = var.region
}
EOF
# Create terraform.tfvars
cat > terraform.tfvars << EOF
project_name = "${PROJECT_NAME}"
project_id = "${GCP_PROJECT}"
environment = "dev"
region = "${REGION}"
EOF
# Create .gitignore
cat > .gitignore << EOF
.terraform/
*.tfstate
*.tfstate.*
*.tfvars.json
crash.log
*.tfplan
!terraform.tfvars.example
.idea/
*.swp
.vscode/
EOF
# Initialize Terraform
echo ""
echo "Initializing Terraform..."
terraform init
echo ""
echo "========================================="
echo "Project created successfully!"
echo "========================================="