Skip to main content
In this lesson we implement an advanced Azure DevOps pipeline for Terraform that separates Plan and Apply into distinct stages, publishes the immutable plan as a pipeline artifact, and enforces a manual approval before applying that plan. This pattern improves auditability and safety by ensuring the exact plan that was reviewed is what gets applied. Overview
  • Plan stage: run terraform init and terraform plan --out=..., then publish the generated plan as a pipeline artifact.
  • Apply stage: run as a deployment job tied to an Azure DevOps environment (with approval checks). The job downloads the plan artifact and runs terraform apply against the downloaded plan file.
Pipeline YAML (complete):
Stages at a glance Key points explained
  • Structure
    • The pipeline uses top-level stages: containing Plan and Apply. Each stage has jobs, and jobs contain steps/tasks. Splitting Plan and Apply allows you to review and approve the exact plan that will be run.
  • Plan stage
    • terraform init configures the backend and providers so Terraform can calculate changes.
    • terraform plan with --out=$(Build.ArtifactStagingDirectory)/tfplan produces a binary plan file on the agent. This file is required for deterministic apply and must be preserved beyond the job lifecycle.
    • PublishPipelineArtifact@1 uploads that plan to Azure DevOps pipeline storage. Uploaded artifacts are associated with the pipeline run and are immutable for traceability.
  • Artifact location and download
    • When published, the tfplan artifact is stored with the pipeline run and is immutable and traceable.
    • In Apply, DownloadPipelineArtifact@2 retrieves the artifact into $(Pipeline.Workspace)/tfplan. The concrete plan file path becomes $(Pipeline.Workspace)/tfplan/tfplan, which is what terraform apply consumes.
  • Apply stage and approvals
    • The Apply job is a deployment job scoped to an Azure DevOps environment terraform-approval. Environments support checks such as manual approvals; when configured, the deployment pauses until an approver releases it.
    • After approval, the job downloads the plan, runs terraform init again (to ensure backend/provider configuration on the agent), and applies the exact binary plan via terraform apply --auto-approve <plan-file>.
Create an Azure DevOps environment named terraform-approval (or change the pipeline to reference an existing environment). Configure approvals and checks for that environment to enforce a manual approval gate before the Apply job runs.
The environment creation UI in Azure DevOps looks like this:
The image shows an Azure DevOps interface with a form to create a new environment, offering options for resources like Kubernetes and virtual machines.
Sample Terraform changes (example HCL)
  • Commit an HCL change to trigger Plan stage. Example Terraform configuration creating a resource group and storage account:
Example pipeline console output excerpts
  • Downloading artifact:
  • Terraform apply execution (abridged):
Disabling automatic runs
  • The example pipeline uses trigger: none to disable CI. If you want CI back, adjust the trigger section to include branches or paths.
Final notes
  • Using pipeline artifacts plus environment approvals provides a safe, auditable, and repeatable process: generate the plan once, keep it immutable, and only apply what was reviewed.
  • Keep credentials and service connections minimal. Grant the service connection only the permissions required.
Ensure the Azure DevOps service connection tf-svc-01 has the required permissions: access to the backend storage account/container and the target subscription/resource group for resource creation. Misconfigured permissions will cause backend init or apply failures.
Links and references

Watch Video