Terraform with GitHub Actions CI/CD Pipeline

By using Terraform with GitHub Actions, IT professionals can automate and streamline the deployment of resources across Azure environments in a consistent and reliable way.

This guide will walk you through setting up Terraform in GitHub Actions, from configuring the necessary secrets and permissions to writing Terraform configuration files and automating deployments.

Whether you're new to CI/CD practices or looking to integrate Terraform into your GitHub workflows, this step-by-step tutorial will provide you with the tools and techniques to get started.

Prerequisites 

Before we get started, make sure you have the following in place:

  1. Azure Subscription: To host the resources provided by Terraform. If you don’t have one, you can sign up for a free trial.
  2. GitHub account: You need a free GitHub account to take advantage of their CI/CD pipeline tools. You can sign up for one here

How to run Terraform in an GitHub Actions pipeline

Create the Service Principal

A Service Principal (SPN) is required to allow Terraform on the GitHub Actions build agent to authenticate against the Azure subscription and create Azure resources. 

  • Within the Azure portal open Microsoft Entra ID
  • Click on Add and select App registration.
Azure app registrations
  • Give the application a name and accept all the default settings and click on Register
  • Make a note of the Application (client) ID and Directory (tenant) ID as we need them later.  
  • When the App registration is created click on Manage > Certificates & Secrets. 
  • Click on Client Secrets and click on New client secret.
  • Give the secret a description and expiry date and click on Add. 
  • Make a note of the value, as you’ll need it later on. 

Create the Terraform State storage account

In Terraform, the "state account" typically refers to the storage solution used to house the "state file," a critical file that keeps track of the existing infrastructure's configuration and status.

This state file contains information about resources that have been deployed, helping Terraform identify what changes are required when the configuration is modified.

Centralising the state file supports collaborative work among team members by preventing conflicting updates. 

In this guide, we’ll demonstrate how to configure an Azure storage account to store the Terraform state file.

Open https://shell.azure.com in a browser and copy the following PowerShell to create the resource group, storage account, and relevant blob container. Change the variable information to suit your requirements. 

# Define variables 
$resourceGroupName = "tfstate-gademo-rg" 
$location = "Sweden Central" 
$storageAccountName = "tfstategademo" 
$containerName = "tfstate" 

# Create a new resource group 
New-AzResourceGroup -Name $resourceGroupName -Location $location 

# Create a new storage account within the resource group 
New-AzStorageAccount -ResourceGroupName $resourceGroupName  -Name $storageAccountName  -Location $location  -SkuName Standard_LRS  -Kind StorageV2 

# Retrieve the storage account context
 $storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name $storageAccountName 
$context = $storageAccount.Context 

# Create a blob container within the storage account 
New-AzStorageContainer -Name $containerName -Context $context

Navigate to the storage account within https://portal.azure.com and under Access Control (IAM) grant the Storage Blob Data Contributor role to the Service Principal (SPN) that you just created. 

This allows the SPN to write the tfstate file to the storage account. 

Set Azure subscription permissions

The SPN also needs permission to deploy the Azure resource in the wider subscription. 

Now setting the appropriate SPN permission is a whole topic in itself, but for this example we’re going to grant the SPN Contributor permissions on the entire subscription. 

  • Within the Azure portal browse to Subscriptions then select the subscription you want to deploy the resources to. 
  • Select Access control (IAM)
  • Click on Add and then Add role assignment
Azure subscription access controls
  • Select Privileged administrator roles then Contributor
  • Select the SPN you created earlier and assign the permissions. 

Set up a GitHub repository

With your account set up, you next need to sign into GitHub and create a repository where you’ll be setting up your CI/CD pipeline. 

Within GitHub click on Repositories on the top menu. 

Now click on New on the top right hand side. 

Create new GitHub Repository

You’ll be asked to answer some questions to create a new repository. Fill in the detail as follows:

  • Owner & repository name: Select your GitHub account and give the repository a name that will make sense to you. 
  • Description: This is an optional field. 
  • Visibility: Select Public
  • Initialize the repository: Tick the Add a README file box and then select Terraform as the .gitignore template
  • License: You can leave this as None

Now click on the Create Repository button. 

Create New GitHub repository

Create GitHub secrets

We don’t want to store our Azure connection information in plain text so we’ll take advantage of GitHub’s ability to store secrets securely that can be called by our GitHub Actions pipeline. 

  • Click on Settings at the top of your repository.
  • Then click on Secrets and Variables on the left hand menu then Actions.
  • Click on New repository secret and create the the following secrets:
    • AZURE_CLIENT_ID
    • AZURE_CLIENT_SECRET
    • AZURE_TENANT_ID
    • AZURE_SUBSCRIPTION_ID

Within each secret make sure you store the right information.  We’ll be calling these secrets as part of our GitHub Action pipeline. 

Create Terraform configuration files

In a previous blog post Deploying an Azure Log Analytics Workspace with Terraform, I designed a Terraform template using Azure Verified Modules to deploy an Azure Log Analytics workspace.  

For this example, we’ll build on that template by adding a new section called backend.

The backend block in this updated Terraform template specifies the location and configuration for storing the Terraform state file.

Here, we reference the storage account and resource group that were set up in a previous step.

  • Within your GitHub repository click on Add file > Create new file.
  • Name the file main.tf.
  • Paste the Terraform code below into the file. 
  • Click on Commit changes
terraform {
  required_version = ">= 1.3.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.71, < 5.0.0"
    }
    random = {
      source  = "hashicorp/random"
      version = ">= 3.5.0, < 4.0.0"
    }
  }
  backend "azurerm" {
    resource_group_name  = “tfstate-gademo-rg”
    storage_account_name = "tfstategademo"
    container_name       = "tfstate"
    key                  = "tfdemo.ga.tfstate"
  }
}
provider "azurerm" {
  features {}
  subscription_id = "6a2908bc-22da-454c-bfb9-87edf787700b"
}
# This ensures we have unique CAF compliant names for our resources.
module "naming" {
  source  = "Azure/naming/azurerm"
  version = "0.3.0"
}
locals {
  azure_regions = [
    "ukwest",
    "westeurope",
    "francecentral",
    "swedencentral"
    # Add other regions as needed
  ]
}
variable "enable_telemetry" {
  description = "Enable or disable telemetry for the log analytics workspace"
  type        = bool
  default     = true # Set a default value if desired
}
# This picks a random region from the list of regions.
resource "random_integer" "region_index" {
  max = length(local.azure_regions) - 1
  min = 0
}
# Add a new random_pet resource to generate a unique, human-readable name
resource "random_pet" "log_analytics_workspace_name" {
  length    = 2
  separator = "-"
}
# This is required for resource modules
resource "azurerm_resource_group" "rg" {
  location = local.azure_regions[random_integer.region_index.result]
  name     = module.naming.resource_group.name_unique
}
# This is the module call
module "log_analytics_workspace" {
  source = "Azure/avm-res-operationalinsights-workspace/azurerm"
  # source             = "Azure/avm-res-operationalinsights-workspace/azurerm"
  enable_telemetry                          = var.enable_telemetry
  location                                  = azurerm_resource_group.rg.location
  resource_group_name                       = azurerm_resource_group.rg.name
  name                                      = "law-${random_pet.log_analytics_workspace_name.id}"
  log_analytics_workspace_retention_in_days = 60
  log_analytics_workspace_sku               = "PerGB2018"
  log_analytics_workspace_daily_quota_gb    = 200
  log_analytics_workspace_identity = {
    type = "SystemAssigned"
  }
}

Configure the GitHub Actions pipeline

We now need to write the GitHub Actions pipeline file. 

  • Within your GitHub repository click on Add file > Create new file.
  • Name the file .github/workflows/deploy.yml.
  • Paste the code below into the file. 
  • Click on Commit changes.
name: Terraform deploy Log Analytics
on:
  workflow_dispatch:
jobs:
  terraform:
    runs-on: ubuntu-latest
    env:
      ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      ARM_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.5.5
      - name: Terraform Init
        run: terraform init
      - name: Terraform Plan
        run: terraform plan
        env:
          TF_VAR_client_id: ${{ secrets.AZURE_CLIENT_ID }}
          TF_VAR_client_secret: ${{ secrets.AZURE_CLIENT_SECRET }}
          TF_VAR_subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
          TF_VAR_tenant_id: ${{ secrets.AZURE_TENANT_ID }}
      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        run: terraform apply -auto-approve
        env:
          TF_VAR_client_id: ${{ secrets.AZURE_CLIENT_ID }}
          TF_VAR_client_secret: ${{ secrets.AZURE_CLIENT_SECRET }}
          TF_VAR_subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
          TF_VAR_tenant_id: ${{ secrets.AZURE_TENANT_ID }}

This GitHub Actions workflow is named "Terraform deploy Log Analytics"

It allows users to manually trigger a Terraform deployment to set up a Log Analytics environment in Azure. 

It authenticates using Azure secrets stored in GitHub, initialises Terraform, runs a plan to preview changes, and, if the workflow is run from the main branch, applies the changes automatically.

Run the pipeline

We’ve configured this pipeline to only run when it’s manually triggered. To run it follow these steps:

  • Click on Actions at the top of your repository.
  • Select the pipeline name.
  • Click on Run workflow.
  • Refresh your browser and you will see the pipeline running and can monitor it. 

Conclusion

The integration of Terraform's robust infrastructure-as-code capabilities with GitHub's CI/CD pipelines simplifies complex deployments, ensuring efficiency, reliability, and consistency across environments.

Now that you’ve set up and run your pipeline, the possibilities are endless—explore further by adding modules, integrating testing steps, or expanding deployments to additional regions. Happy automating!