Terraform on Azure: Introduction and Core Concepts

Terraform on Azure: Introduction and Core Concepts

Introduction

If you’re an Azure Administrator or a Cloud Engineer, you’ve probably spent hours in the Azure Portal. It’s a great tool—it gives you visibility, makes resource management intuitive, and helps you spin up infrastructure quickly. Click a few buttons, fill in a few fields, and your resources are live.

But what happens as your environment grows?
You’re rolling out a dozen virtual machines with a VNet, a couple of subnets, and a load balancer—first in dev, then test, then prod. The clicks add up. Tiny differences creep in. A tag is missing here; a size is off there. Mid-deployment changes turn into rework.

You start running into the same problems:

  • It takes time — repeating the same steps is tedious.
  • It’s error-prone — small differences in settings create inconsistencies.
  • Changes are hard to track — mid-deployment updates are easy to miss.
  • Consistency suffers — keeping environments identical is tough.

What worked for one or two resources becomes a bottleneck. You find yourself wondering if there’s a way to avoid the repetitive parts without losing control.

Fortunately, there is. It’s called Infrastructure as Code (IaC). Instead of writing out every step, you describe the end state in files. The tool then checks what’s already there, shows you a plan of the changes, and applies only what’s needed—safely and repeatably. And when something can’t be changed in place (for example, moving a resource to another region), it will create a new one and switch over instead of guessing.

In this article, we’ll cover:

  • The evolution from Portal → scripting → IaC
  • How Azure Resource Manager (ARM) fits into the picture
  • Why Terraform is a popular choice on Azure
  • The core concepts that make Terraform powerful yet approachable

By the end, you’ll see how Terraform fits into your Azure workflow—and you’ll be ready to try your first deployment with confidence.

Let’s dive in!

First Steps into Automation: Scripting

After running into the limits of click-by-click deployments, most Azure professionals naturally reachout for Azure CLI or PowerShell as the first step toward automation.

The appeal is immediate. Instead of clicking through multiple screens to create a VM, you can run:

# Create a new VM
az vm create \
  --resource-group prod-rg \
  --name web-server \
  --image Ubuntu2204 \
  --size Standard_D2s_v3 \
  --location eastus

One command, and your VM is created. Even better, you can:

  • Save commands in a script
  • Run them whenever you need
  • Share them with your team
  • Tweak them quickly when requirements change

Why scripting feels like a game-changer

At first, scripting seems to fix the portal’s pain points:

  • Fewer clicks — you automate the repetitive parts
  • Faster rollouts — whole environments spin up from a file
  • Better consistency — everyone runs the same commands

Imagine deploying ten identical VMs. In the portal, that’s a lot of screens. With a script, it’s a loop and you’re done.

Where scripting starts to show its limits

Before long, three familiar situations appear. Each one nudges you from “running commands” toward “managing what already exists.”

1) Partial deployments — stops halfway, leaves a mess You kick off a script to stand up a small stack: VNet → subnets → NICs → VMs. The early steps succeed; the VM step fails on a quota hiccup. Now your subscription has a VNet and NICs, but no VMs. You rerun the same script and it trips immediately: those NIC names already exist. You pause to clean up or rename by hand before trying again.

2) Updating what already exists — a rerun doesn’t update First run: web-server is created with size Standard_D2s_v3. A week later, traffic grows and you need Standard_D4s_v3. You rerun the same create script—nothing changes (“already exists”). What you actually need is basic smarts in the script: detect the existing VM, deallocate, change the size, then start it again. That’s no longer a single command; it’s branching and sequencing you have to maintain.

3) Multiple environments — small differences, slow drift You copy the script for dev, test, and prod, tweaking names, counts, and sizes. A tag gets added in dev but never makes it to prod. A week later, a required NSG rule lands in test only. The scripts look nearly the same, but tiny edits have drifted apart—and keeping them aligned becomes a task of its own.

The quiet, underlying issue: state

Scripts execute steps; they don’t remember what they built. Unless you build that memory yourself (files, tags, inventories), a script won’t:

  • check what already exists,
  • compare current vs intended settings, or
  • change only what’s needed in the right order.

You can add those checks and safeguards—but each improvement is more code to write, read, and carry forward.

The takeaway

Scripting is a great first step and a tool you’ll keep using. But as environments grow, the job shifts from “run these commands” to knowing what’s there, what should be there, and changing only what’s needed—reliably, every time.

Teams realized they needed a way to describe the desired end state, have a tool compare it to what’s actually there, and then apply just the necessary changes—consistently across environments.

Infrastructure as Code: A Different Way of Thinking

In the scripting world, you tell Azure what to do step by step. The pain we just saw comes from those steps not remembering what’s already there.
The shift is simple: write down the outcome you want and let the tool figure out the steps safely.

That’s the idea behind Infrastructure as Code (IaC). Instead of writing steps, you write the desired end state. The tool works out how to get from what exists to what you asked for.

  • Imperative thinking (scripts): “Run these commands.”
  • Declarative thinking (IaC): “Make the environment look like this.”

Let’s take an example to understand this better

You decide your app needs two Linux VMs. With IaC, you describe that goal in code. One popular IaC tool is Terraform. Here’s a simplified sketch (networking details omitted to keep the focus clear):

# Simplified Terraform – networking/auth omitted

resource "azurerm_resource_group" "app" {
  name     = "rg-web-eastus"
  location = "East US"
}

resource "azurerm_linux_virtual_machine" "web" {
  count               = 2
  name                = "web-${count.index}"
  resource_group_name = azurerm_resource_group.app.name
  location            = azurerm_resource_group.app.location
  size                = "Standard_D2s_v3"
  # ...image, NIC, and SSH settings would go here in a real config
}

What happens behind the scenes

When you apply this configuration, the tool:

  1. Looks at what’s already there in your subscription,
  2. Shows a plan of the changes it intends to make, and
  3. Creates only what’s missing—then remembers the result for next time.

A week later, traffic grows. You change count = 3 and run it again. The plan sees you already have two, adds one, and you end up with three VMs—no custom logic in your script, and no guesswork.

How this changes your day-to-day

  • You see a plan before anything changes.
  • The tool remembers what it created.
  • It updates only what’s needed, in the right order.

Where this is heading

The reason this works reliably on Azure is the control plane underneath: Azure Resource Manager (ARM). Next, we’ll look at how ARM fits under the Portal, CLI, and tools like Terraform—and why different authoring options emerged on top of it.

Azure Resource Manager: The Foundation of Azure Automation

As teams started embracing Infrastructure as Code, Microsoft introduced a fundamental change to Azure: Azure Resource Manager (ARM). But to understand why this matters, let’s break down what ARM actually is.

What is Azure Resource Manager?

Think of ARM as Azure’s control center. It’s the system that:

  • Handles every request to create, update, or delete resources
  • Makes sure only authorized people can make changes
  • Ensures resources are deployed consistently
  • Organizes resources into logical groups

In fact, everything you do in Azure goes through ARM:

  • Clicking buttons in the Portal? ARM handles it.
  • Running PowerShell commands? They talk to ARM.
  • Using the Azure CLI? ARM processes those requests.

Azure Resource Manager (ARM)

This centralized approach means every change to your Azure infrastructure follows the same rules and checks, regardless of how you make that change.

Enter ARM Templates

To help teams adopt Infrastructure as Code, Microsoft created a specific JSON-based format called ARM templates. These templates let you describe your desired infrastructure in a way that the Azure Resource Manager system can understand.

Here’s a simple example of an ARM template:

{
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "mystorageaccount",
      "location": "East US",
      "sku": {
        "name": "Standard_LRS"
      }
    }
  ]
}

At first, ARM templates seemed to solve many infrastructure challenges:

  • They work directly with Azure’s Resource Manager system—no extra tools needed
  • They ensure resources are created consistently
  • They understand dependencies (like creating networks before VMs)
  • They can be saved and reused for future deployments

When ARM Templates Started Showing Their Limits

However, as teams started using ARM templates more extensively, they encountered practical challenges:

Complex JSON Structure
Even our simple storage account example requires specific JSON formatting. Now imagine defining an entire application environment with multiple VMs, networks, and storage:

{                                          ← Opening brackets begin to stack up
  "resources": [                           ← as your template grows larger
    {
      "properties": {
        "networkProfile": {                ← Deeply nested properties become
          "networkInterfaces": [           ← difficult to follow
            { ... }
          ]
        }
      }
    }
  ]
}                                          ← One misplaced bracket breaks everything

This led to several challenges:

  • Templates quickly become long and difficult to read
  • Even small changes require careful attention to brackets and formatting
  • A single misplaced comma can break the entire template
  • Finding errors in large templates becomes time-consuming

Hard to Maintain and Update
As infrastructure needs grew, teams found themselves:

  • Managing multiple large template files
  • Spending more time fixing JSON syntax than actually building infrastructure
  • Having trouble making quick changes without breaking something
  • Needing to repeat similar configurations in multiple places

Teams needed the power of ARM’s management capabilities but wanted a simpler way to define their infrastructure—a way that would be easier to read, write, and maintain.

ARM’s Important Role

Despite the challenges with ARM templates, the Azure Resource Manager system itself was a crucial advancement. It established:

  • A consistent way to manage Azure resources
  • Protection against unauthorized changes
  • Proper ordering of resource deployment (like networks before VMs)
  • Reliable and predictable deployments

The challenge wasn’t with ARM itself—it was with how we wrote templates to work with it. What teams needed was a more user-friendly way to define infrastructure while still leveraging all the benefits of Azure Resource Manager.

Enter Terraform: Making Infrastructure Code More Human-Friendly

After seeing the challenges with both scripting and ARM templates, you might be wondering: “Isn’t there a way to get the best of both worlds?” This is exactly what Terraform offers—a way to define infrastructure that’s both powerful and easy to read.

How Terraform Makes Things Different

Let’s look at that same storage account we saw earlier, but written in Terraform’s HashiCorp Configuration Language (HCL):

resource "azurerm_storage_account" "example" {
  name                = "mystorageaccount"
  location            = "East US"
  account_tier        = "Standard"
  account_replication_type = "LRS"
}

Compare this to the ARM template version:

{
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "mystorageaccount",
      "location": "East US",
      "sku": {
        "name": "Standard_LRS"
      }
    }
  ]
}
AspectARM TemplatesTerraform
FormatJSON (deeply nested)HCL (flatter, more readable)
State ManagementPartial (deployment history)Comprehensive (detailed state tracking)
Syntax ComplexityHigh (brackets, formatting)Lower (minimal special characters)

Notice the differences:

  • No complex JSON formatting
  • No nested brackets to manage
  • Settings are clearly laid out
  • Names are easy to understand (notice the “azurerm” prefix indicating the Azure provider)

But Terraform isn’t just about nice-looking code. Remember those challenges we discussed with scripting and ARM templates? Terraform actually solves them.

Solving the State Management Problem

The first major challenge Terraform solves is state management. It does this by maintaining what’s called a “state file” that:

  • Keeps track of everything it creates
  • Knows the current settings of each resource
  • Understands relationships between resources
  • Updates automatically when changes are made

Making Changes Safely

Want to move that storage account to a different region? Just update the location:

resource "azurerm_storage_account" "example" {
  name                = "mystorageaccount"
  location            = "West US"  # Changed from East US to West US
  account_tier        = "Standard"
  account_replication_type = "LRS"
}

Before making any changes, Terraform:

  1. Checks its state file to see what’s already deployed
  2. Compares it with the changes you made
  3. Makes only the necessary changes (in this case, just updating the location)
  4. Updates its state file with the new configuration

No accidental duplicates, no need to manually check what exists, and no complex syntax to manage.

Where Does That Leave ARM?

Here’s something important to understand: Terraform doesn’t replace Azure Resource Manager—it works with it. When you use Terraform with Azure:

  • You write your infrastructure code in Terraform’s more readable language (HCL)
  • The Azure provider for Terraform translates your code into the API calls that ARM understands
  • ARM handles the actual deployment of resources
  • Terraform tracks everything in its state file

Think of Terraform as your friendly translator:

  • You communicate in a clear, simple language (HCL)
  • Terraform handles the complex translation to ARM’s API requirements
  • You get the best of both worlds: Terraform’s user-friendly syntax with ARM’s powerful management capabilities

This combination gives you:

  • The readability and simplicity of Terraform’s syntax
  • The power and reliability of ARM’s deployment system
  • The safety of Terraform’s state management

And this is just a glimpse of what Terraform can do. Throughout this series, you’ll discover how Terraform provides even more powerful features that will help you manage your growing infrastructure with confidence.

Looking Ahead: Your Journey with Terraform

In this article, we’ve taken a journey through the evolution of infrastructure management in Azure:

  • We started with the familiar territory of the Azure Portal
  • Explored how scripting offered our first taste of automation
  • Saw how Infrastructure as Code changed the game
  • Learned about ARM’s role in Azure
  • And discovered how Terraform brings it all together with a more approachable solution

You’ve seen how Terraform addresses the key challenges that Azure administrators face:

  • No more clicking through portals for repetitive deployments
  • No more worrying about scripts duplicating resources
  • No more wrestling with complex JSON templates
  • No more manually tracking what’s deployed where

By adopting Terraform, you’ll gain:

  • Faster, more reliable deployments
  • Easier collaboration with your team through version-controlled infrastructure
  • Consistent environments across development, testing, and production
  • The ability to scale your infrastructure management as your needs grow

What you’ve learned here provides a solid foundation, but there’s more to discover. In the upcoming articles, we’ll guide you through the practical steps of working with Terraform in Azure:

Part 2: Getting Started with Terraform in Azure

  • Setting up your development environment with the necessary tools
  • Writing and understanding your first Terraform configuration
  • Deploying your first resources to Azure
  • Mastering the core Terraform workflow: init, plan, apply

Part 3: Your First Real-World Project

  • Building a complete multi-resource environment
  • Implementing resource dependencies and relationships
  • Organizing your configurations for maintainability
  • Applying infrastructure best practices from day one

Part 4: Advanced Terraform Concepts

  • Managing state files securely in team environments
  • Creating flexible configurations with variables and outputs
  • Building reusable components with Terraform modules
  • Managing multiple environments with workspaces

The learning curve might seem steep at first, but each concept builds naturally on what you’ve already learned. By the end of this series, you’ll have all the tools you need to manage your Azure infrastructure efficiently, consistently, and confidently.

Ready to start your hands-on Terraform journey? Head to Part 2: Getting Started with Terraform in Azure where we’ll help you set up your environment and deploy your first resource with Terraform.