Skip to content

Azure IaC DevOps for Terraform Project

Step-00: Introduction

Build Pipeline - CI

  • Implement Build Pipeline (Continuous Integration Pipeline)
  • Use CopyFiles and PublishArtifacts Tasks in Build Pipeline

Release Pipelines - CD

  • Implement Deployment stages Dev, QA, Stage and Prod
  • In each stage implement below listed Tasks for a Ubuntu Agent
  • terraform install
  • terraform init
  • terraform validate
  • terraform plan
  • terraform apply -auto-approve
  • Test both CI CD Pipelines

  • Azure DevOps Parallelism Free Tier Request Form

Step-01: Review Terraform Configs

  • Folder: Git-Repo-Files/terraform-manifests

Step-01-01: c1-versions.tf

# Terraform State Storage to Azure Storage Container (Values will be taken from Azure DevOps)
  backend "azurerm" {
     }   

Step-01-02: c7-01-web-linuxvm-input-variables.tf

  • Define Input Variables for VM Size and VM admin user name.
  • If we required we can parameterize more arguments in azurerm_linux_virtual_machine resource.
    # Linux VM Input Variables Placeholder file.
    variable "web_linuxvm_size" {
      description = "Web Linux VM Size"
      type = string 
      default = "Standard_DS1_v2"
    }
    
    variable "web_linuxvm_admin_user" {
      description = "Web Linux VM Admin Username"
      type = string 
      default = "azureuser"
    }
    

Step-01-03: c7-05-web-linuxvm-resource.tf

  • Update arguments size, admin_username and admin_ssh_key.username in Linux VM Resource
    # Resource: Azure Linux Virtual Machine
    resource "azurerm_linux_virtual_machine" "web_linuxvm" {
      name = "${local.resource_name_prefix}-web-linuxvm"
      #computer_name = "web-linux-vm" # Hostname of the VM (Optional)
      resource_group_name = azurerm_resource_group.rg.name
      location = azurerm_resource_group.rg.location 
      size = var.web_linuxvm_size
      admin_username = var.web_linuxvm_admin_user
      network_interface_ids = [ azurerm_network_interface.web_linuxvm_nic.id ]
      admin_ssh_key {
        username = var.web_linuxvm_admin_user
        public_key = file("${path.module}/ssh-keys/terraform-azure.pub")
      }
      os_disk {
        caching = "ReadWrite"
        storage_account_type = "Standard_LRS"
      }  
      source_image_reference {
        publisher = "RedHat"
        offer = "RHEL"
        sku = "83-gen2"
        version = "latest"
      }  
      #custom_data = filebase64("${path.module}/app-scripts/redhat-webvm-script.sh")
      custom_data = base64encode(local.webvm_custom_data)
    }
    

Step-01-04: terraform.tfvars

# Generic Variables 
business_divsion = "hr"
resource_group_location = "eastus"
resource_group_name = "rg"

Step-01-05: dev.tfvars

# Environment Name
environment = "dev"

# Virtual Network Variables
vnet_name = "vnet"
vnet_address_space = ["10.1.0.0/16"]

web_subnet_name = "websubnet"
web_subnet_address = ["10.1.1.0/24"]

app_subnet_name = "appsubnet"
app_subnet_address = ["10.1.11.0/24"]

db_subnet_name = "dbsubnet"
db_subnet_address = ["10.1.21.0/24"]

bastion_subnet_name = "bastionsubnet"
bastion_subnet_address = ["10.1.100.0/24"]

# Web Linux VM Variables
web_linuxvm_size = "Standard_DS1_v2"
web_linuxvm_admin_user = "azureuser"

Step-01-06: qa.tfvars

# Environment Name
environment = "qa"

# Virtual Network Variables
vnet_name = "vnet"
vnet_address_space = ["10.2.0.0/16"]

web_subnet_name = "websubnet"
web_subnet_address = ["10.2.1.0/24"]

app_subnet_name = "appsubnet"
app_subnet_address = ["10.2.11.0/24"]

db_subnet_name = "dbsubnet"
db_subnet_address = ["10.2.21.0/24"]

bastion_subnet_name = "bastionsubnet"
bastion_subnet_address = ["10.2.100.0/24"]

# Web Linux VM Variables
web_linuxvm_size = "Standard_DS1_v2"
web_linuxvm_admin_user = "azureuser"

Step-01-07: stage.tfvars

# Environment Name
environment = "stage"

# Virtual Network Variables
vnet_name = "vnet"
vnet_address_space = ["10.3.0.0/16"]

web_subnet_name = "websubnet"
web_subnet_address = ["10.3.1.0/24"]

app_subnet_name = "appsubnet"
app_subnet_address = ["10.3.11.0/24"]

db_subnet_name = "dbsubnet"
db_subnet_address = ["10.3.21.0/24"]

bastion_subnet_name = "bastionsubnet"
bastion_subnet_address = ["10.3.100.0/24"]

# Web Linux VM Variables
web_linuxvm_size = "Standard_DS1_v2"
web_linuxvm_admin_user = "azureuser"

Step-01-08: prod.tfvars

# Environment Name
environment = "prod"

# Virtual Network Variables
vnet_name = "vnet"
vnet_address_space = ["10.4.0.0/16"]

web_subnet_name = "websubnet"
web_subnet_address = ["10.4.1.0/24"]

app_subnet_name = "appsubnet"
app_subnet_address = ["10.4.11.0/24"]

db_subnet_name = "dbsubnet"
db_subnet_address = ["10.4.21.0/24"]

bastion_subnet_name = "bastionsubnet"
bastion_subnet_address = ["10.4.100.0/24"]

# Web Linux VM Variables
web_linuxvm_size = "Standard_DS1_v2"
web_linuxvm_admin_user = "azureuser"

Step-01-09: No Changes to files

  • c2-generic-input-variables.tf
  • c3-locals.tf
  • c4-random-resources.tf
  • c5-resource-group.tf
  • c6-01 to c6-07 Virtual Network Files
  • c7-02-web-linuxvm-publicip.tf
  • c7-03-web-linuxvm-network-interface.tf
  • c7-04-web-linuxvm-network-security-group.tf
  • c7-06-web-linuxvm-outputs.tf

Step-02: Create Github Repository and Check-In Files

Step-02-01: Create new github Repository

  • URL: github.com
  • Click on Create a new repository
  • Repository Name: terraform-on-azure-with-azure-devops
  • Description: Terraform on Azure with Azure IaC DevOps
  • Repo Type: Public / Private
  • Initialize this repository with:
  • CHECK - Add a README file
  • CHECK - Add .gitignore
  • Select .gitignore Template: Terraform
  • CHECK - Choose a license (Optional)
  • Select License: Apache 2.0 License
  • Click on Create repository

Step-02-02: Clone Github Repository to Local Desktop

# Clone Github Repo
git clone https://github.com/<YOUR_GITHUB_ID>/<YOUR_REPO>.git
git clone https://github.com/stacksimplify/terraform-on-azure-with-azure-devops.git

Step-02-03: Copy files from Git-Repo-Files folder to local repo & Check-In Code

  • Source Location: Git-Repo-Files
  • Destination Location: Copy all folders and files from Git-Repo-Files newly cloned github repository folder in your local desktop terraform-on-azure-with-azure-devops
  • Check-In code to Remote Repository
    # GIT Status
    git status
    
    # Git Local Commit
    git add .
    git commit -am "First Commit"
    
    # Push to Remote Repository
    git push
    
    # Verify the same on Remote Repository
    https://github.com/stacksimplify/terraform-on-azure-with-azure-devops.git
    

Step-03: Terraform Dependency Lock File Concept

  • Understand Terraform Dependency Lock File Concept
  • For detailed understanding we need to reference this topic from our Demo-67: Azure HashiCorp Terraform Associate Certification course. Primarily refer Step-04 and Step-05 of this section
    # Go to Local Git Repo 
    cd demo-repos
    cd terraform-on-azure-with-azure-devops/terraform-manifests
    
    # Delete `.terraform.lock.hcl`
    Delete file if exists "`.terraform.lock.hcl`"
    rm -rf .terraform.lock.hcl  # Explicitly for students to get latest version of Providers on that respective day when you are learning this module
    
    # Terraform Providers lock for multiple platforms
    terraform providers lock -platform=windows_amd64 -platform=darwin_amd64 -platform=linux_amd64
    
    # Terraform Initialize
    terraform init
    Observation: 
    1. Provider plugins downloaded to ".terraform folder"
    2. `.terraform.lock.hcl` created with command `terraform providers lock` used by `terraform init` to download those respective providers
    3. We need to check-in this file `.terraform.lock.hcl` with our TF Configs to Git Repos for IaC DevOps Pipelines to ensure our provider versions doesnt get upgraded to latest versions and break our application. 
    
    # Delete ".terraform" folder
    rm -rf .terraform 
    
    # We will ensure we are checking in `.terraform.lock.hcl`
    `.terraform.lock.hcl`
    
    # GIT Status
    git status
    
    # Git Local Commit
    git add .
    git commit -am "First Commit"
    
    # Push to Remote Repository
    git push
    
    # Verify the same on Remote Repository
    https://github.com/stacksimplify/terraform-on-azure-with-azure-devops.git
    

Step-04: Create Azure DevOps Organization

Step-04-01: Create Azure DevOps Organization

  • Understand about Azure DevOps Agents and Free-Tier Limits
  • Navigate to https://dev.azure.com
  • Click on Sign in to Azure DevOps
  • Provide your Azure Cloud admin user
  • Username: XXXXXXXXXXXXXX
  • Password: XXXXXXXXXXXXXX
  • Click on create New Organization
  • Name your Azure DevOps organization: stacksimplify1
  • We'll host your projects in: Choose the location (Azure selects based on current location where you are accessing from)
  • Enter the characters you see:
  • Click on Continue

Step-04-02: Request for Azure DevOps Parallelism

Step-05: Install Terraform Extension for Azure DevOps

Step-06: Create New Project in Azure DevOps Organization

  • Create a New Project in Azure DevOps Organization newly created
  • Click on New Project
  • Project Name: terraform-on-azure-with-azure-devops
  • Description: terraform-on-azure-with-azure-devops
  • Visibility: Private
  • Click on Create

Step-07: Understand Azure Pipelines

  • Understand about Azure Pipelines
  • Pipeline Hierarchial Flow: Stages -> Stage -> Jobs -> Job -> Steps -> Task1, Task2

Step-08: Create Azure CI (Continuous Integration) Pipeline (Build Pipeline)

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Pipelines
  • Click on New Pipeline
  • Where is your code?: GitHub
  • Follow browser redirect steps to integrate with Github Account
  • Select a repository: stacksimplify/terraform-on-azure-with-azure-devops
  • Configure your pipeline: Starter Pipeline
  • Rename the Pipeline file name to 01-terraform-azure-devops-ci-pipeline
  • Build the below code using two tasks listed below
  • Copy Files
  • Publish Artifacts
  • Click on Save and Run to Run the pipeline
    trigger:
    - main
    
    # Stages
    # Stage-1:
      # Task-1: Copy terraform-manifests files to Build Artifact Directory
      # Task-2: Publish build articats to Azure Pipelines
    # Pipeline Hierarchial Flow: Stages -> Stage -> Jobs -> Job -> Steps -> Task1, Task2, Task3  
    
    stages:
    # Build Stage 
    - stage: Build
      displayName: Build Stage
      jobs:
      - job: Build
        displayName: Build Job
        pool:
          vmImage: 'ubuntu-latest'
        steps: 
    ## Publish Artifacts pipeline code in addition to Build and Push          
        - bash: echo Contents in System Default Working Directory; ls -R $(System.DefaultWorkingDirectory)        
        - bash: echo Before copying Contents in Build Artifact Directory; ls -R $(Build.ArtifactStagingDirectory)        
        # Task-2: Copy files (Copy files from a source folder to target folder)
        # Source Directory: $(System.DefaultWorkingDirectory)/terraform-manifests
        # Target Directory: $(Build.ArtifactStagingDirectory)
        - task: CopyFiles@2
          inputs:
            SourceFolder: '$(System.DefaultWorkingDirectory)/terraform-manifests'
            Contents: '**'
            TargetFolder: '$(Build.ArtifactStagingDirectory)'
            OverWrite: true
        # List files from Build Artifact Staging Directory - After Copy
        - bash: echo After copying to Build Artifact Directory; ls -R $(Build.ArtifactStagingDirectory)  
        # Task-3: Publish build artifacts (Publish build to Azure Pipelines)           
        - task: PublishBuildArtifacts@1
          inputs:
            PathtoPublish: '$(Build.ArtifactStagingDirectory)'
            ArtifactName: 'terraform-manifests'
            publishLocation: 'Container'  
    
  • Verify First Run logs
  • Rename the Pipeline name to Terraform Continuous Integration CI Pipeline

Step-09: Sync Local Git Repo

  • A new file named 01-terraform-azure-devops-ci-pipeline.yaml will be added git Remote Repo
  • Sync the same thing to your local Git Repo
    # Local Git Repo
    git pull
    

Step-10: Azure Release Pipelines Introduction

  1. Understand Azure Release Pipelines
  2. What are we going to implement as part of Release Pipelines ?
  3. Review the Infra we are going to provision.
  4. Understand where Terraform State files will be stored for 4 environments.
  5. Demonstrate Continuous Delivery by making a change to our TF Configs atleast for one environment (Prod)

Step-11: Create Azure Resource Manager Service Connection for Azure DevOps

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) > Project Settings -> Pipelines -> Service Connections
  • Click on New Service Connection
  • Choose a service or connection type: Azure Resource Manager
  • Authentication Method: Service principal (automatic)
  • Scope level: Subscription
  • Subscription: Select Subsciption if we have many
  • Username: Azure Cloud Admin User
  • Password: XXXXXXXXX
  • Resource Group: leave empty
  • Service connection name: terraformiacdevops1
  • Description (optional): terraformiacdevops1 Service Connection used for CICD Pipelines
  • Security: CHECK Grant access permission to all pipelines (leave to default checked)
  • Click on Save

Step-12: Create Storage Account for storing Terraform State Files

  • Create Storage Account, Storage Container if not created.
  • We have already created that as part of Section-24-Step-02 which will re-use here.
    # Terraform State Storage to Azure Storage Container
        resource_group_name   = "terraform-storage-rg"
        storage_account_name  = "terraformstate201"
        container_name        = "tfstatefiles"
        key                   = "dev-terraform.tfstate"
    

Step-13: Release Pipelines - Create Dev Stage

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Releases
  • Click on New Release Pipeline
  • Pipeline Name: Terraform-CD

Dev Stage

  • Stage Name: Dev Stage
  • Stage Owner: stacksimplify@gmail.com (your-azure-admin-id)
  • Click on 1 Job, 0 Task

Agent Job

  • Display Name: Terraform Ubuntu Agent
  • Agent Pool: Azure Pipelines
  • Agent Specification: Ubuntu latest image
  • Rest all leave to defaults

Task-1: Terraform Tool Installer

  • Display Name: Install Terraform latest version
  • Version: 1.0.5 (as on today)
  • Important Note: Get latest terraform version number from Terraform Downloads page

Task-2: Terraform: init

  • Display Name: Terraform: init
  • Provider: azurerm
  • Command: init
  • Configuration directory: Select by browsing it (Example: $(System.DefaultWorkingDirectory)/_Terraform Continuous Integration CI Pipeline/terraform-manifests)
  • Additional command arguments: Nothing leave empty
  • AzureRM backend configuration
  • Azure subscription: terraformiacdevops1 (Select the service connection created in step-10)
  • Resource group: terraform-storage-rg
  • Storage account: terraformstate201
  • Container: tfstatefiles
  • Key: dev-terraform.tfstate
  • Rest all leave to defaults

Task-3: Terraform: validate

  • Display Name: Terraform: validate
  • Provider: azurerm
  • Command: validate
  • Configuration directory: Select by browsing it (Example: $(System.DefaultWorkingDirectory)/_Terraform Continuous Integration CI Pipeline/terraform-manifests)
  • Additional command arguments: Nothing leave empty
  • Rest all leave to defaults

Task-4: Terraform: plan

  • Display Name: Terraform: plan
  • Provider: azurerm
  • Command: plan
  • Configuration directory: Select by browsing it (Example: $(System.DefaultWorkingDirectory)/_Terraform Continuous Integration CI Pipeline/terraform-manifests)
  • Additional command arguments: -var-file=dev.tfvars
  • Azure subscription: terraformiacdevops1 (Select the service connection created in step-10)
  • Rest all leave to defaults

Task-5: Terraform: apply -auto-approve

  • Display Name: Terraform: apply -auto-approve
  • Provider: azurerm
  • Command: validate and apply
  • Configuration directory: Select by browsing it (Example: $(System.DefaultWorkingDirectory)/_Terraform Continuous Integration CI Pipeline/terraform-manifests)
  • Additional command arguments: -var-file=dev.tfvars -auto-approve
  • Azure subscription: terraformiacdevops1 (Select the service connection created in step-10)
  • Rest all leave to defaults

  • Click on *Save to save the release-pipeline.

Step-14: Release Pipeline - Artifacts Settings

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Releases -> Terraform-CD

Step-14-01: Add Artifacts

  • Click on Add Artifacts
  • Source Type: Build
  • Project: terraform-on-azure-with-azure-devops
  • Source (build pipeline): Terraform Continuous Integration CI Pipeline
  • Default version: Latest (leave to default)
  • Source alias: _Terraform Continuous Integration CI Pipeline (leave to default)
  • Click on Add

Step-14-02: Enable Continuous deployment trigger

  • Continuous deployment trigger: Enabled
  • Rest all leave to defaults

Step-15: Trigger Build (CI) and Release (CD) Pipelines

  • Make a minor change in git repo and push the changes from local git repo
    ## In any file add some changes
    Example: Add some comment in any of the *.tf files (Just for testing)
    
    # Git Status
    git status
    
    # Git Commit
    git commit -am "CICD-Test-1"
    
    # Git Push
    git push
    

Step-16: Review Build (CI) Pipeline and Release Pipeline(CD) Logs

Verify Build Pipeline Logs

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Pipelines -> Terraform Continuous Integration CI Pipeline

Verify Release Pipeline Logs

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Releases -> Terraform CD

Step-17: Verify Dev Resources created in Azure Portal

Verify dev-terraform.tfsate file

  • Go to Storaage Accounts -> terraform-rg-storage -> terraformstate201 -> tfstatefiles
  • Verify the file dev-terraform.tfstate

Verify Dev Resources in Azure Portal

  1. Azure Virtual Network
  2. Azure Subnets
  3. Azure Public IP
  4. Azure Linux Virtual Machine

Step-18: Create Stages listed below by cloning Dev Stage in Releases

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Releases -> Terraform CD -> Edit
  • Updates include the following for QA, Stage and Prod

Task-1: Terraform: init

  • Update Key to respective environment
  • QA Key: qa-terraform.tfsate
  • Stage Key: stage-terraform.tfstate
  • Prod Key: prod-terraform.tfstate

Task-2: Terraform: plan

  • Update Additional command arguments to respective environment
  • QA Additional command arguments: -var-file=qa.tfvars
  • Stage Additional command arguments: -var-file=stage.tfvars
  • Prod Additional command arguments: -var-file=prod.tfvars

Task-3: Terraform: apply -auto-approve

  • Update Additional command arguments to respective environment
  • QA Additional command arguments: -var-file=qa.tfvars -auto-approve
  • Stage Additional command arguments: -var-file=stage.tfvars -auto-approve
  • Prod Additional command arguments: -var-file=prod.tfvars -auto-approve

Step-19: Add Pre-Deployment Approval and Post Deployment Approvals

  • Pre-Deployment Approvals: QA, Stage and Prod
  • Post-Deployment Approvals: Stage

Step-20: Trigger Build (CI) and Release (CD) Pipelines

  • Make a minor change in git repo and push the changes from local git repo
    ## In any file add some changes
    Example: Add some comment in any of the *.tf files (Just for testing)
    
    # Git Status
    git status
    
    # Git Commit
    git commit -am "CICD-Test-2"
    
    # Git Push
    git push
    

Step-21: Verify Resources created in Azure Portal for QA, Stage and Prod Environments

Verify TFState File for Dev, QA and Prod

  • Go to Storaage Accounts -> terraform-rg-storage -> terraformstate201 -> tfstatefiles
  • Verify the files listed below
  • qa-terraform.tfstate
  • stage-terraform.tfstate
  • prod-terraform.tfstate

Verify Resources in Azure Portal for QA, Stage and Prod Environments

  1. Azure Virtual Network
  2. Azure Subnets
  3. Azure Public IP
  4. Azure Linux Virtual Machine

Step-22: Change web_linuxvm_admin_user to Prod Environment

# File: prod.tfvars
#web_linuxvm_admin_user = "azureuser"
web_linuxvm_admin_user = "produser" # Enable during step-21

# Git Status
git status

# Git Commit
git commit -am "Changed Prod VM adminuser name to produser"

# Git Push
git push

Step-23: Review Build (CI) Pipeline and Release Pipeline(CD) Logs

Verify Build Pipeline Logs

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Pipelines -> Terraform Continuous Integration CI Pipeline

Verify Release Pipeline Logs

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Releases -> Terraform CD
  • Dev Stage: Review Logs
  • QA Stage: Approve (Pre-Deployment Approval) and Review Logs
  • Staging Stage: Approve (Pre-Deployment Approval) and Review logs and also do Post-Deployment Approval
  • Prod Stage: Approve (Pre-Deployment Approval)

Verify Virtual Machines in Azure Portal

  • Go to -> Virtual Machines
  • Verify VM hr-prod-web-linuxvm and get the Public IP
    # Connect to prod VM using SSH
    ssh -i ssh-keys/terraform-azure.pem produser@<Prod-VM-Public-IP>
    

Step-24: Disable Build (CI) Pipeline

  • Go to Azure DevOps -> Organization (stacksimplify1) -> Project (terraform-on-azure-with-azure-devops) -> Pipelines -> Pipelines -> Terraform Continuous Integration CI Pipeline
  • Settings -> Disabled -> Click on Save
  • This will help us if by any chance you made any accidental commits to your git repo we don't get any unexpected surprise azure bills.

Step-25: Delete Resources or Clean-Up

Delete Resources

  • Go to Azure Portal -> Resource Groups -> Delete Resource Groups for All Environments
  • Dev
  • QA
  • Staging
  • Prod

Delete Terraform State Files

  • Go to Azure Portal -> Storage Containers -> terraformstate201 -> Containers -> tfstatefiles -> Delete all files
  • dev-terraform.tfstate
  • qa-terraform.tfstate
  • stage-terraform.tfstate
  • prod-terraform.tfstate
🎉 New Course
Ultimate DevOps Real-World Project Implementation on AWS
$15.99 $84.99 81% OFF
MARCH2026
Enroll Now on Udemy →
🎉 Offer