Skip to content
Pipelines and Pizza 🍕
Go back

Connecting Terraform to Azure

3 min read

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.3 recommended)
  • 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

References