TerraformPilot

Terraform

Terraform CI/CD with Azure DevOps Pipelines

Automate Terraform with Azure DevOps Pipelines. YAML pipelines, service connections, environment approvals, and Azure backend state configuration.

LLuca Berton1 min read

Quick Answer

#
trigger:
  branches:
    include: [main]
 
pool:
  vmImage: 'ubuntu-latest'
 
steps:
  - task: TerraformInstaller@1
    inputs:
      terraformVersion: '1.8.5'
  - script: terraform init
  - script: terraform plan -out=tfplan
  - script: terraform apply -auto-approve tfplan
    condition: eq(variables['Build.SourceBranch'], 'refs/heads/main')

Production Pipeline

#
# azure-pipelines.yml
trigger:
  branches:
    include: [main]
  paths:
    include: [terraform/**]
 
pr:
  branches:
    include: [main]
 
variables:
  - group: terraform-vars
  - name: TF_IN_AUTOMATION
    value: 'true'
 
stages:
  - stage: Validate
    jobs:
      - job: Validate
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: TerraformInstaller@1
            inputs:
              terraformVersion: '1.8.5'
          - script: |
              cd terraform
              terraform init -backend=false
              terraform validate
              terraform fmt -check
            displayName: 'Validate & Format'
 
  - stage: Plan
    dependsOn: Validate
    jobs:
      - job: Plan
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: TerraformInstaller@1
            inputs:
              terraformVersion: '1.8.5'
          - task: AzureCLI@2
            inputs:
              azureSubscription: 'terraform-service-connection'
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                cd terraform
                export ARM_CLIENT_ID=$servicePrincipalId
                export ARM_CLIENT_SECRET=$servicePrincipalKey
                export ARM_SUBSCRIPTION_ID=$(az account show --query id -o tsv)
                export ARM_TENANT_ID=$tenantId
                terraform init
                terraform plan -out=tfplan -no-color
            addSpnToEnvironment: true
          - publish: terraform/tfplan
            artifact: tfplan
 
  - stage: Apply
    dependsOn: Plan
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: Apply
        pool:
          vmImage: 'ubuntu-latest'
        environment: 'production'  # Requires approval
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: tfplan
                - task: TerraformInstaller@1
                  inputs:
                    terraformVersion: '1.8.5'
                - task: AzureCLI@2
                  inputs:
                    azureSubscription: 'terraform-service-connection'
                    scriptType: 'bash'
                    scriptLocation: 'inlineScript'
                    inlineScript: |
                      cd terraform
                      export ARM_CLIENT_ID=$servicePrincipalId
                      export ARM_CLIENT_SECRET=$servicePrincipalKey
                      export ARM_SUBSCRIPTION_ID=$(az account show --query id -o tsv)
                      export ARM_TENANT_ID=$tenantId
                      terraform init
                      terraform apply -auto-approve $(Pipeline.Workspace)/tfplan/tfplan
                  addSpnToEnvironment: true

Azure Backend Configuration

#
terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-state-rg"
    storage_account_name = "tfstate12345"
    container_name       = "tfstate"
    key                  = "production.tfstate"
  }
}

Service Connection Setup

#
  1. Project Settings → Service connections → New
  2. Choose Azure Resource Manager
  3. Select Service principal (automatic)
  4. Name it terraform-service-connection

Environment Approvals

#
  1. Pipelines → Environments → production
  2. Click Approvals and checks
  3. Add Approvals → select approvers
  4. Add Business hours (optional)

Variable Groups

#
# Create variable group in Azure DevOps
# Project Settings → Pipelines → Library → Variable groups
# Add: ARM_CLIENT_ID, ARM_CLIENT_SECRET (secret), ARM_SUBSCRIPTION_ID, ARM_TENANT_ID
#

Conclusion

#

Use Azure DevOps service connections for authentication, the azurerm backend for state, Environment approvals for production gates, and the AzureCLI task to inject service principal credentials. Publish plan artifacts between stages and use deployment jobs for apply.

#Terraform#Azure DevOps#CI/CD#DevOps#Infrastructure as Code

Share this article