Terraform Cert Guide Chapter 7 Part 2: Building Real Modules for AWS, Azure, and GCP

Part 1 of this chapter covered the theory: what modules are, where they come from, meta-arguments, and source types. Now it’s time to actually build stuff. Part 2 is where Ravi Mishra walks you through creating real Terraform modules for all three major cloud providers: Azure, AWS, and GCP.

The nice thing here? Once you understand the pattern for one provider, the others are basically the same structure with different resource names. The book makes this pretty clear by repeating the same workflow three times.

The Azure VM Module

The book starts with Azure. The example creates a virtual machine module that includes a NIC, KeyVault, and the VM itself. The module lives in a directory called azurerm-virtual-machine-module and follows the standard three-file structure you’ll see everywhere:

  • main.tf for the actual resource definitions
  • variables.tf for input variables
  • outputs.tf for what the module exposes back to the caller

Then there’s a separate directory called azurerm-virtual-machine-module-use-case that shows how to consume the module. This separation is important. The module is one thing, using the module is another thing.

The consumer side has a main.tf that calls the module like this:

module "terraform-vm" {
  source = "github.com/PacktPublishing/HashiCorp-Infrastructure-Automation-Certification-Guide.git//chapter7/azurerm/azurerm-virtual-machine-module?ref=v0.0.1"

  vm_name    = var.vm_name
  location   = var.location
  # ... other variables
}

The providers.tf file handles the backend configuration (storing state in Azure Blob Storage) and pins the provider version. One thing the book correctly flags: don’t hardcode your access_key in the config. Pass it through environment variables. I’ve seen people commit storage keys to Git repos in production. It’s not fun to clean up.

The outputs.tf in the consumer directory references module outputs using the module.<MODULE_NAME>.<OUTPUT_NAME> syntax:

output "vm_private_ip" {
  value = module.terraform-vm.nic
}

output "vm_name" {
  value = module.terraform-vm.vm_name
}

After running terraform init, terraform plan, and terraform apply, you get a working VM in Azure. The init step downloads the module from GitHub, the plan shows what will be created, and apply does the actual deployment.

The AWS VPC Module

Next up is AWS. The concept is identical, just different resources. This time the book creates a VPC with a subnet.

Same file structure in the module directory:

aws-vpc-subnet-module/
├── VERSION
├── main.tf
├── outputs.tf
└── variables.tf

The main.tf creates a VPC and a subnet:

resource "aws_vpc" "terraform-vpc" {
  cidr_block       = var.cidr_block
  instance_tenancy = "default"
  tags = {
    Name = var.vpc_name
  }
}

resource "aws_subnet" "terraform-subnet" {
  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = cidrsubnet(var.cidr_block, 8, 1)
  tags = {
    Name = var.subnet_name
  }
}

Notice the cidrsubnet() function. It calculates a subnet CIDR from the parent VPC CIDR automatically. That’s a nice touch for making the module flexible without requiring the user to do subnet math manually.

Variables are straightforward: vpc_name, cidr_block (with a default of 10.0.0.0/16), and subnet_name. Outputs expose the vpc_id and vpc_cidr_block.

The consumer side points to a specific version of the module using a Git tag:

module "terraform-aws-vpc" {
  source      = "github.com/PacktPublishing/HashiCorp-Infrastructure-Automation-Certification-Guide.git//chapter7/aws/aws-vpc-subnet-module?ref=v1.0.0"
  vpc_name    = var.vpc_name
  cidr_block  = var.cidr_block
  subnet_name = var.subnet_name
}

The terraform.tfvars file provides the actual values:

subnet_name = "Terraform-aws-subnet"
vpc_name    = "Terraform-aws-vpc"
cidr_block  = "10.0.0.0/16"
region      = "us-east-1"

Run the Terraform workflow, and you get a VPC and subnet in AWS. Same pattern, different cloud.

The GCP Storage Bucket Module

For GCP, the book picks Google Cloud Storage as the example resource. Again, the structure is the same: module directory with main.tf, variables.tf, outputs.tf, and a separate use-case directory for consumption.

The module’s main.tf:

resource "google_storage_bucket" "gcp-stg" {
  name          = var.gcp_stg_name
  location      = var.gcp_location
  force_destroy = var.force_destroy
  storage_class = var.storage_class
  project       = var.project
  labels        = var.labels

  versioning {
    enabled = true
  }
}

This one has more variables than the AWS example because GCS buckets have more configuration options: storage class (MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, ARCHIVE), force destroy behavior, labels as a map type, and project ID.

The outputs expose the bucket’s self_link, url, and name. After publishing with a v2.0.0 tag and consuming it, you get a storage bucket in GCP.

One important warning from the book: they included a GCP service account credential JSON file in the repo for demo purposes. Never do this in real life. Keep your credential files local or use environment variables. Pushing secrets to GitHub is one of those mistakes that can cost you real money if someone finds them.

The Pattern You Should See

If you step back and look at all three examples, the workflow is always the same:

  1. Write the module with main.tf (resources), variables.tf (inputs), outputs.tf (what you expose)
  2. Publish the module to a Git repository with a version tag
  3. Consume the module by referencing the source URL with a version ref
  4. Pass variables from the consumer to the module
  5. Read outputs using module.<NAME>.<OUTPUT> syntax

The only things that change between Azure, AWS, and GCP are the resource types and their specific arguments. The module pattern itself is provider-agnostic.

Publishing Modules to Terraform Registry

The book wraps up by explaining how to share your modules with the broader community through Terraform Registry. This is basically the public marketplace for Terraform modules.

The key requirements to publish:

  • Your module must be on a public GitHub repository
  • The repo name must follow the format terraform-<PROVIDER>-<NAME> (like terraform-google-storage-bucket or terraform-aws-ec2-instance)
  • Include a repository description (the short one-liner on GitHub)
  • Follow the standard module structure so the registry can auto-generate docs
  • Use semantic versioning for release tags (like v1.0.0 or 0.5.0)

The publishing process itself is simple: go to registry.terraform.io, sign in with GitHub, select your properly-named repo, and click publish. The registry picks up your tags, generates documentation from your variables and outputs, and makes the module available for anyone to use.

This is actually a nice way to contribute to the Terraform ecosystem. If you’ve written a module that solves a common problem, publishing it takes five minutes and could save someone else hours of work.

Chapter Summary

Chapter 7 Part 2 is really about one thing: practice. The theory from Part 1 gets applied to three real cloud providers, and you can see that modules are modules regardless of where you’re deploying.

Key takeaways:

  • Module structure is always the same: main.tf, variables.tf, outputs.tf
  • Version your modules with Git tags so consumers can pin to specific versions
  • Don’t hardcode secrets in your config (storage keys, credential files, etc.)
  • Module output syntax follows module.<MODULE_NAME>.<OUTPUT_NAME>
  • Terraform Registry is how you share modules publicly, and it requires specific naming conventions
  • The workflow is provider-agnostic: learn it once for Azure, apply the same pattern to AWS and GCP

If you’re prepping for the certification, make sure you understand the module consumption syntax, how versioning with Git tags works, and the requirements for publishing to Terraform Registry. Those are the kinds of things that show up in exam questions.


Previous: Chapter 7 Part 1: Module Sources

Next: Chapter 8: Configuration Files


This is part of a series retelling “HashiCorp Infrastructure Automation Certification Guide” by Ravi Mishra (Packt, 2021). For the full text, grab the book - ISBN: 978-1-80056-597-5.

About

About BookGrill.net

BookGrill.net is a technology book review site for developers, engineers, and anyone who builds things with code. We cover books on software engineering, AI and machine learning, cybersecurity, systems design, and the culture of technology.

Know More