Terraform Cloud Architecture Patterns: VPC/EKS/RDS on AWS, VNET/AKS on Azure, VPC/GKE on GCP

Terraform Cloud Architecture Patterns#

The three-tier architecture — networking, managed Kubernetes, managed database — is the most common pattern for production deployments on any major cloud. The concepts are identical across AWS, Azure, and GCP. The Terraform code is not. Resource names differ, required arguments differ, default behaviors differ, and the gotchas that catch agents and humans are cloud-specific.

This article shows the real Terraform for each layer on each cloud, side by side, so agents can write correct infrastructure code for whichever cloud the user deploys to.

Terraform Code Quality: Patterns, Anti-Patterns, and Review Heuristics

Terraform Code Quality#

Writing Terraform that works is easy. Writing Terraform that is safe, maintainable, and comprehensible to the next person (or agent) is harder. Most quality problems are not bugs — they are patterns that work today but create pain tomorrow: hardcoded IDs that break in a new account, missing lifecycle rules that cause accidental data loss, modules that are too big to understand or too small to justify their existence.

Terraform Core Concepts and Workflow

Providers, Resources, and Data Sources#

Terraform has three core object types. Providers are plugins that talk to APIs (AWS, Azure, GCP, Kubernetes, GitHub). Resources are the things you create and manage. Data sources read existing objects without managing them.

# providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.5.0"
}

provider "aws" {
  region = var.region
}
# A resource Terraform creates and manages
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  tags = { Name = "main-vpc" }
}

# A data source that reads an existing AMI
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  subnet_id     = aws_vpc.main.id
}

Resources create, update, and delete. Data sources only read. If you need information about something Terraform does not manage, use a data source.

Terraform Cost Management: Writing Cost-Aware Infrastructure Code

Terraform Cost Management#

The most expensive line in your cloud bill was written in a .tf file. A single instance_type choice, a forgotten NAT Gateway, or an over-provisioned RDS instance can cost thousands per month — and none of these show up in terraform plan. Plan shows what changes. It does not show what it costs.

This article covers how to write cost-aware Terraform and catch expensive decisions before they reach production.

Terraform Import and Brownfield Adoption: Bringing Existing Infrastructure Under Code

Terraform Import and Brownfield Adoption#

Most organizations do not start with Infrastructure as Code. They start with console clicks, CLI commands, and scripts. At some point they decide to adopt Terraform — and now they have hundreds of existing resources that need to be brought under management without disruption.

This is the brownfield problem: writing Terraform code that matches existing infrastructure exactly, importing the state so Terraform knows about the resources, and resolving the inevitable drift between what exists and what the code describes.

Terraform Modules: Structure, Composition, and Reuse

What Modules Are#

A Terraform module is a directory containing .tf files. Every Terraform configuration is already a module (the “root module”). When you call another module from your root module, that is a “child module.” Modules let you encapsulate a set of resources behind a clean interface of input variables and outputs.

Module Structure#

A well-organized module looks like this:

modules/vpc/
  main.tf           # resource definitions
  variables.tf      # input variables
  outputs.tf        # output values
  versions.tf       # required providers and terraform version
  README.md         # usage documentation

The module itself has no backend, no provider configuration, and no hardcoded values. Everything configurable comes in through variables. Everything downstream consumers need comes out through outputs.

Terraform Networking Patterns: VPC, Subnets, NAT, Peering, and Transit Gateway Across Clouds

Terraform Networking Patterns#

Networking is the first thing you build and the last thing you want to change. CIDR ranges, subnet allocation, and connectivity topology are difficult to modify after resources depend on them. Getting the network right in Terraform saves months of migration work later.

This article covers the networking patterns across AWS, Azure, and GCP — from basic VPC design to multi-region hub-spoke topologies.

CIDR Planning#

Plan CIDR ranges before writing any Terraform. Once a VPC is created with a CIDR block, changing it requires recreating the VPC and everything in it.

Terraform Provider Configuration Patterns: Versioning, Aliasing, Multi-Region, and Authentication

Terraform Provider Configuration Patterns#

Providers are Terraform’s interface to cloud APIs. Misconfiguring them causes resources to be created in the wrong region, with the wrong credentials, or with an incompatible provider version. These failures are often silent — Terraform succeeds, but the resource is in the wrong place.

Version Constraints#

The Required Providers Block#

Every configuration should declare its providers with version constraints:

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = "~> 2.25"
    }
    helm = {
      source  = "hashicorp/helm"
      version = "~> 2.12"
    }
  }
}

Version Constraint Syntax#

ConstraintMeaningExample
= 5.31.0Exact version onlyPin for maximum reproducibility
~> 5.0Any 5.x (>=5.0.0, <6.0.0)Allow minor + patch updates
~> 5.31Any 5.31.x (>=5.31.0, <5.32.0)Allow patch updates only
>= 5.0, < 6.0RangeSame as ~> 5.0 but explicit
>= 5.0Any version 5.0 or newerDangerous — allows breaking changes

Recommended patterns:

Terraform Safety for Agents: Plans, Applies, and the Human Approval Gate

Terraform Safety for Agents#

Terraform is the most dangerous tool most agents have access to. A single terraform apply can create, modify, or destroy real infrastructure — databases with production data, networking that carries live traffic, security groups that protect running services. There is no undo button. terraform destroy is not an undo — it is a different destructive action.

This article defines the safety protocols agents must follow when working with Terraform: what to check before every plan, how to read plan output for danger, how to present plans to humans, when to apply vs when to stop, and how to handle state conflicts.

Terraform Secrets and Sensitive Data: Patterns for Variables, State, Providers, and CI/CD

Terraform Secrets and Sensitive Data#

Every Terraform configuration eventually needs a password, API key, or certificate. How you handle that secret determines whether it ends up in your state file (readable by anyone with state access), in plan output (visible in CI logs), in version control (permanent history), or properly managed through a secrets provider.

This article covers the patterns for handling secrets at every stage of the Terraform lifecycle — from variable declaration through state storage.