Back to the blog

A reusable Terraform base for AWS and GCP

One repository shape, small modules and strict state rules that turn the first week of every new cloud project into an afternoon.

date:
reading:
3 min

Every new project needs the same first week: a network, an access model, somewhere to run containers and a way to see what is happening. This is the layout I use to make that week an afternoon, for both AWS and GCP.

The idea

Keep one repository per cloud, with the same shape in each:

layout
infra/├── modules/│   ├── network/│   ├── cluster/│   └── observability/├── envs/│   ├── dev/│   └── prod/└── README.md

Modules hold the logic and know nothing about environments. Each folder in envs/ only calls the modules with its own values. That is the whole trick: a new environment is a new folder, not a new design.

One module, two clouds

The network module takes the same inputs on both clouds, so the environment files look alike:

envs/dev/main.tf
module "network" {  source = "../../modules/network"   name       = "dev"  cidr_block = "10.10.0.0/16"  az_count   = 2} module "cluster" {  source = "../../modules/cluster"   name       = "dev"  network_id = module.network.id  node_count = 2}

The differences stay inside the modules:

ConcernAWSGCP
NetworkVPC with public and private subnetsVPC with regional subnets
ClusterEKSGKE
Node identityIAM role per node groupService account per node pool
EgressNAT gatewayCloud NAT

State and safety

State is the part that hurts when it goes wrong, so it gets its own rules:

  • Remote state in an object bucket, with versioning on

  • State locking, so two applies can not run at once

  • One state file per environment, never one for everything

backend.tf
terraform {  backend "s3" {    bucket         = "acme-terraform-state"    key            = "dev/network.tfstate"    region         = "ap-southeast-1"    dynamodb_table = "terraform-locks"    encrypt        = true  }}

The workflow

bash
terraform fmt -check -recursiveterraform validateterraform plan -out=tfplanPlan: 14 to add, 0 to change, 0 to destroy.terraform apply tfplan

The plan is saved to a file and the apply uses that exact file. What was reviewed is what runs.

Lessons

  1. Small modules with few inputs are easier to reuse than flexible ones with forty.

  2. Pin provider and module versions. An unpinned upgrade is a surprise change.

  3. Tag everything with its environment and owner from day one. Cost reports depend on it.

  4. Write the destroy plan too. If you can not tear an environment down cleanly, you do not fully understand it.