Deploy Azure Bicep using GitHub Actions
Walk through the process of deploying an Azure Bicep file using GitHub Actions.
Deploying your resources to Azure using Infrastructure as Code (IaC) is often a more efficient and reliable way to deploy resources to Azure than deploying them manually.
There are many ways for you to deploy your IaC templates, you can manually send commands instructing the templates to deploy. Alternatively you can use DevOps tooling such as Azure DevOps, GitHub Actions, or something similar.
In this blog post I want to walk you through the process of deploying an Azure Bicep template to Azure using GitHub Actions.
Azure Bicep template files
I am going to deploy an Azure Storage account to Azure during this process. If you are unfamiliar with Bicep templates, check out my video which walks you through building your first Bicep template.
Create a repository within GitHub
The first thing that we want to do is create a repository within GitHub so we can store our Bicep templates and also create our GitHub Action.
If you don’t already have a GitHub account, it is fast and free to sign up for an account.
In the upper right corner of any page of GitHub use the + drop down menu and select New repository.
You will have the option of using a template to create your repository, in this case a template isn’t needed.
In the Owner drop-down select the account you wish to create the repository on. Type a name for your repository and an optional description.
Choose a repository visibility.
Click Create repository.
Store Azure Bicep files within GitHub
Now that a repository within GitHub has been created it’s time to get the Bicep files into that repository.
There are a number of ways this can be done. Using the Git command line tool is usually my preferred way. If you are unfamiliar with Git you can follow along my 14 days of Git series to learn more.
However, as there are only two files to add to the repository at this time I am going to create them manually within the GitHub website.
Navigate to the new repository you have just created.
Click on Add File
Then select Create New File
This will open an editor.
Within the name file type Storage-Template/main.bicep - this will create a folder called Storage-Template and then the file main.bicep.
Within the file editor copy the following Bicep template file.
@description('Storage Account type')
@allowed([
'Premium_LRS'
'Premium_ZRS'
'Standard_GRS'
'Standard_GZRS'
'Standard_LRS'
'Standard_RAGRS'
'Standard_RAGZRS'
'Standard_ZRS'
])
param storageAccountType string = 'Standard_LRS'
@description('Location for all resources.')
param location string = resourceGroup().location
@description('The name of the Storage Account')
param storageAccountName string = 'store${uniqueString(resourceGroup().id)}'
@description('The name of the Owner of this resource for the Azure Tag declaration')
param ownerTag string = 'Sarah'
@description('The purpose of this resource for the Azure Tag declaration')
param purposeTag string = 'Demo'
resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = {
name: storageAccountName
location: location
tags: {
Owner: ownerTag
Purpose: purposeTag
}
sku: {
name: storageAccountType
}
kind: 'StorageV2'
properties: {}
}
output storageAccountName string = storageAccountName
output storageAccountId string = storage.id
At the bottom of the editor there is a box asking for information about the new file you are about to commit, type in why you are adding this new file and then select Commit New File.
Best practice would be to create a new branch then create a pull request to move the file into the working branch, but as this is an empty repository we are just going to commit straight into the working branch.
Now the Bicep template is stored within our repository, the next file that is needed inside the repository is the Bicep lint file.
The linter file checks for any syntax errors and best practice violations within our Bicep files. The linter file can help to enforce code standards that your organisation has specified.
You can customise the linter file to match your needs.
To add our linter file to the repository navigate to inside the Storage-Template folder.
Click on Add File
Then select Create New File
This will open an editor.
Within the name file type bicepconfig.json
Within the file editor copy the following linter file configuration.
{
"analyzers": {
"core": {
"verbose": false,
"enabled": true,
"rules": {
"no-hardcoded-env-urls": {
"level": "error",
"disallowedhosts": [
"management.core.windows.net",
"gallery.azure.com",
"management.core.windows.net",
"management.azure.com",
"database.windows.net",
"core.windows.net",
"login.microsoftonline.com",
"graph.windows.net",
"trafficmanager.net",
"vault.azure.net",
"datalake.azure.net",
"azuredatalakestore.net",
"azuredatalakeanalytics.net",
"vault.azure.net",
"api.loganalytics.io",
"api.loganalytics.iov1",
"asazure.windows.net",
"region.asazure.windows.net",
"api.loganalytics.iov1",
"api.loganalytics.io",
"asazure.windows.net",
"region.asazure.windows.net",
"batch.core.windows.net"
]
},
"no-unused-params": {
"level": "error"
},
"no-unused-vars": {
"level": "error"
},
"prefer-interpolation":{
"level": "error"
},
"secure-parameter-default":{
"level": "error"
},
"simplify-interpolation":{
"level": "error"
}
}
}
}
}
At the bottom of the editor there is a box asking for information about the new file you are about to commit, type in why you are adding this new file and then select Commit New File.
The repository now contains the Bicep template file and the linter file.
Create credentials within Azure to programmatically deploy resources
The next step is to create an Azure Service Principal that can be used to connect to the Azure Subscription and deploy or manage resources inside that subscription.
You can create the Service Principal in a couple of ways, using the Azure Portal, PowerShell or the CLI. Below is the CLI method.
Open the Azure Cloud Shell and set it to use bash. Enter the following command:
subscriptionId=$(az account show --query id --output tsv)
az ad sp create-for-rbac -n "GitHubActionsCrednetial" --role "Owner" --scopes /subscriptions/$subscriptionId
The output will be similar to this:
{
"appId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"displayName": "GitHubActionsCredential",
"password": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"tenant": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
Take a copy of this output as it’s needed in the next step.
Use GitHub secrets to store Azure credentials
In order for GitHub to use the Azure Service Principal credentials it needs to be stored inside a GitHub secret.
Within the repository where your workflow is click on Settings > Secrets > Actions and then click on new repository secret.
Give the new secret a name, I usually go with "Azure_Credentials" and then take the output from the CLI script and paste it into the value section.
Create GitHub Action to deploy Azure Bicep template
Now that we have our Bicep files stored within GitHub and we have the necessary credentials created and stored within GitHub it’s time to create the GitHub action to deploy our resources.
Within your GitHub repository head over to Actions and then select New Workflow.
There are templates that you can use but we are going to start from scratch, so select “set up a workflow yourself”.
The first thing that we want to do is give the workflow a name and also instruct why and when this workflow should trigger.
# This workflow
name: Bicep-Template_Build
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
branches:
- main
This workflow will trigger on three occasions:
- Manually
- When code is pushed into the main branch of the repository
- When a pull request is pushed into the main branch of the repository
The next section of the workflow is to define variables. This will allow us to use the template in other scenarios.
env:
resourceGroupName: TemplateDeploymentRG
resourceGroupLocation: uksouth
bicepfilePath: ./Storage-Template/main.bicep
Now it’s time to start defining the job and steps for this workflow. This workflow will have two jobs and multiple steps. The first job is to build or validate our template.
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
# Builds the Bicep template
- name: bicep-build-output
uses: Azure/bicep-build-action@v1.0.1
with:
bicepFilePath: ${{ env.bicepfilePath }}
outputFilePath: ./main.json
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.1
with:
path: ./main.json
The build part of the workflow does several things. The first is to take a copy of the code within the repository and make it available for the GitHub worker to use.
The next step is to build the Bicep file. This validates the file we have created against the linter file and then creates an Azure ARM template file.
Within the workflow the ARM template file that is built from the Bicep is uploaded as an artifact and is available to view or use either later in the workflow or after the workflow has finished. This is an entirely optional stage, it’s one I build into the process just for more complex deployments and potential troubleshooting.
Our second job is to deploy the template and create our Azure Storage account inside Azure.
# Deploys the template to Azure
deploy:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v3
- name: Azure Login
uses: Azure/login@v1.4.3
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Az CLI Create Resource Group
uses: Azure/CLI@v1
with:
inlineScript: |
az group create \
--name ${{ env.resourceGroupName }} \
--location ${{ env.resourceGroupLocation }}
- name: Deploy bicep to Azure
uses: Azure/cli@v1
with:
inlineScript: |
az deployment group create \
--template-file ${{ env.bicepfilePath }} \
--resource-group ${{ env.resourceGroupName }}
The first step in this job is logging into Azure, this is calling the credentials we created earlier and using them to authenticate.
The next step is to create an Azure resource group, this is using one of the variables that you created right at the start of the template. If your resource group already exists, then this step will just continue on to the next.
This third step is now deploying the Bicep template.
After a few minutes of running within the Azure Portal the storage account will be available and the GitHub Action will report as having finished.
Conclusion
Hopefully this article has guided you through the process of deploying your first Azure Bicep template using GitHub Actions. I look forward to seeing what else you automate!