Simple Single-Stage Pipeline Automation
Overview
This module covers the implementation of automated Infrastructure as Code deployment pipelines using GitHub Actions. You'll learn to create YAML pipelines that automatically deploy Terraform configurations and establish proper security and governance practices for production deployments.
Learning Objectives
- Build GitHub Actions workflows for Terraform deployment
- Establish state management and locking for team collaboration
- Implement automated testing and validation in pipelines
- Troubleshoot common pipeline issues and deployment failures
Test the Azure↔GitHub Connection
We will setup a very simple workflow to test the connection between GitHub and Azure.
The Workflow will simply connect to Azure, prepare terraform and plan the deployment of our resources.
Create a new folder structure in your repository named .github/workflows/:
Create the following YAML file in .github/workflows/terraform-ci-cd.yml:
name: 'Terraform CI/CD'
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
id-token: write # Required for OIDC
contents: read # Required for checkout
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Azure Login using OIDC
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
# with:
# terraform_version: 1.5.0 # It is recommended to pin to a specific version for production
- name: Terraform Init
run: terraform init
working-directory: ./terraform
- name: Terraform Plan
run: terraform plan -var-file="environments/dev/terraform.tfvars"
working-directory: ./terraform
Once created, commit and push the changes to your repository.
In GitHub, go to the "Actions" tab of your repository and select the "Terraform CI/CD" workflow. You should see the workflow running.
If the workflow completes successfully, it means that the connection between GitHub and Azure is working correctly, and Terraform has been initialized and planned successfully.
Backend configuration
The current workflow does not use any backend configuration for Terraform state management. This means that the state file is stored locally on the runner, which is not suitable for team collaboration or production environments.
We will now update the workflow to use an Azure Storage Account as a backend for Terraform state management.
Configure the workflow
Edit the workflow file .github/workflows/terraform-ci-cd.yml to update the Terraform Init step to include backend configuration:
# ...
# Initialize a backend store for state management
- name: Terraform Init with backend
run: |
terraform init \
-backend-config="storage_account_name=tfstates${{ vars.RANDOM_SUFFIX }}" \
-backend-config="container_name=iac-workshop-tfstates" \
-backend-config="key=terraform-dev.tfstate" \
-backend-config="resource_group_name=rg-terraform-state"
working-directory: ./terraform
# ...
Configure backend for state management
Edit terraform/versions.tf to add the backend configuration in the terraform block:
Refer to HashiCorp documentation for more details: Configure the AzureRM backend for Terraform.
Deployment Pipeline
Add few more steps to the workflow to validate, plan and the baseline of the infrastructure.
# .github/workflows/terraform-ci-cd.yml
name: 'Terraform CI/CD'
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
id-token: write # Required for OIDC
contents: read # Required for checkout
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Azure Login using OIDC
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
# with:
# terraform_version: 1.5.0 # It is recommended to pin to a specific version for production
# Initialize a backend store for state management
- name: Terraform Init with backend
run: |
terraform init \
-backend-config="storage_account_name=tfstates${{ vars.RANDOM_SUFFIX }}" \
-backend-config="container_name=iac-workshop-tfstates" \
-backend-config="key=terraform-dev.tfstate" \
-backend-config="resource_group_name=rg-terraform-state"
working-directory: ./terraform
- name: Terraform Validate
run: terraform validate
working-directory: ./terraform
- name: Terraform Plan
run: terraform plan -var-file="environments/dev/terraform.tfvars"
working-directory: ./terraform
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push' # Only apply on push to main branch
run: terraform apply -auto-approve -var-file="environments/dev/terraform.tfvars"
working-directory: ./terraform
If you did copy your terraform files from previous workshop
Change the Azure Keyvault network ACL to authorize access from all networks (for the purpose of this workshop only): Allow instead of Deny.
network_acls {
default_action = "Allow"
bypass = "AzureServices"
ip_rules = [] # Add your IP addresses as needed
}
This configuration is not recommended for production environments but as we are using GitHub hosted runners with dynamic IP addresses, we cannot whitelist specific IPs.
Commit and push the changes to your repository. Observe the workflow running in the "Actions" tab of your repository.
My pipeline failed
If your pipeline fails, check the logs for errors. Common issues include:
- Incorrect service connection permissions or naming
- Missing backend storage account or container
- Syntax errors in the YAML file
Congratulations! You have successfully set up a first simple single-stage CI/CD pipeline to validate, plan and apply Terraform configurations.