Mastering the Basics: Terraform and Infrastructure as Code in Azure
Introduction
Managing cloud infrastructure manually is slow, error-prone, and impossible to scale. Infrastructure as Code (IaC) solves these problems by allowing infrastructure to be defined, deployed, and maintained with code. Terraform has become the go-to IaC tool because of its declarative approach, multi-cloud support, and ability to track infrastructure changes over time.
This article will walk through the fundamentals of Terraform, covering why manual provisioning causes problems, how Terraform improves cloud management, and key best practices to ensure scalable and secure deployments.
The Problem with Manual Cloud Management
Inconsistencies & Errors
Manually deploying Azure resources might seem straightforward at first, but as complexity grows, so do the risks. No two environments are ever truly identical when built manually, leading to configuration drift, security vulnerabilities, and unpredictable behaviour across development, staging, and production.
For example, one engineer may deploy a virtual network with custom security rules, while another follows a different configuration. Over time, these minor inconsistencies pile up, leading to hard-to-troubleshoot issues, unexpected downtime, and a lack of confidence in the stability of the infrastructure.
Scaling Challenges
Infrastructure needs to scale as fast as the business does—but without automation, growth is slow and painful. Manually provisioning new services or expanding existing ones is time-consuming, prone to human error, and often leads to bottlenecks that delay deployments.
Terraform eliminates these roadblocks by allowing teams to define reusable templates for cloud resources. Whether you need to spin up a new region, deploy a high-availability cluster, or provision dozens of identical environments, Terraform ensures the process is fast, repeatable, and error-free.
Compliance & Auditing Issues
In highly regulated industries, tracking infrastructure changes is non-negotiable as for most other organisations it’s seen as a basic need. When managing resources manually, answering basic compliance questions like:
- Who deployed this resource?
- When was it last updated?
- Was this change authorised?
It becomes nearly impossible without a centralised, version-controlled approach.
Terraform solves this by integrating seamlessly with Git-based workflows, providing full visibility into infrastructure changes. Teams can implement policy enforcement, automated security scanning, and audit trails, ensuring they stay compliant while avoiding costly misconfigurations.
What is Infrastructure as Code (IaC)?
IaC ensures infrastructure is repeatable, scalable, and secure. Instead of manually provisioning resources, infrastructure is defined in code, making deployments consistent and predictable.
Declarative vs. Imperative
- Imperative: Step-by-step scripting (PowerShell, Bash), where each step must be manually defined.
- Declarative: Define the desired state (Terraform, ARM/Bicep), and Terraform determines the required steps.
Declarative models ensure idempotency, meaning Terraform maintains the desired state regardless of repeated executions.
What is Terraform?
Terraform is an open-source IaC tool that manages infrastructure across Azure, AWS, GCP, and on-premises environments. Unlike native tools like ARM templates or Bicep, Terraform provides:
- ✅ A single language for multi-cloud management
- ✅ State tracking to manage infrastructure changes over time
- ✅ A declarative syntax that simplifies deployments
Terraform ensures teams can manage infrastructure in a repeatable and scalable way without manually provisioning resources every time.
How Terraform Works
Terraform follows a simple but powerful workflow:
- Write – Define infrastructure as code (.tf files).
- Plan – Terraform previews what changes will be made (
terraform plan
). - Apply – Terraform provisions the infrastructure (
terraform apply
). - Manage – Terraform keeps track of state and ensures consistency (
terraform state
).
Deploying an Azure Resource Group with Terraform
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "example" {
name = "my-tf-resource-group"
location = "West Europe"
}
With just a few lines of code, Terraform provisions infrastructure that can be replicated, versioned, and managed efficiently.
Best Practices & Advanced Techniques
Using Variables & Parameter Files
Hardcoding values in Terraform files is a recipe for disaster. It makes configurations rigid, difficult to update, and prone to human error. Instead, use variables to keep infrastructure definitions flexible and reusable.
Defining Variables variables.tf
Variables allow you to pass values dynamically, making deployments adaptable to different environments:
variable "location" {
type = string
default = "West Europe"
}
Using .tfvars for Different Environments Instead of manually updating the Terraform configuration, define environment-specific values in a .tfvars file:
location = "North Europe"
Applying the Configuration with a Variable File
terraform apply -var-file="dev.tfvars"
This keeps infrastructure code clean, modular, and easy to manage across multiple deployments.
Remote State Management
Terraform relies on a state file (terraform.tfstate) to keep track of resources. Storing this file locally can lead to lost data, inconsistent deployments, and major headaches when working in teams.
A remote backend solves this by keeping the state file in a central location, allowing multiple engineers to collaborate without conflicts.
Using a Remote Backend in Azure
terraform {
backend "azurerm" {
resource_group_name = "terraform-backend"
storage_account_name = "tfstatedata"
container_name = "tfstate"
key = "terraform.tfstate"
}
}
Why this matters:
✅ Prevents accidental overwrites when multiple users apply changes.
✅ Enables state locking, avoiding deployment conflicts.
✅ Ensures teams always work with the latest version of the infrastructure.
Workspaces for Multi-Environment Management
Managing Dev, Test, and Prod environments separately is essential, but maintaining duplicate configurations is inefficient. Terraform workspaces provide a streamlined way to manage multiple environments without unnecessary duplication.
Creating and Switching Workspaces
terraform workspace new dev
terraform workspace new prod
terraform workspace select dev
terraform apply
Workspaces prevent configuration drift by ensuring each environment has isolated state tracking, so teams can deploy changes without affecting production.
Best Practices:
✅ Keep environments separate to avoid accidental changes to production.
✅ Ensure backend storage keys are unique per workspace to prevent conflicts.
✅ Use workspaces for small-scale environment separation, but consider multiple state files for more complex setups.
Modularising Infrastructure with Terraform Modules
Terraform modules structure infrastructure as reusable components. Instead of duplicating code, use modules for frequently deployed infrastructure.
Example Module (modules/network/main.tf
):
resource "azurerm_virtual_network" "example" {
name = var.vnet_name
location = var.location
resource_group_name = var.resource_group_name
address_space = ["10.0.0.0/16"]
}
Using the Module (main.tf
):
module "network" {
source = "./modules/network"
vnet_name = "my-vnet"
location = "West Europe"
resource_group_name = "my-rg"
}
Modules improve code reusability, maintainability, and standardisation.
Securing Secrets
Never hardcode credentials. Hardcoding credentials in Terraform files is a security disaster waiting to happen. If credentials get committed to Git, you could be exposing sensitive information to the world.
Instead, use Azure Key Vault to securely store secrets and retrieve them at runtime.
Fetching a Secret from Azure Key Vault
data "azurerm_key_vault_secret" "db_password" {
name = "db-password"
key_vault_id = azurerm_key_vault.example.id
}
Best Practices:
✅ Store secrets in Key Vault, not in Terraform files.
✅ Use Terraform’s sensitive = true flag to mask values.
✅ Regularly rotate credentials to reduce risk exposure.
Common Terraform Pitfalls & How to Avoid Them
Terraform is powerful, but mistakes can cause outages, misconfigurations, and security risks. Avoid these common pitfalls:
Hardcoding Secrets
Why it’s a problem: Credentials and API keys stored in plain text can be leaked, compromised, or accidentally committed to Git.
✅ Solution: Always store secrets in a secrets manager like Azure Key Vault or Terraform Cloud Vault. Use sensitive = true in variables to prevent exposure in logs.
Skipping terraform plan
Why it’s a problem: Applying Terraform changes blindly can delete or modify resources unintentionally, leading to downtime.
✅ Solution: Always run:
terraform plan
This allows teams to review exactly what Terraform will change before applying.
Poor State Management
Why it’s a problem: Terraform state tracks infrastructure, and losing or corrupting it can result in orphaned resources, conflicting deployments, or infrastructure drift.
✅ Solution: Use remote state storage with state locking to avoid conflicts when multiple engineers are working on the same infrastructure.
Manual Infrastructure Changes
Why it’s a problem: Making changes directly in the Azure portal or CLI creates drift between the actual environment and Terraform’s state, leading to inconsistencies.
✅ Solution: Always use Terraform as the single source of truth. If manual changes are made, import them into Terraform state:
terraform import azurerm_resource_group.example /subscriptions/12345/resourceGroups/my-rg
This keeps Terraform in sync with real-world infrastructure.
Not Handling Dependencies Properly
Why it’s a problem: Resources often depend on each other. If dependencies aren’t managed correctly, deployments may fail due to timing issues.
✅ Solution: Use depends_on in Terraform to specify dependencies:
resource "azurerm_storage_account" "example" {
name = "examplestorage"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "LRS"
depends_on = [azurerm_resource_group.example]
}
This ensures Terraform provisions resources in the correct order.
Terraform is a powerful tool, but getting the fundamentals right is critical. Avoiding these common mistakes ensures secure, scalable, and reliable deployments, keeping infrastructure under control and easy to manage.
Final Thoughts
Terraform enables scalable, secure, and automated infrastructure. Get started by:
- Deploying a simple Terraform configuration to familiarise yourself with the tool.
- Exploring Terraform modules to enhance code reusability.
- Implementing remote state storage for team collaboration.
- Using workspaces and variables for multi-environment management.
Start automating your infrastructure today!