By the end of this lab, you'll be able to:
- Install the Terraform extension — enabling Terraform tasks (
init,plan,apply) in Azure Pipelines - Create an Azure DevOps repository — to host all pipeline YAML, Terraform code, and app source
- Configure
production.tfvars— with your environment-specific values includingadmin_object_id - Update the pipeline YAML — with your Terraform backend (storage account, resource group, service connection)
- Run the pipeline — to provision the full AKS infrastructure via Terraform
⏱️ Estimated Time: ~30 minutes
Before starting, ensure you have:
- Terraform basics — variables, state,
init/plan/apply - Azure DevOps pipeline concepts — stages, tasks, YAML
- Completed Lab 1.1 — WIF service connection created
- Completed Lab 1.2 — storage account and container created
- Completed Lab 1.3 — Group Object ID recorded
-
🔌 Navigate to the Marketplace
Go to the Terraform extension on the Azure DevOps Marketplace and click Get it free.
-
🏢 Select your organisation
Choose your Azure DevOps organisation (e.g.,
devopsjourneyapr2026) and click Install.The extension adds the following pipeline tasks:
TerraformInstaller@1— downloads and installs a specific Terraform versionTerraformTaskV4@4— runsinit,validate,plan,apply, ordestroy
✅ Expected Output:
Extension "Terraform" successfully installed to your organisation.
-
📁 Navigate to Repos
In your Azure DevOps project, click Repos in the left navigation.
-
➕ Create a new repository
Click the repository dropdown → New repository.
- Name:
DevOps-Journey(or your preferred name) - Keep Add a README checked
- Name:
-
📋 Copy the repository contents
Clone the repo and push the contents from this GitHub repository:
# Clone your new Azure DevOps repo git clone https://dev.azure.com/<your-org>/DevOps-Journey/_git/DevOps-Journey cd DevOps-Journey # Copy the lab 2 contents into it cp -r /path/to/labs/2-AzureDevOps-Terraform-Pipeline/* . git add -A git commit -m "Initial Terraform pipeline setup" git push
-
📝 Open the tfvars file
Open
labs/2-AzureDevOps-Terraform-Pipeline/vars/production.tfvarsand update the values for your environment. -
🔑 Set
admin_object_idThis is the Entra ID Group Object ID from Lab 1.3. It grants the group:
- Key Vault Administrator RBAC on the Key Vault
- AKS Cluster Admin RBAC on the AKS cluster
# Replace with your actual values admin_object_id = "278cc1b9-653d-464a-90f7-309e02d4b5d1"
⚠️ Useadmin_object_id— not the old nameaccess_policy_id. The Key Vault in this repo uses RBAC-based access control, not legacy access policies.Other values to review and update in the tfvars file:
resource_group_name = "devopsjourneyapr2026-rg" location = "uksouth" cluster_name = "devopsjourneyapr2026" kubernetes_version = "1.33" acr_name = "devopsjourneyapr2026acr" key_vault_name = "devopsjourneyapr2026-kv"
-
📝 Open the pipeline YAML
Open
labs/2-AzureDevOps-Terraform-Pipeline/pipelines/lab2pipeline.yamland update the backend variables:variables: - name: backendServiceArm value: 'azure-devops-journey-apr2026' # ← Your WIF service connection name - name: backendAzureRmResourceGroupName value: 'devops-journey-rg-apr2026' # ← Your Terraform state RG - name: backendAzureRmStorageAccountName value: 'devopsjourneyapr2026' # ← Your storage account name - name: backendAzureRmContainerName value: 'tfstate' - name: backendAzureRmKey value: 'terraform.tfstate'
💡 These values connect Terraform to the Azure Blob backend you created in Lab 1.2.
-
🔧 Create the pipeline in Azure DevOps
- Navigate to Pipelines → New Pipeline
- Choose Azure Repos Git → select your repository
- Choose Existing Azure Pipelines YAML file
- Select the branch and path to
lab2pipeline.yaml
-
💾 Save and run
Click Save and run.
-
📋 Review pipeline stages
The pipeline runs the following Terraform stages:
- Validate —
terraform validatechecks HCL syntax - Plan —
terraform planpreviews all resources to be created - Apply —
terraform applyprovisions the infrastructure
✅ Expected Output (Terraform Apply stage):
Apply complete! Resources: 25 added, 0 changed, 0 destroyed. Outputs: aks_cluster_name = "devopsjourneyapr2026" acr_name = "devopsjourneyapr2026acr" key_vault_name = "devopsjourneyapr2026-kv" resource_group_name = "devopsjourneyapr2026-rg" - Validate —
Infrastructure checklist:
- Pipeline completes all stages (Validate, Plan, Apply) with green ticks
- AKS cluster
devopsjourneyapr2026visible in Azure Portal - ACR
devopsjourneyapr2026acrcreated - Key Vault
devopsjourneyapr2026-kvcreated with RBAC access control enabled - Application Insights workspace-based resource created
Technical validation:
# Verify AKS cluster
az aks show \
--name devopsjourneyapr2026 \
--resource-group devopsjourneyapr2026-rg \
--query "{Name:name, K8sVersion:kubernetesVersion, State:provisioningState}" -o table
# Verify ACR
az acr show \
--name devopsjourneyapr2026acr \
--query "{Name:name, LoginServer:loginServer, SKU:sku.name}" -o table
# Verify Key Vault (RBAC-based)
az keyvault show \
--name devopsjourneyapr2026-kv \
--resource-group devopsjourneyapr2026-rg \
--query "{Name:name, RBAC:properties.enableRbacAuthorization}" -o table✅ Expected Output:
Name K8sVersion State
--------------------- ------------ ---------
devopsjourneyapr2026 1.33.x Succeeded
Name LoginServer SKU
----------------------- --------------------------------------- ------
devopsjourneyapr2026acr devopsjourneyapr2026acr.azurecr.io Premium
Name RBAC
------------------------ ------
devopsjourneyapr2026-kv True
🔧 Troubleshooting (click to expand)
# Problem: Pipeline fails at Terraform Init with "AuthorizationFailed"
# Solution: Ensure the WIF service connection has Contributor + User Access Admin on the subscription
az role assignment list \
--assignee "$(az ad sp list --display-name 'azure-devops-journey-identity' --query '[0].id' -o tsv)" \
--query "[].roleDefinitionName" -o tsv
# Problem: "admin_object_id is not set" or Terraform variable error
# Solution: Check production.tfvars — ensure admin_object_id is populated with the correct group ID
az ad group show --group "devopsjourney-aks-group-apr2026" --query id -o tsv
# Problem: AKS version "1.33" not available in your region
# Solution: List available versions
az aks get-versions --location uksouth --query "values[].version" -o tsv | sort -V | tail -5
# Problem: Pipeline cannot access Terraform state storage account
# Solution: Assign Storage Blob Data Contributor to the WIF service principal on the storage account
az role assignment create \
--assignee "<wif-sp-object-id>" \
--role "Storage Blob Data Contributor" \
--scope "/subscriptions/<sub-id>/resourceGroups/devops-journey-rg-apr2026/providers/Microsoft.Storage/storageAccounts/devopsjourneyapr2026"backendServiceArmauthenticates Terraform's Azure backend — it tells theTerraformTaskV4task which Azure DevOps service connection to use when reading/writing the remote state file in Azure Blob Storage.- The Key Vault uses RBAC authorization (
enableRbacAuthorization = true) — not legacy access policies. Theadmin_object_idcreates a Key Vault Administrator role assignment.access_policy_idwas the old name used when Key Vault access policies were configured. - Kubernetes
1.33is configured inproduction.tfvarsaskubernetes_version = "1.33". AKS uses the patch upgrade channel so patch versions are managed automatically. - User Access Administrator is needed to create role assignments — Terraform creates RBAC assignments (e.g., ACR pull for AKS, Key Vault roles) during
terraform apply. Contributor alone cannot manage role assignments.
All Azure infrastructure is now provisioned via the Terraform pipeline. In the next lab you'll build and push the Python/Flask Docker image to your new Azure Container Registry.
← Back to Lab 1.3 | Continue to Lab 3 →