Terraform Cert Guide Chapter 3 Part 1: Providers, Resources, and Variables Explained

Chapter 3 of Ravi Mishra’s certification guide is where Terraform stops being theoretical and starts getting real. You installed it in Chapter 2, now you actually write configuration code. This first half covers three fundamentals: providers, resources, and input variables across Azure, AWS, and GCP.

Let me walk you through what the chapter covers and what actually matters.

Terraform Providers: How Terraform Knows Where to Go

Here is the first question Mishra raises: if you write a Terraform config to create a virtual network, how does Terraform know you mean Azure and not AWS or Google Cloud?

The answer is providers.

A provider is a plugin binary that gets downloaded when you run terraform init. It is the thing that actually talks to cloud APIs. The AzureRM provider talks to Azure. The AWS provider talks to AWS. The Google provider talks to GCP. Simple as that.

Every provider lives in the Terraform Registry, which is basically a package manager for infrastructure plugins. You can use official ones from HashiCorp, community-contributed ones, or even write your own.

Best practice: put your provider config in a file called providers.tf or required_providers.tf. You can technically put it in any .tf file, but keeping it separate makes your codebase easier to navigate.

AzureRM Provider

The Azure provider setup looks like this:

terraform {
  required_version = ">= 1.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "2.54.0"
    }
  }
}

provider "azurerm" {
  features {}
  subscription_id = "..."
  client_id       = "..."
  client_secret   = "..."
  tenant_id       = "..."
}

The key arguments are subscription_id, client_id, client_secret, and tenant_id. All of them can be sourced from environment variables (ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, etc.) instead of hardcoding them. And you should use environment variables. Never put secrets directly in your Terraform files.

The features {} block is required and a bit unique to Azure. It lets you control behavior for specific resources like Key Vault, VMs, and template deployments. For example, you can tell Terraform whether to recover soft-deleted key vaults or permanently delete a Log Analytics workspace on destroy.

Version Constraints

Mishra spends time on version pinning, and for good reason. Here is the cheat sheet:

  • >= 2.54.0 means that version or newer
  • = 2.54.0 means exactly that version
  • ~> 2.54.0 means any 2.54.x release (this is the most useful one)
  • >= 2.46, <= 2.54 means anything in that range

The ~> operator is the one you will use the most. It lets you get patch updates while blocking major version changes that might break things.

Provider Aliases

What if you need to deploy resources across multiple Azure subscriptions? Use the alias argument:

provider "azurerm" {
  features {}
}

provider "azurerm" {
  features {}
  alias = "nonprod_01_subscription"
}

resource "azurerm_resource_group" "example" {
  provider = azurerm.nonprod_01_subscription
  name     = "example-resources"
  location = "West Europe"
}

You can also mix different providers entirely. The book shows using azurerm and random together, where a random integer gets appended to a resource group name. Practical trick for avoiding naming collisions.

AWS Provider

Same concept, different arguments:

terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.35.0"
    }
  }
}

provider "aws" {
  region     = "us-east-1"
  access_key = "..."
  secret_key = "..."
}

Three main arguments: access_key, secret_key, and region. All sourceable from environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION). Same rule applies: do not hardcode credentials.

The alias and version arguments work exactly the same as Azure.

Google Cloud Provider

terraform {
  required_version = ">= 1.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "3.63.0"
    }
  }
}

provider "google" {
  credentials = file("account.json")
  project     = "my-google-project-id"
  region      = "europe-west2"
  zone        = "europe-west2-b"
}

Google uses a JSON credentials file instead of individual key/secret pairs. You also specify project, region, and zone. The zone is a nice addition since GCP is more zone-aware than Azure tends to be.

Everything else (alias, version constraints) works the same way.

Terraform Resources: The Actual Infrastructure Code

Providers tell Terraform where to go. Resources tell it what to build.

A resource block is the core building block of any Terraform config. It declares an infrastructure object you want to create, update, or destroy. The syntax is consistent across all clouds:

resource "<provider_resource_type>" "<local_name>" {
  argument1 = "value1"
  argument2 = "value2"
}

The resource type always starts with the provider name (aws_, azurerm_, google_). The local name is your internal reference, something you pick for referring to this resource elsewhere in your code.

Azure Resources

Mishra uses an Azure Public IP example:

resource "azurerm_resource_group" "example" {
  name     = "example-resources"
  location = "West Europe"
}

resource "azurerm_public_ip" "azure-pip" {
  name                = "my-public-ip"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  allocation_method   = "Dynamic"
}

Notice how the public IP references the resource group using azurerm_resource_group.example.location. That is how you chain resources together. Terraform figures out the dependency graph automatically.

AWS Resources

resource "aws_instance" "web" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

Same pattern. The type is aws_instance, the local name is web. The arguments (ami, instance_type) are specific to EC2 instances.

Google Cloud Resources

resource "google_project" "my_project" {
  name       = "My Project"
  project_id = "your-project-id"
  org_id     = "1234567"
}

resource "google_app_engine_application" "app" {
  project     = google_project.my_project.project_id
  location_id = "us-central"
}

Same pattern again. The App Engine resource references the project resource for its project argument.

The key takeaway from the book here: the resource block syntax is identical across all providers. Only the arguments change based on what each cloud service expects.

Terraform Input Variables: Stop Hardcoding Everything

Hardcoding values in resource blocks works for demos, but it is terrible for real projects. Terraform variables let you parameterize your configs so you can reuse them across environments.

The syntax is var.<variable_name> when referencing, and a variable block for declaring.

Azure Variable Example

Here is the public IP example with variables:

# main.tf
resource "azurerm_resource_group" "example" {
  name     = var.rgname
  location = var.rglocation
}

resource "azurerm_public_ip" "azure-pip" {
  name                    = var.public_ip_name
  location                = azurerm_resource_group.example.location
  resource_group_name     = azurerm_resource_group.example.name
  allocation_method       = var.allocation_method
  idle_timeout_in_minutes = var.idle_timeout_in_minutes
  tags                    = var.tags
}

Then you declare those variables in variables.tf:

variable "rgname" {
  description = "(Required) Name of the Resource Group"
  type        = string
  default     = "example-rg"
}

variable "rglocation" {
  description = "Resource Group location"
  type        = string
  default     = "West Europe"
}

variable "tags" {
  description = "Tags to assign to the resource"
  type        = map(any)
  default = {
    environment = "Test"
    Owner       = "Azure-Terraform"
  }
}

Each variable has a description, a type (string, number, map, etc.), and an optional default value.

Four Ways to Provide Variable Values

This is the part worth memorizing for the cert exam. Terraform accepts variable values from four sources:

  1. Environment variables with the TF_VAR_ prefix (e.g., export TF_VAR_rgname=example-rg)
  2. Variable definition files named terraform.tfvars, terraform.tfvars.json, or anything ending in .auto.tfvars
  3. Runtime prompts where Terraform asks you to type a value if it is missing
  4. Default values defined in the variable block itself

The precedence order matters: environment variables and runtime values override terraform.tfvars, which overrides default values.

If you use a custom filename like testing.tfvars, you need to explicitly tell Terraform about it: terraform apply -var-file="testing.tfvars".

AWS and GCP Variables

The variable syntax is exactly the same for AWS and GCP. The only difference is what you are parameterizing.

AWS EC2 example:

variable "ami" {
  description = "Name of the AMI"
  type        = string
}

variable "instance_type" {
  description = "Name of the instance type"
  type        = string
}

GCP App Engine example:

variable "myproject_name" {
  description = "Name of the google project"
  type        = string
}

variable "project_id" {
  description = "Name of the project ID"
  type        = string
}

Values go in terraform.tfvars the same way regardless of which cloud you target.

What to Take Away

Chapter 3 Part 1 boils down to three concepts:

  • Providers connect Terraform to a cloud. They are plugins downloaded via terraform init. Pin your versions. Use environment variables for credentials. Use aliases when you need multiple configurations of the same provider.
  • Resources are the actual infrastructure declarations. The syntax is the same across all clouds. Only the provider-specific arguments differ.
  • Variables let you parameterize everything. Declare them in variables.tf, provide values through environment variables, .tfvars files, runtime input, or defaults.

If you are studying for the cert, make sure you know the version constraint operators (especially ~>) and the variable value precedence order. Those are the kinds of things that show up on the exam.

In Part 2, we will look at the other half of Chapter 3: Terraform outputs and data sources.


Previous: Chapter 2: Installation Guide

Next: Chapter 3 Part 2: Outputs and Data Sources


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