Using Terraform to deploy your Azure resources is becoming more and more popular; in some instances overtaking the use of ARM to deploy into Azure. I will show you in this blog how you can deploy your Azure Resources created in Terraform using Azure DevOps finishing with an example .yml pipeline.
What is Azure DevOps?
Deploying resources already into Azure; you probably already have came across using Azure DevOps, it is a hosted service by Microsoft that provides an end-to-end DevOps toolchain for developing and deploying software, along with this – it is a hosted service to deploy CI/CD Pipelines
Initial requirements before you can begin deploying
There are some prior requirements you need to complete before we can get deploying Terraform using Azure DevOps. These are:-
- Where to store the Terraform state file?
- Azure DevOps Project
- Azure Service Principal
- Sample Terraform code
Lets have a look at each of these requirements; I will include an example of each and how you can configure.
Where to store the Terraform state file?
When deploying Terraform there is a requirement that it must store a state file; this file is used by Terraform to map Azure Resources to your configuration that you want to deploy, keeps track of meta data and can also assist with improving performance for larger Azure Resource deployments.
In this deployment, I want to store the state file remotely in Azure; I will be storing my state file in a Storage Account container called:- tfstatedevops
Lets deploy the required storage container called tfstatedevops in Storage Account tamopstf inside Resource Group tamopstf
Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.
#Create Resource Group
New-AzureRmResourceGroup -Name "tamopstf" -Location "eastus2"
#Create Storage Account
New-AzureRmStorageAccount -ResourceGroupName "tamopstf" -AccountName "tamopstf" -Location eastus2 -SkuName Standard_LRS
#Create Storage Container
New-AzureRmStorageContainer -ResourceGroupName "tamopstf" -AccountName "tamopstf" -ContainerName "tfstatedevops"

Azure DevOps Project
Deploying Terraform using Azure DevOps, requires some sort of project; in this blog I will create a new project
This is documented already by Microsoft here, I recommend this guide to show you how to setup a DevOps Project similar to mine below
The DevOps Project in my example will be called TamOpsTerraform as below

Azure Service Principal
A Service Principal (SPN) is considered a best practice for DevOps within your CI/CD pipeline. It is used as an identity to authenticate you within your Azure Subscription to allow you to deploy the relevant Terraform code.
In this blog, I will show you how to create this manually (there is PowerShell / CLI but within this example I want you to understand the initial setup of this)
To begin creation, within your newly created Azure DevOps Project – select Project Settings

Select Service Connections

Select Create Service Connection -> Azure Resource Manager -> Service Principal (Automatic)
For scope level I selected Subscription and then entered as below, for Resource Group I selected tamopstf which I created earlier

Once created you will see similar to below

You can select Manage Service Principal to review further
When creating this way, I like to give it a relevant name so I can reference my SPN easier within my Subscription. This is done within “Manage Service Principal”
Settings -> Properties and change Name as below

You can also reference your SPN easier if you want to give it further IAM control to your subscription, in this setup I also give the SPN “contributor” access to my subscription.
Documented role assignment here by Microsoft

Sample Terraform code
We’re now near ready to configure your DevOps pipeline; but first! Some sample Terraform code to deploy. In my example I will deploy a Storage Account tamopssatf inside a Resource Group tamops-tf (Notice the reference to the tfstate resource_group_name, storage_account_name and container_name
provider "azurerm" {
# The "feature" block is required for AzureRM provider 2.x.
# If you're using version 1.x, the "features" block is not allowed.
version = "~>2.0"
features {}
}
terraform {
backend "azurerm" {}
}
data "azurerm_client_config" "current" {}
resource "azurerm_resource_group" "tamopsrg" {
name = "tamops-tf"
location = "eastus2"
}
resource "azurerm_storage_account" "tamopssa" {
name = "tamopssatf"
resource_group_name = azurerm_resource_group.tamopsrg.name
location = azurerm_resource_group.tamopsrg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
Deploy this into your repo

Deploying Terraform using Azure DevOps
The initial requirements now configured, time to setup Azure DevOps to deploy your Terraform into Azure.
Install the Terraform extension/task from here
The Terraform task enables running Terraform commands as part of Azure Build and Release Pipelines providing support for the following Terraform commands
- init
- validate
- plan
- apply
- destroy
Once installed, we can now configure a pipeline
Select your Repo -> Setup Build

Select Starter Pipeline

Now you are Produced with an .yml format. Further understand documented here
YML example Pipelines and further Terraform info is found here. My example Pipeline consists of snippets from this GitHub
In my Pipeline, I have two Stages
Validate:- To Validate my Terraform code, if validation fails the pipeline fails (consists of Terraform init & validate)
Deploy:- if Validation is successful, it moves to next stage of pipeline which is Deploying the Terraform code to deploy required Azure Resources (consists of Terraform plan & deploy)
Throughout the Pipeline, notice my reference to the previously created Storage Account, Resource Group and container for the Terraform state file along with the newly created SPN? (extraction below)
backendServiceArm: 'tamopstf'
backendAzureRmResourceGroupName: 'tamopstf'
backendAzureRmStorageAccountName: 'tamopstf'
backendAzureRmContainerName: 'tfstatedevops'
backendAzureRmKey: 'terraform.tfstate'
Full Azure DevOps Pipeline
stages :
- stage: validate
jobs:
- job: validate
continueOnError: false
steps:
- task: TerraformInstaller@0
displayName: 'install'
inputs:
terraformVersion: '0.12.3'
- task: TerraformTaskV1@0
displayName: 'init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: 'tamopstf'
backendAzureRmResourceGroupName: 'tamopstf'
backendAzureRmStorageAccountName: 'tamopstf'
backendAzureRmContainerName: 'tfstatedevops'
backendAzureRmKey: 'terraform.tfstate'
- task: TerraformTaskV1@0
displayName: 'validate'
inputs:
provider: 'azurerm'
command: 'validate'
- stage: deploy
jobs:
- deployment: deploy_terraform
continueOnError: false
environment: 'dev'
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: TerraformInstaller@0
displayName: 'install'
inputs:
terraformVersion: '0.12.3'
- task: TerraformTaskV1@0
displayName: 'init'
inputs:
provider: 'azurerm'
command: 'init'
backendServiceArm: 'tamopstf'
backendAzureRmResourceGroupName: 'tamopstf'
backendAzureRmStorageAccountName: 'tamopstf'
backendAzureRmContainerName: 'tfstatedevops'
backendAzureRmKey: 'terraform.tfstate'
- task: TerraformTaskV1@0
displayName: 'plan'
inputs:
provider: 'azurerm'
command: 'plan'
environmentServiceNameAzureRM: 'tamopstf'
- task: TerraformTaskV1@0
displayName: 'apply'
inputs:
provider: 'azurerm'
command: 'apply'
environmentServiceNameAzureRM: 'tamopstf'
Once you configure & save the above pipeline, you will see it beginning to run and can review both stages

After a few minutes, the build Pipeline will run through and if both stages are successful you will see similar to below

Reviewing the job, you will see a more thorough breakdown of the tasks

Selecting for example plan, you will see what Azure Resources are planned to be deployed

Reviewing inside the Azure Portal, you will see the newly created Resource Group & Storage Account

Great work!
If you followed this blog post, you now have a good solid introduction into how you can create your Terraform code and run successfully using Azure DevOps to deploy Azure Resources!
The pipeline I showed was a simple execution, you can configure this further depending on your requirements but hopefully a good base-line to get you started!
Hi, I was following your instructions and they look pretty good, but I have gotten to the part of creating the repo and getting the example.tf file into it. Your instructions appear to be missing a step as I’m getting told to add some code in Devops in the repo but struggling to understand how as you haven’t explained. Have I done something wrong? missed something? wonder if you could help please?
Hi Ashley, I had referenced undwr the Terraform code “Deploy this into your repo” – see “sample terraform code section”
had wrote the blog in understanding that those who follow had worked with Azure Devops before.
Heres a MS article to add code to repo:- https://docs.microsoft.com/en-us/azure/devops/repos/git/create-new-repo?view=azure-devops
Feel free to reach out to me on Twitter to discuss further or reply to comment
Thank you for reading the blog post, hope you enjoyed it
Hi,
I’m seeing the same issue. I have the “example.tf” file on Azure DevOps repo. However, I see “Error: No configuration files” in the deployment stage. Please help.
Hi,
Could mail me some screenshot and your Azure devops pipeline?
Thanks
Nevermind, I made a silly mistake, instead of “example.tf”, I had “example.cf”. After the change it worked as you outlined.
Glad you got the issue resolved! Do reach out if you have any queries and feel free to check my other blog posts out 👍
Thanks for the blog! It was really useful.
Thank you, glad you enjoyed it!
Nice! Thank you for taking your time out to pen down this blog. Helped me big time!
Thanks Kiran, good luck with your Azure DevOps & Terraform journey! Check out my other blog posts also.
Can you explain how exactly the build environment uses the state file to only add the infrastructure changes but not deploy them all over again?
Hi Sangeetha,
Thanks for the comment – I have included the Terraform documentation on “state”, hope this helps – let me know
https://www.terraform.io/docs/state/index.html
Thanks
Thomas
Hi,
Using your sample code, I was able to build a linux vm. Can you help me with post install script.
Below doesn’t work.
– task: SSH@0
inputs:
runOptions: ‘script’
scriptPath: ‘new-node-setup.sh’
readyTimeout: ‘20000’
Hi pd, what error are you getting?
Thanks
Thomas
##[error]Error: Input required: sshEndpoint
Just to make it clear: I have a script “new-node.sh” which is in my DevOps repo (along with my variables.tf and vm.tf) and I want to run after the node build is done within the same pipeline.
Just to make it clear: I have a script “new-node.sh” which is in my DevOps repo and I want to run after the node build is done within the same pipeline.
I’m using username/password stored in azure key vault. Once the node build is done I can login using these credentials.
Hi PD,
see here:- https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/ssh?view=azure-devops
the task you are trying to use, requires sshEndpoint:
Thanks
Thomas
I think the backend block is incorrect, based on container created. I’m still somewhat new to Terraform, so could be wrong…but I think you provided what should be the “key” as the “container_name”.
Hi Frank,
Yes you are correct. I ran an update on this blog post recently and show its ended like that.
Will update now,
Thanks
Hi Frank,
Now updated
Thanks
Thomas
Nice article.
One suggestion I would make – strongly – is to separate the plan and apply steps. I would never, ever do a terraform apply without first scutinising the plan because, you know, bitter experience 🙂
Hi Peter,
Thanks for the comment and feedback
Ofcourse, in the Production world 99% of the time you will want to review the plan before the apply 🙂 This blog post caters for the initial deploying terraform using Azure DevOps. I’ve a number of posts similar which do show plan & apply at different stages – even using an approval gate
Thanks
Thomas
Great article. However it is worth mentioning that Manage Service Principal is not supported on Azure DevOps server editions.
Thanks for the message – this blog post was aimed solely at using Azure DevOps online/web based rather than server