← Back to Guides

Deploying Terraform via Azure Pipelines

AdvancedAzure DevOps2026-03-14

Overview

Automating Terraform through Azure DevOps pipelines ensures consistent, repeatable infrastructure deployments with full audit trails and approval controls.

Prerequisites

  • Azure DevOps project with a Git repository
  • Service Connection to Azure (Workload Identity Federation recommended)
  • Storage Account for Terraform remote state
  • Terraform extension installed from the Azure DevOps Marketplace

Remote State Configuration

Store your Terraform state in Azure Blob Storage:

terraform {
  backend "azurerm" {
    resource_group_name  = "rg-terraform-state"
    storage_account_name = "stterraformstate"
    container_name       = "tfstate"
    key                  = "infrastructure.tfstate"
  }
}

Create the storage account beforehand (you cannot use Terraform to create its own state storage).

Pipeline Definition

trigger:
  branches:
    include:
      - main
  paths:
    include:
      - infra/*

variables:
  - group: terraform-vars
  - name: workingDirectory
    value: '$(System.DefaultWorkingDirectory)/infra'

stages:
  - stage: Plan
    jobs:
      - job: TerraformPlan
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: TerraformInstaller@1
            inputs:
              terraformVersion: 'latest'

          - task: TerraformTaskV4@4
            displayName: 'Terraform Init'
            inputs:
              provider: 'azurerm'
              command: 'init'
              workingDirectory: '$(workingDirectory)'
              backendServiceArm: 'terraform-service-connection'
              backendAzureRmResourceGroupName: 'rg-terraform-state'
              backendAzureRmStorageAccountName: 'stterraformstate'
              backendAzureRmContainerName: 'tfstate'
              backendAzureRmKey: 'infrastructure.tfstate'

          - task: TerraformTaskV4@4
            displayName: 'Terraform Plan'
            inputs:
              provider: 'azurerm'
              command: 'plan'
              workingDirectory: '$(workingDirectory)'
              environmentServiceNameAzureRM: 'terraform-service-connection'
              commandOptions: '-out=tfplan'

          - publish: '$(workingDirectory)/tfplan'
            artifact: 'tfplan'

  - stage: Apply
    dependsOn: Plan
    condition: succeeded()
    jobs:
      - deployment: TerraformApply
        environment: 'infrastructure-production'
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self

                - download: current
                  artifact: 'tfplan'

                - task: TerraformInstaller@1
                  inputs:
                    terraformVersion: 'latest'

                - task: TerraformTaskV4@4
                  displayName: 'Terraform Init'
                  inputs:
                    provider: 'azurerm'
                    command: 'init'
                    workingDirectory: '$(workingDirectory)'
                    backendServiceArm: 'terraform-service-connection'
                    backendAzureRmResourceGroupName: 'rg-terraform-state'
                    backendAzureRmStorageAccountName: 'stterraformstate'
                    backendAzureRmContainerName: 'tfstate'
                    backendAzureRmKey: 'infrastructure.tfstate'

                - task: TerraformTaskV4@4
                  displayName: 'Terraform Apply'
                  inputs:
                    provider: 'azurerm'
                    command: 'apply'
                    workingDirectory: '$(workingDirectory)'
                    environmentServiceNameAzureRM: 'terraform-service-connection'
                    commandOptions: '$(Pipeline.Workspace)/tfplan/tfplan'

Service Connection Setup

For the most secure approach, use Workload Identity Federation:

  1. Go to Project Settings → Service Connections
  2. Choose Azure Resource Manager → Workload Identity Federation (automatic)
  3. Select scope (subscription or resource group)
  4. Name it consistently (e.g., terraform-service-connection)

This eliminates the need for client secrets.

State Locking

Azure Blob Storage provides automatic state locking via blob leases. This prevents concurrent modifications that could corrupt your state file.

Best Practices

  1. Always run plan before apply — never skip the review step
  2. Use approval gates on the Apply stage
  3. Pin Terraform versions to avoid unexpected changes
  4. Use variable groups for environment-specific values
  5. Store sensitive values in Key Vault referenced via variable groups
  6. Use -detailed-exitcode in plan to detect actual changes
  7. Tag all resources with environment, owner, and cost centre