Part 5: Deploying Infrastructure with Terraform in Azure Pipelines
📘 This post is part of the DevSecOps for Java series:
🏗️ Why Automate Infrastructure Deployment?
Scanning Terraform code is great (and we did that in Part 4),
But at some point, you need to actually provision the infrastructure:
Virtual Networks, App Services, Storage, Event Grid, etc.
Doing it manually = slow, inconsistent, and error-prone.
Using Terraform + Azure Pipelines = automated, repeatable, and secure.
🔧 Pre-requisites
Before we write the pipeline steps, make sure you:
-
- Have a working Terraform directory (
terraform/
)
- Have a working Terraform directory (
-
- Are using a remote backend (Azure Storage or Terraform Cloud)
-
- Have created an Azure service connection in Azure DevOps (SPN with Contributor or least privilege)
-
- Store secrets like client ID, tenant ID, and secret in DevOps pipeline variables or key vault
🧪 Terraform Directory Structure
Example layout:
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── backend.tf
Make sure your backend.tf
points to your Azure Storage account for state management.
🛠️ Add Terraform Apply Stage to Azure DevOps Pipeline
- stage: TerraformApply
displayName: 'Deploy Infrastructure to Azure'
dependsOn: IaCScan
jobs:
- job: terraformApply
displayName: 'Run Terraform Apply'
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
- task: AzureCLI@2
displayName: 'Terraform Init & Apply'
inputs:
azureSubscription: 'YourServiceConnectionName'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
cd terraform
terraform init -backend-config="..." # your backend values
terraform validate
terraform plan -out=tfplan.out
terraform apply -auto-approve tfplan.out
🧠 What Each Command Does
-
terraform init
: Downloads providers, configures backend
-
terraform validate
: Makes sure the.tf
files are syntactically valid
-
terraform plan
: Shows what will be created/changed
-
terraform apply
: Provisions the actual resources on Azure
🔐 Secure Your Pipeline
-
- Store sensitive values as pipeline secrets or in Azure Key Vault
-
- Lock down the service principal’s permissions to only what’s needed
-
- Use
terraform apply -input=false
to avoid prompts
- Use
🧼 Optional Enhancements
-
- Add
terraform destroy
in a separate stage for teardown in test environments
- Add
-
- Store plan output as an artifact to audit changes
-
- Use
terraform fmt -check
for code style checks
- Use
-
- Add environment approvals before applying to production
🧭 Example Output
After apply runs, you should see logs like:
azurerm_resource_group.rg: Creation complete after 3s
azurerm_storage_account.sa: Creation complete after 6s
...
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
💥 Your infrastructure is now provisioned — automatically, cleanly, and consistently.
✅ What You’ve Done So Far
You now have a secure, real-world pipeline that:
-
- Scans your dependencies with Snyk
-
- Scans your code with SonarQube
-
- Audits your infra-as-code with Checkov
-
- Applies your Terraform-defined infrastructure to Azure
⏭️ What’s Next?
Now that your infrastructure is ready, it’s time to:
🎯 Deploy the Java application onto the Azure resources we’ve just provisioned.
We’ll do that in Part 6: Application Deployment to Azure App Service