Enterprise Azure Terraform Lab Phase 1 Part 2: Terraform Variables and Locals Explained

📅 Created: 2026-04-25 | ⏱️ Read Time: 11 mins

📁 GitHub Repository: lwc-wk/enterprise-azure-lab1

Enterprise Azure Terraform Lab Phase 1 Part 2: Terraform Variables and Locals Explained

🧩 Why This Note Exists

Before building the Azure infrastructure, I realized one important thing:

Terraform is not only about creating resources. It is about controlling how resources are described, named, reused, and managed.

In my Azure Terraform lab, two files are especially important:

infra/generic-input-variables.tf
infra/locals.tf

At first, these files may look like small supporting files. However, they are actually part of the foundation of the whole lab.

They control:

  • What values can be changed from outside Terraform
  • How resources are named
  • How common tags are applied
  • How the project stays consistent
  • How the same Terraform code can be reused for different environments

This blog is a note-style explanation of how these files work and why they are necessary.


1. Basic Terraform Concept

Terraform is an Infrastructure as Code tool.

Instead of manually clicking resources in the Azure Portal, I describe the infrastructure using .tf files.

Example:

I want a Resource Group.
I want a Virtual Network.
I want two subnets.
I want a Storage Account.
I want a Log Analytics Workspace.

Terraform reads the configuration and talks to Azure through the AzureRM provider.

The normal Terraform workflow is:

terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
terraform destroy

What Each Command Means

Command Purpose
terraform init Initializes Terraform and downloads required providers
terraform fmt Formats Terraform files consistently
terraform validate Checks whether the configuration is valid
terraform plan Shows what Terraform will create, update, or delete
terraform apply Applies the changes to Azure
terraform destroy Deletes Terraform-managed resources

The most important mindset is:

Terraform does not just deploy resources. Terraform compares desired state with real state.

That means Terraform looks at the code, checks the current Azure environment, and decides what needs to change.


2. Why Split Terraform Files?

Terraform does not require every resource to be in one large file.

As long as the files are in the same folder, Terraform reads them together.

In this lab, the Terraform files are split by responsibility:

infra/
├── backend.tf
├── versions.tf
├── providers.tf
├── generic-input-variables.tf
├── locals.tf
├── resource-group.tf
├── network.tf
├── security.tf
├── storage.tf
├── monitoring.tf
├── diagnostics.tf
├── app-service.tf
├── budget.tf
├── rbac.tf
└── outputs.tf

This makes the lab easier to understand.

Instead of putting everything into one giant main.tf, each file has a clear purpose.

For example:

File Purpose
generic-input-variables.tf Defines values that can be customized
locals.tf Defines calculated values used repeatedly
resource-group.tf Creates the Azure Resource Group
network.tf Creates the VNet and subnets
security.tf Creates NSG rules and associations
storage.tf Creates the Storage Account
outputs.tf Prints useful values after deployment

3. What Is generic-input-variables.tf?

The generic-input-variables.tf file defines input variables.

Input variables are values that can be changed without rewriting the main Terraform resource code.

Example concept:

Instead of hardcoding the location as "centralus" everywhere,
I define a variable once and reuse it across the project.

A simplified example:

variable "resource_group_location" {
  description = "Azure region where resources will be created"
  type        = string
  default     = "centralus"
}

Then other Terraform files can reference it:

location = var.resource_group_location

This is useful because I can change the region from one place instead of editing many resource files.


4. Example Variables Used in This Lab

In this lab, the variables are designed to support naming, environment separation, cost control, and ownership.

Example:

variable "business_division" {
  description = "Business division or team name"
  type        = string
  default     = "infra"
}

variable "environment" {
  description = "Environment name such as dev, test, or prod"
  type        = string
  default     = "dev"
}

variable "project_name" {
  description = "Project name used for resource naming"
  type        = string
  default     = "azlab2"
}

variable "resource_group_location" {
  description = "Azure region where resources are deployed"
  type        = string
  default     = "centralus"
}

variable "owner" {
  description = "Owner tag value"
  type        = string
  default     = "lw"
}

These variables allow the same Terraform code to produce different environments.

For example:

business_division = infra
environment       = dev
project_name      = azlab2

can produce names like:

rg-infra-dev-azlab2
vnet-infra-dev-azlab2
nsg-infra-dev-azlab2
law-infra-dev-azlab2

This is much better than random manual names.


5. Why Variables Are Necessary

Variables are necessary because infrastructure should be flexible.

Without variables, I would need to hardcode values inside every resource.

Bad example:

resource "azurerm_resource_group" "lab" {
  name     = "rg-infra-dev-azlab2"
  location = "centralus"
}

This works, but it is not reusable.

A better example:

resource "azurerm_resource_group" "lab" {
  name     = local.resource_group_name
  location = var.resource_group_location
}

Now the resource is controlled by variables and locals.

This makes the Terraform code:

  • Easier to reuse
  • Easier to test
  • Easier to change
  • Easier to explain
  • Closer to real enterprise infrastructure practice

6. What Is dev.tfvars?

Variables can have default values, but environment-specific values are usually placed inside a .tfvars file.

In this lab, the development environment uses:

env/dev.tfvars

Example:

business_division       = "infra"
environment             = "dev"
project_name            = "azlab2"
resource_group_location = "centralus"
owner                   = "lw"

monthly_budget_amount = 10
budget_start_date     = "2026-05-01T00:00:00Z"

Then Terraform can be run with:

terraform plan -var-file="../env/dev.tfvars"
terraform apply -var-file="../env/dev.tfvars"

This means the .tf files define the infrastructure logic, while the .tfvars file provides the environment-specific input values.

A simple way to understand it:

.tf files     = infrastructure blueprint
.tfvars file  = environment configuration

7. What Is locals.tf?

The locals.tf file defines local values.

Locals are calculated values used inside the Terraform configuration.

They are not passed from outside like variables.

They are created inside Terraform to avoid repeating logic.

Example:

locals {
  resource_name_prefix = "${var.business_division}-${var.environment}-${var.project_name}"
}

If the variables are:

business_division = infra
environment       = dev
project_name      = azlab2

Then the local value becomes:

infra-dev-azlab2

This prefix can then be reused everywhere.

Example:

name = "rg-${local.resource_name_prefix}"

Result:

rg-infra-dev-azlab2

This keeps naming consistent across all resources.


8. Variables vs Locals

At first, variables and locals can look similar, but they have different purposes.

Concept Purpose Example
Variable Value passed into Terraform var.environment
Local Value calculated inside Terraform local.resource_name_prefix

Simple explanation:

Variables are inputs.
Locals are internal calculations.

For example:

variable "environment" {
  type    = string
  default = "dev"
}

locals {
  resource_name_prefix = "infra-${var.environment}-azlab2"
}

Here, environment is an input.

resource_name_prefix is calculated from that input.


9. Common Tags in locals.tf

Tags are important in Azure because they help with governance, ownership, and cost visibility.

Instead of writing tags manually on every resource, I define common tags once in locals.tf.

Example:

locals {
  common_tags = {
    Project      = "enterprise-azure-lab"
    Environment  = var.environment
    Owner        = var.owner
    ManagedBy    = "terraform"
    CostControl  = "destroy-after-validation"
  }
}

Then resources can use:

tags = local.common_tags

This means all resources can receive the same standard tags.

Why this matters:

  • Easier cost tracking
  • Easier ownership identification
  • Easier cleanup
  • Better governance habit
  • More professional lab design

10. Naming Pattern in This Lab

The lab uses a consistent naming pattern:

<resource-type>-<business-division>-<environment>-<project-name>

Example:

rg-infra-dev-azlab2
vnet-infra-dev-azlab2
nsg-infra-dev-azlab2
law-infra-dev-azlab2
asp-infra-dev-azlab2

This naming pattern is simple, but it is much better than random resource names.

Good naming helps answer:

  • What is this resource?
  • Which project does it belong to?
  • Which environment is it for?
  • Who manages it?
  • Can it be safely destroyed?

In a real company, naming standards are even more strict, but this lab version is good enough to show the concept.


11. Why Storage Account Names Need Random Suffixes

Some Azure resource names must be globally unique.

Storage Account names are one example.

That means this name cannot already exist anywhere in Azure:

stdevazlab2

To avoid naming conflicts, the lab uses a random suffix.

Example concept:

resource "random_string" "storage_suffix" {
  length  = 6
  upper   = false
  special = false
}

Then the Storage Account name can include that suffix:

name = "st${var.environment}${var.project_name}${random_string.storage_suffix.result}"

Example result:

stdevazlab2a1b2c3

This solves the global uniqueness problem.


12. How These Files Work Together

The relationship looks like this:

generic-input-variables.tf
        ↓
env/dev.tfvars
        ↓
locals.tf
        ↓
resource files
        ↓
Azure resources

More specifically:

Variables define what can be customized.
.tfvars provides real values for one environment.
Locals calculate reusable names and tags.
Resource files use variables and locals to create Azure resources.

Example flow:

var.business_division = "infra"
var.environment       = "dev"
var.project_name      = "azlab2"

local.resource_name_prefix = "infra-dev-azlab2"

Resource Group name = "rg-infra-dev-azlab2"

This is the reason generic-input-variables.tf and locals.tf are not optional decoration.

They are part of the control system of the whole lab.


13. Common Beginner Confusion

Confusion 1: Are Variables and Locals the Same?

No.

Variables are values provided to Terraform.

Locals are values calculated inside Terraform.

variable = input
local    = calculated helper value

Confusion 2: Do File Names Matter?

Mostly no.

Terraform reads all .tf files in the same directory.

These two structures can both work:

main.tf

or:

resource-group.tf
network.tf
storage.tf
locals.tf

The second approach is better for readability.

Confusion 3: Why Not Hardcode Everything?

Hardcoding works for a quick test, but it becomes messy quickly.

For a portfolio project, reusable structure looks more professional.

Confusion 4: Why Use Tags?

Tags are useful for ownership, billing, governance, and cleanup.

Even in a lab, tags show enterprise-style thinking.


14. My Key Takeaway

The main lesson is:

Terraform structure matters before the infrastructure gets complicated.

generic-input-variables.tf defines the inputs.

dev.tfvars provides environment-specific values.

locals.tf builds consistent names and tags.

The resource files then use those values to deploy Azure infrastructure in a predictable way.

This is why these files are necessary.

They make the lab:

  • Cleaner
  • Safer
  • More reusable
  • Easier to explain
  • More professional
  • More suitable for interview discussion

In simple words:

Variables make Terraform flexible. Locals make Terraform clean.

That is the foundation before moving deeper into Resource Groups, networking, security, monitoring, App Service, RBAC, and cost control.