If you’ve been following along with my Terraform journey, you know we’ve covered the basics of providers, resources, state, and variables. Now it’s time to put that knowledge to work with Microsoft Azure.
Why Azure + Terraform?
- Multi-cloud consistency: Same workflow for Azure, AWS, Nutanix, or on-prem
- State management: Track what’s deployed and detect drift
- HCL readability: Cleaner syntax than JSON-based ARM templates
Prerequisites
- Terraform installed (
>= 1.3recommended) - Azure CLI installed
- An active Azure subscription
terraform -v
az --version
Authentication Methods
1. Azure CLI Authentication (Local Development)
az login
Terraform automatically picks up your CLI session.
2. Service Principal (Automation)
az ad sp create-for-rbac \
--name "terraform-sp" \
--role Contributor \
--scopes /subscriptions/<YOUR_SUBSCRIPTION_ID>
Never commit these credentials to Git. Use environment variables:
export ARM_CLIENT_ID="your-app-id"
export ARM_CLIENT_SECRET="your-password"
export ARM_TENANT_ID="your-tenant-id"
export ARM_SUBSCRIPTION_ID="your-subscription-id"
Configuring the Provider
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}
Creating Your First Resource
resource "azurerm_resource_group" "rg" {
name = "rg-terraform-demo"
location = "eastus"
tags = {
Environment = "Development"
ManagedBy = "Terraform"
}
}
terraform init
terraform plan
terraform apply
Using Variables for Flexibility
variables.tf
variable "resource_group_name" {
description = "Name of the resource group"
type = string
}
variable "location" {
description = "Azure region for resources"
type = string
default = "eastus"
}
terraform.tfvars
resource_group_name = "rg-myapp-prod"
location = "westus2"
Remote State in Azure Storage
# Create storage account for state
az group create --name rg-terraform-state --location eastus
az storage account create \
--name tfstateacct001 \
--resource-group rg-terraform-state \
--sku Standard_LRS
az storage container create \
--name tfstate \
--account-name tfstateacct001
Configure backend:
terraform {
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "tfstateacct001"
container_name = "tfstate"
key = "terraform.tfstate"
}
}
CI/CD Integration
.github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
env:
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve tfplan
Best Practices
- Use service principals for CI/CD
- Store state remotely with locking enabled
- Parameterize everything
- Tag your resources
- Review plans before apply