Why CI/CD Matters for Infrastructure
Adopting a CI/CD automated approach helps your infrastructure align with the pillars of the Well-Architected Framework (WAF) and the Cloud Adoption Framework (CAF). These frameworks set the standard for creating secure, reliable and maintainable environments.
- Using a main.bicep file with parameters helps ensure these principles are adhered to while allowing certain values to be adjusted via the parameter file. For example, you could modify the Virtual Machine SKU, adjust backup policies, or enable/disable specific services during the development stage.
- You can also use a bicepparam file to simplify testing across dev, test, and production environments, using CI/CD pipelines handling automation based on Environment/Input variables.
- CI/CD takes it further by automating checks and enforcing consistency. For example, you can pull requests using PSRule.Rules. Azure, ensuring the bicepparam file is checked and validated before deployment. This keeps best practices at the heart of your infrastructure deployments.
Structuring an AVM-Based Bicep deployment
Below is an example folder structure to help keep your CI/CD clean and organised.
Folder structure
A well-organised AVM-based Bicep project should follow this structure:
/infra
├── bicepparam
│ ├── dev.bicepparam
│ ├── acc.bicepparam
│ ├── prod.bicepparam
├── main.bicep
├── azure-pipelines.yml
├── .github/workflows/deploy.yml
Using an Azure Verified Module in main.bicep
Here’s an example of how to use an Azure Verified Module directly in your main.bicep file to deploy a resource group with standardised parameters.
targetScope = 'subscription'
@description('Customer Name (max 15 characters to prevent long resource names)')
@minLength(3)
@maxLength(15)
param customerName string
@description('Deployment Location')
param location string
@description('Location Short Code (2-4 characters, e.g., "weu" for West Europe)')
@minLength(2)
@maxLength(4)
param locationShortCode string
@description('Environment Type')
@allowed([
'dev'
'acc'
'prod'
])
param environmentType string
// Resource Group Naming Convention
var resourceGroupName = 'rg-${customerName}-${environmentType}-${locationShortCode}'
// Storage Account Name
@description('Storage Account Name (must be unique, 3-24 characters, lowercase)')
@minLength(3)
@maxLength(24)
param storageAccountName string
// Azure Verified Modules - Start Here
module createResourceGroup 'br/public:avm/res/resources/resource-group:0.4.1' = {
name: 'create-resource-group'
params: {
name: resourceGroupName
location: location
}
}
module storageAccount 'br/public:avm/res/storage/storage-account:0.17.0' = {
name: 'create-storage-account'
scope: resourceGroup(resourceGroupName)
params: {
name: storageAccountName
location: location
kind: 'StorageV2'
skuName: 'Standard_LRS'
}
}
Environment-Specific Parameters
(bicepparam/dev.bicepparam)
using './main.bicep'
param customerName = 'contoso'
param location = 'westeurope'
param locationShortCode = 'weu'
param environmentType = 'dev'
param storageAccountName = 'stcontosodevweu' // must be lowercase and static
Deploying AVM Modules Using Azure DevOps Pipelines
If you’re using Azure DevOps, you can automate the deployment of Azure Verified Modules with a pipeline defined in azure-pipelines.yml.
Here’s an example:
Pipeline YAML (azure-pipelines.yml)
name: Infrastructure Deployment
trigger:
- main
pool:
vmImage: ubuntu-latest
parameters:
- name: subscriptionId
displayName: Subscription Id
type: string
default: ''
- name: environmentType
displayName: Deployment Environment
type: string
default: dev
values:
- dev
- acc
- prd
steps:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
azureResourceManagerConnection: '<azureResourceConnectionId>'
deploymentScope: Subscription
subscriptionId: ${{ parameters.subscriptionId }}
location: westeurope
templateLocation: 'Linked artifact'
csmFile: './infra/main.bicep'
csmParametersFile: 'infra/bicepparam/${{ parameters.environmentType }}.bicepparam'
deploymentMode: Incremental
GitHub Actions
If your organisation is more comfortable using GitHub Actions, We have you covered. Here is an example workflow.
Deploying AVM Modules Using GitHub Actions Workflow YAML (.github/workflows/deploy.yml)
name: Infrastructure Deployment
on:
workflow_dispatch:
inputs:
subscriptionId:
description: Azure Subscription ID
required: true
environment:
description: Deployment Environment
required: true
type: choice
options:
- dev
- acc
- prd
permissions:
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
env:
AZURE_SUBSCRIPTION_ID: ${{ inputs.subscriptionId }}
AZURE_ENVIRONMENT: ${{ inputs.environment }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
- name: Deploy Bicep Template
uses: azure/arm-deploy@v1
with:
subscriptionId: ${{ env.AZURE_SUBSCRIPTION_ID }}
scope: 'subscription'
region: 'westeurope'
template: './infra/main.bicep'
parameters: './infra/bicepparam/${{ env.AZURE_ENVIRONMENT }}.bicepparam'
deploymentMode: 'Incremental'
Best Practices for AVM Deployment
While using the Azure Verified Modules is the way to go, let’s look at some best practices regarding authentication and validation:
Best practice 1: Authentication best practices
Best practice 2: Validation before deployment
- Run az bicep build to validate module syntax before deployment.
- Use az deployment sub what-if to preview changes before applying.