Run terraform fmt
to format your code consistently
Use terraform validate
to check for syntax or semantic issues before apply
Adopt tflint
or similar linters to catch anti-patterns or unused code
These tools improve reliability and make collaboration smoother — and they're easy to integrate into a basic workflow or CI/CD pipeline.
Backend ready (even if local state)
Environment separation (dev/staging/prod)
Readable naming conventions
Inline documentation
Module-ready folder structure
Optional: start using terraform-docs or tflint
Best practices for deploying a platform on GCP with Terraform?
-
Project Structure
- Organize your Terraform code into logical modules
- Use a consistent directory structure:
terraform/ ├── environments/ │ ├── dev/ │ ├── staging/ │ └── prod/ ├── modules/ │ ├── networking/ │ ├── compute/ │ └── storage/ └── shared/
-
State Management
- Use remote state storage in GCS (Google Cloud Storage)
- Enable state locking using GCS Or S3 backend
- Example backend configuration:
terraform { backend "gcs" { bucket = "your-terraform-state-bucket" prefix = "terraform/state" } }
-
Security Best Practices
- Use service accounts with least privilege
- Store sensitive data in Secret Manager
- Enable VPC Service Controls
- Use private Google Access
- Implement proper IAM roles and permissions
-
Code Organization
- Use variables for configurable values
- Implement proper tagging/labeling
- Use data sources for existing resources
- Implement proper output values
- Use locals for computed values
-
Version Control
- Use semantic versioning for modules
- Implement proper branching strategy
- Use .gitignore for sensitive files
- Document your infrastructure code
-
CI/CD Integration
- Implement automated testing
- Use Terraform Cloud or similar for remote execution
- Implement proper validation steps
- Use workspaces for environment separation
-
Resource Management
- Use proper resource naming conventions
- Implement proper resource dependencies
- Use count/for_each for resource replication
- Implement proper resource lifecycle rules
-
Cost Management
- Use proper resource sizing
- Implement proper auto-scaling
- Use committed use discounts where appropriate
- Implement proper monitoring and alerting
-
Networking
- Use proper VPC design
- Implement proper firewall rules
- Use proper subnet design
- Implement proper routing
-
Monitoring and Logging
- Enable proper logging
- Implement proper monitoring
- Use proper alerting
- Implement proper tracing
-
Disaster Recovery
- Implement proper backup strategies
- Use proper replication
- Implement proper failover
- Use proper recovery procedures
-
Documentation
- Document your infrastructure
- Document your modules
- Document your variables
- Document your outputs
-
Testing
- Implement proper unit testing
- Implement proper integration testing
- Use proper test coverage
- Implement proper validation
-
Compliance
- Implement proper security controls
- Use proper compliance frameworks
- Implement proper auditing
- Use proper logging
-
Performance
- Use proper resource sizing
- Implement proper caching
- Use proper load balancing
- Implement proper scaling
terraform/
├── environments/
│ ├── dev/
│ ├── staging/
│ └── prod/
├── modules/
│ ├── networking/
│ ├── compute/
│ └── storage/
└── shared/
terraform {
backend "gcs" {
bucket = "your-terraform-state-bucket"
prefix = "terraform/state"
}
}
Networking
A comprehensive guide:
- VPC Design Best Practices
- Create separate VPCs for different environments (dev, staging, prod)
- Use proper CIDR ranges that don't overlap
- Implement proper subnet design
- Use proper naming conventions
Example
# modules/networking/main.tf
resource "google_compute_network" "vpc" {
name = var.vpc_name
auto_create_subnetworks = false
project = var.project_id
}
resource "google_compute_subnetwork" "subnet" {
for_each = var.subnets
name = each.value.name
ip_cidr_range = each.value.ip_cidr_range
region = each.value.region
network = google_compute_network.vpc.id
private_ip_google_access = true
dynamic "log_config" {
for_each = each.value.enable_flow_logs ? [1] : []
content {
aggregation_interval = "INTERVAL_5_SEC"
flow_sampling = 0.5
metadata = "INCLUDE_ALL_METADATA"
}
}
}
- Firewall Rules Best Practices
- Implement least privilege access
- Use proper naming conventions
- Implement proper tagging
- Use proper source/destination ranges
Example of firewall rules:
# modules/networking/firewall.tf
resource "google_compute_firewall" "allow_internal" {
name = "allow-internal"
network = google_compute_network.vpc.name
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
allow {
protocol = "icmp"
}
source_ranges = [var.vpc_cidr]
target_tags = ["internal"]
}
resource "google_compute_firewall" "allow_ssh" {
name = "allow-ssh"
network = google_compute_network.vpc.name
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["ssh"]
}
- Cloud NAT Best Practices
- Implement proper NAT gateway configuration
- Use proper IP allocation
- Implement proper logging
- Use proper routing
Example of Cloud NAT configuration:
# modules/networking/nat.tf
resource "google_compute_router" "router" {
name = "nat-router"
region = var.region
network = google_compute_network.vpc.id
}
resource "google_compute_router_nat" "nat" {
name = "nat-gateway"
router = google_compute_router.router.name
region = google_compute_router.router.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"
}
}
- VPC Peering Best Practices
- Implement proper peering configuration
- Use proper routing
- Implement proper security
- Use proper naming conventions
Example of VPC peering:
# modules/networking/peering.tf
resource "google_compute_network_peering" "peering" {
name = "peering-${var.peer_name}"
network = google_compute_network.vpc.id
peer_network = var.peer_network_id
export_custom_routes = true
import_custom_routes = true
}
- Cloud DNS Best Practices
- Implement proper DNS zones
- Use proper record sets
- Implement proper security
- Use proper naming conventions
Example of Cloud DNS configuration:
# modules/networking/dns.tf
resource "google_dns_managed_zone" "private_zone" {
name = "private-zone"
dns_name = "private.example.com."
description = "Private DNS zone"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.vpc.id
}
}
}
resource "google_dns_record_set" "a_record" {
name = "app.${google_dns_managed_zone.private_zone.dns_name}"
managed_zone = google_dns_managed_zone.private_zone.name
type = "A"
ttl = 300
rrdatas = ["10.0.0.1"]
}
- Load Balancing Best Practices
- Implement proper health checks
- Use proper backend services
- Implement proper SSL/TLS
- Use proper routing rules
Example of load balancer configuration:
# modules/networking/load_balancer.tf
resource "google_compute_health_check" "health_check" {
name = "health-check"
check_interval_sec = 5
timeout_sec = 5
http_health_check {
port = 80
}
}
resource "google_compute_backend_service" "backend" {
name = "backend-service"
protocol = "HTTP"
port_name = "http"
timeout_sec = 10
health_checks = [google_compute_health_check.health_check.id]
backend {
group = var.instance_group
}
}
- Variables and Outputs
- Use proper variable definitions
- Implement proper output values
- Use proper descriptions
- Use proper validation
Example of variables and outputs:
# modules/networking/variables.tf
variable "project_id" {
description = "The project ID to deploy to"
type = string
}
variable "vpc_name" {
description = "The name of the VPC"
type = string
}
variable "subnets" {
description = "The subnets to create"
type = map(object({
name = string
ip_cidr_range = string
region = string
enable_flow_logs = bool
}))
}
# modules/networking/outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = google_compute_network.vpc.id
}
output "subnet_ids" {
description = "The IDs of the subnets"
value = { for k, v in google_compute_subnetwork.subnet : k => v.id }
}
- Security Best Practices
- Implement proper IAM roles
- Use proper service accounts
- Implement proper VPC Service Controls
- Use proper private Google Access
Example of security configuration:
# modules/networking/security.tf
resource "google_compute_shared_vpc_host_project" "host" {
project = var.project_id
}
resource "google_compute_shared_vpc_service_project" "service" {
host_project = google_compute_shared_vpc_host_project.host.project
service_project = var.service_project_id
}
# modules/networking/main.tf
resource "google_compute_network" "vpc" {
name = var.vpc_name
auto_create_subnetworks = false
project = var.project_id
}
resource "google_compute_subnetwork" "subnet" {
for_each = var.subnets
name = each.value.name
ip_cidr_range = each.value.ip_cidr_range
region = each.value.region
network = google_compute_network.vpc.id
private_ip_google_access = true
dynamic "log_config" {
for_each = each.value.enable_flow_logs ? [1] : []
content {
aggregation_interval = "INTERVAL_5_SEC"
flow_sampling = 0.5
metadata = "INCLUDE_ALL_METADATA"
}
}
}
# modules/networking/firewall.tf
resource "google_compute_firewall" "allow_internal" {
name = "allow-internal"
network = google_compute_network.vpc.name
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
allow {
protocol = "icmp"
}
source_ranges = [var.vpc_cidr]
target_tags = ["internal"]
}
resource "google_compute_firewall" "allow_ssh" {
name = "allow-ssh"
network = google_compute_network.vpc.name
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["ssh"]
}
# modules/networking/nat.tf
resource "google_compute_router" "router" {
name = "nat-router"
region = var.region
network = google_compute_network.vpc.id
}
resource "google_compute_router_nat" "nat" {
name = "nat-gateway"
router = google_compute_router.router.name
region = google_compute_router.router.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"
}
}
# modules/networking/peering.tf
resource "google_compute_network_peering" "peering" {
name = "peering-${var.peer_name}"
network = google_compute_network.vpc.id
peer_network = var.peer_network_id
export_custom_routes = true
import_custom_routes = true
}
# modules/networking/dns.tf
resource "google_dns_managed_zone" "private_zone" {
name = "private-zone"
dns_name = "private.example.com."
description = "Private DNS zone"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.vpc.id
}
}
}
resource "google_dns_record_set" "a_record" {
name = "app.${google_dns_managed_zone.private_zone.dns_name}"
managed_zone = google_dns_managed_zone.private_zone.name
type = "A"
ttl = 300
rrdatas = ["10.0.0.1"]
}
# modules/networking/load_balancer.tf
resource "google_compute_health_check" "health_check" {
name = "health-check"
check_interval_sec = 5
timeout_sec = 5
http_health_check {
port = 80
}
}
resource "google_compute_backend_service" "backend" {
name = "backend-service"
protocol = "HTTP"
port_name = "http"
timeout_sec = 10
health_checks = [google_compute_health_check.health_check.id]
backend {
group = var.instance_group
}
}
# modules/networking/variables.tf
variable "project_id" {
description = "The project ID to deploy to"
type = string
}
variable "vpc_name" {
description = "The name of the VPC"
type = string
}
variable "subnets" {
description = "The subnets to create"
type = map(object({
name = string
ip_cidr_range = string
region = string
enable_flow_logs = bool
}))
}
# modules/networking/outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = google_compute_network.vpc.id
}
output "subnet_ids" {
description = "The IDs of the subnets"
value = { for k, v in google_compute_subnetwork.subnet : k => v.id }
}
# modules/networking/security.tf
resource "google_compute_shared_vpc_host_project" "host" {
project = var.project_id
}
resource "google_compute_shared_vpc_service_project" "service" {
host_project = google_compute_shared_vpc_host_project.host.project
service_project = var.service_project_id
}
Shared VPC
A Shared VPC allows you to share a VPC network across multiple projects, which is particularly useful for organizations with multiple teams or environments.
Here's a comprehensive guide for implementing a Shared VPC:
- Project Structure First, you'll need to organize your projects into:
- Host Project: Contains the Shared VPC
- Service Projects: Projects that will use the Shared VPC
Here's an example of how to structure your Terraform code:
# modules/shared-vpc/main.tf
# Host Project Configuration
resource "google_compute_shared_vpc_host_project" "host" {
project = var.host_project_id
}
# VPC Network in Host Project
resource "google_compute_network" "shared_vpc" {
name = "shared-vpc"
auto_create_subnetworks = false
project = var.host_project_id
}
# Subnets in Host Project
resource "google_compute_subnetwork" "shared_subnet" {
for_each = var.subnets
name = each.value.name
ip_cidr_range = each.value.ip_cidr_range
region = each.value.region
network = google_compute_network.shared_vpc.id
project = var.host_project_id
private_ip_google_access = true
}
# Service Project Attachment
resource "google_compute_shared_vpc_service_project" "service" {
for_each = var.service_projects
host_project = var.host_project_id
service_project = each.value
}
- IAM Configuration Set up proper IAM roles for the Shared VPC:
# modules/shared-vpc/iam.tf
# Network Admin role for service projects
resource "google_project_iam_member" "network_admin" {
for_each = var.service_projects
project = each.value
role = "roles/compute.networkUser"
member = "serviceAccount:${var.service_account_email}"
}
# Security Admin role for service projects
resource "google_project_iam_member" "security_admin" {
for_each = var.service_projects
project = each.value
role = "roles/compute.securityAdmin"
member = "serviceAccount:${var.service_account_email}"
}
- Firewall Rules Configure firewall rules in the host project:
# modules/shared-vpc/firewall.tf
resource "google_compute_firewall" "allow_internal" {
name = "allow-internal"
network = google_compute_network.shared_vpc.name
project = var.host_project_id
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
allow {
protocol = "icmp"
}
source_ranges = [var.vpc_cidr]
target_tags = ["internal"]
}
# Allow specific service project access
resource "google_compute_firewall" "allow_service_project" {
name = "allow-service-project"
network = google_compute_network.shared_vpc.name
project = var.host_project_id
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = var.service_project_cidrs
target_tags = ["service-project"]
}
- Variables Configuration Define the necessary variables:
# modules/shared-vpc/variables.tf
variable "host_project_id" {
description = "The ID of the host project"
type = string
}
variable "service_projects" {
description = "Map of service project IDs"
type = map(string)
}
variable "subnets" {
description = "Map of subnet configurations"
type = map(object({
name = string
ip_cidr_range = string
region = string
enable_flow_logs = bool
}))
}
variable "service_account_email" {
description = "Service account email for IAM bindings"
type = string
}
variable "vpc_cidr" {
description = "CIDR range for the VPC"
type = string
}
variable "service_project_cidrs" {
description = "List of CIDR ranges for service projects"
type = list(string)
}
- Outputs Define useful outputs:
# modules/shared-vpc/outputs.tf
output "vpc_id" {
description = "The ID of the shared VPC"
value = google_compute_network.shared_vpc.id
}
output "subnet_ids" {
description = "Map of subnet IDs"
value = { for k, v in google_compute_subnetwork.shared_subnet : k => v.id }
}
- Usage Example Here's how to use the Shared VPC module:
# environments/prod/main.tf
module "shared_vpc" {
source = "../../modules/shared-vpc"
host_project_id = "host-project-id"
service_projects = {
"service-project-1" = "service-project-1-id"
"service-project-2" = "service-project-2-id"
}
subnets = {
"subnet-1" = {
name = "subnet-1"
ip_cidr_range = "10.0.0.0/24"
region = "us-central1"
enable_flow_logs = true
}
"subnet-2" = {
name = "subnet-2"
ip_cidr_range = "10.0.1.0/24"
region = "us-east1"
enable_flow_logs = true
}
}
service_account_email = "[email protected]"
vpc_cidr = "10.0.0.0/16"
service_project_cidrs = ["10.0.0.0/24", "10.0.1.0/24"]
}
- Best Practices for Shared VPC
- Use proper naming conventions for all resources
- Implement proper IAM roles and permissions
- Use proper subnet design
- Implement proper firewall rules
- Use proper logging and monitoring
- Implement proper security controls
- Use proper documentation
- Implement proper testing
- Use proper version control
- Implement proper CI/CD
- Security Considerations
- Implement proper IAM roles
- Use proper service accounts
- Implement proper VPC Service Controls
- Use proper private Google Access
- Implement proper firewall rules
- Use proper logging and monitoring
- Implement proper security controls
- Use proper documentation
# modules/shared-vpc/main.tf
# Host Project Configuration
resource "google_compute_shared_vpc_host_project" "host" {
project = var.host_project_id
}
# VPC Network in Host Project
resource "google_compute_network" "shared_vpc" {
name = "shared-vpc"
auto_create_subnetworks = false
project = var.host_project_id
}
# Subnets in Host Project
resource "google_compute_subnetwork" "shared_subnet" {
for_each = var.subnets
name = each.value.name
ip_cidr_range = each.value.ip_cidr_range
region = each.value.region
network = google_compute_network.shared_vpc.id
project = var.host_project_id
private_ip_google_access = true
}
# Service Project Attachment
resource "google_compute_shared_vpc_service_project" "service" {
for_each = var.service_projects
host_project = var.host_project_id
service_project = each.value
}
# modules/shared-vpc/iam.tf
# Network Admin role for service projects
resource "google_project_iam_member" "network_admin" {
for_each = var.service_projects
project = each.value
role = "roles/compute.networkUser"
member = "serviceAccount:${var.service_account_email}"
}
# Security Admin role for service projects
resource "google_project_iam_member" "security_admin" {
for_each = var.service_projects
project = each.value
role = "roles/compute.securityAdmin"
member = "serviceAccount:${var.service_account_email}"
}
# modules/shared-vpc/firewall.tf
resource "google_compute_firewall" "allow_internal" {
name = "allow-internal"
network = google_compute_network.shared_vpc.name
project = var.host_project_id
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
allow {
protocol = "icmp"
}
source_ranges = [var.vpc_cidr]
target_tags = ["internal"]
}
# Allow specific service project access
resource "google_compute_firewall" "allow_service_project" {
name = "allow-service-project"
network = google_compute_network.shared_vpc.name
project = var.host_project_id
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = var.service_project_cidrs
target_tags = ["service-project"]
}
# modules/shared-vpc/variables.tf
variable "host_project_id" {
description = "The ID of the host project"
type = string
}
variable "service_projects" {
description = "Map of service project IDs"
type = map(string)
}
variable "subnets" {
description = "Map of subnet configurations"
type = map(object({
name = string
ip_cidr_range = string
region = string
enable_flow_logs = bool
}))
}
variable "service_account_email" {
description = "Service account email for IAM bindings"
type = string
}
variable "vpc_cidr" {
description = "CIDR range for the VPC"
type = string
}
variable "service_project_cidrs" {
description = "List of CIDR ranges for service projects"
type = list(string)
}
# modules/shared-vpc/outputs.tf
output "vpc_id" {
description = "The ID of the shared VPC"
value = google_compute_network.shared_vpc.id
}
output "subnet_ids" {
description = "Map of subnet IDs"
value = { for k, v in google_compute_subnetwork.shared_subnet : k => v.id }
}
# environments/prod/main.tf
module "shared_vpc" {
source = "../../modules/shared-vpc"
host_project_id = "host-project-id"
service_projects = {
"service-project-1" = "service-project-1-id"
"service-project-2" = "service-project-2-id"
}
subnets = {
"subnet-1" = {
name = "subnet-1"
ip_cidr_range = "10.0.0.0/24"
region = "us-central1"
enable_flow_logs = true
}
"subnet-2" = {
name = "subnet-2"
ip_cidr_range = "10.0.1.0/24"
region = "us-east1"
enable_flow_logs = true
}
}
service_account_email = "[email protected]"
vpc_cidr = "10.0.0.0/16"
service_project_cidrs = ["10.0.0.0/24", "10.0.1.0/24"]
}