When setting up CI/CD for a system, we tend to focus on how our code and database updates will get to our environment. What we often fail to consider, however, is how changes to our environment will be deployed. Fortunately, infrastructure as code is available to us for both AWS (CloudFormation) and Azure (ARM templates). We can leverage infrastructure as code to make our environment changes as we make our code and database changes.
But why?
- Reduced downtime
- Fewer misconfigurations
- Better security
- Easier to create new environments
- Improved team wellbeing
Prerequisites
- Visual Studio Code with Bicep tools (or another editor of your choice)
- Azure CLI
- A basic understanding of GitHub Actions and YAML
Overview
This post will focus on setting up the Azure environment and automatically updating it via GitHub actions and Bicep files. To demonstrate this, we’ll set up a Storage Account, a Function App for the API layer, and a Static Web App for the website. The final structure will look something like

Bicep Files
Bicep is a Domain Specific Language for declaring how to configure an Azure environment. It can create an environment from scratch or update existing resources.
We will have two Bicep files in this example that we will store inside an infrastructure folder for organization. Our primary file is setup-subscription.bicep
. The first order of business is to prompt for or take from the command line several variables we need. Some of these, like resourceNamePrefix and location, have a default value and don’t have to be specified but can be overridden. The descriptions are used when prompting the user from the command line for missing variables.
The first resource we create is the resource group that will hold all of our other resources. Here the resource group name includes the environment. This lets us quickly establish Azure services for different testing environments and production.
Next, we will call a module which is a call to an external Bicep file. This is necessary because most of the resources we set up will target the resource group we created instead of the Azure subscription. We’ll look at this other Bicep file just a little later.
Finally, we generate some outputs. These will be used in our build pipeline as we deploy the code to these resources,
Now the bulk of the work is done in the second file, setup-resource-group.bicep
. Again, we will set up several parameters, but we’ll skip the descriptions here because we do not expect this file to be called by a user and only as a module.
Next, we’ll create a storage account. Our Function App will use this account.
Now we will create a static website. We will be copying our prebuilt website through our GitHub action. Therefore, we don’t want to utilize the static website’s ability to automatically pull changes from a git repository. Thus, this is a straightforward setup.
The last resource we establish in our Bicep file is our Function App. Here we will create an app plan for consumption-based pricing. We will also set up the Function App to run under Linux and as an isolated .NET process. We also set up several environment variables for the Function App.
Lastly, we output a few variables back to the setup-subscription.bicep
file.
We can now use these files to generate a parameters file. You can use the command, az bicep generate-params --file infrastructure/setup-subscription.bicep
or if you are using VS Code with the Bicep extension, you can right-click the Bicep file and select “Generate Parameters File.” I also added a “.qa” to my file name to differentiate the different environments. The result should be something like this.
Now we can test our Azure environment with the command
GitHub Actions
It is time to create our GitHub Action deployment as part of our normal CI/CD process. We won’t go over the entire process but just those portions that are important to our CI/CD pipeline.
To start, we will need to get some credentials from Azure so that GitHub can connect. Since we are creating the resource group, this permission must be scoped to the subscription. This will generate JSON data necessary for our Azure login. Ideally, this would also be restricted to only the permissions our deployment needs.
You should create the following secrets inside your GitHub repository.
Key | Value |
---|---|
AZURE_CREDENTIALS | The JSON from the command above |
AZURE_SUBSCRIPTION | You azure subscription id |
The first steps we want to highlight are the actual deployment of our Azure environment via our Bicep file. Here we first log into Azure and then execute our Bicep file template. The deployment name, wif-blog-aaes-${{inputs.environment}}-${{ github.run_number }}
, utilizes the GitHub run number to track our different deployments in Azure.
Another step to highlight is the deployment of the static web app. We utilize the output from azure-environment-setup to get the static web app key through an Azure CLI command. Then we can use that key to push our precompiled frontend to the static web app.
We can now automatically create an Azure environment and deploy our code with these files. Standing up a new environment is as simple as creating a new Bicep config file and the appropriate GitHub Action triggers. If the Bicep files are updated, the changes are updated in Azure when triggered in the GitHub Action. Now, you can update your environment as part of your normal CI/CD process.
The entire repo with complete files and application can be found here.
Digging Deeper
Here are some additional resources if you’d like to learn more.