Learning Infrastructure as Code (IaC): Environment Setup

Set up your development environment to kickstart your Infrastructure as Code journey!

Introduction

Hello and Welcome the first post in the series of Bicep and Infrastructure as Code. this is post we’re going to be covering the initial setup requirements for getting started with Bicep. This will cover, Installing and Configuring Visual Studio Code, with some useful extensions, for example:

Packages, Packages and Packages

During the setup for this post, We are using a vanilla Windows 11 Machine, with nothing installed.

Once you’ve got logged into your machine, the first thing you want do is open Windows Terminal and run the following winget installation.

Base Software Installation

1
2
3
4
5
$appList = ('Microsoft.VisualStudioCode','Microsoft.VisualStudioCode.CLI','Microsoft.AzureCLI')
forEach ($app in $appList) {
    Write-Output `r "Installing: $app"
    winget install --exact --scope user --id $app
}

Now that we have the base software packages installed, Using the Visual Studio Code CLI we can install the required extensions.

Visual Studio Code Extensions Install

1
2
3
4
5
$extensionList = ('ms-azuretools.vscode-bicep','microsoft-dciborow.align-bicep','GitHub.copilot','GitHub.copilot-chat','usernamehw.errorlens','eamodio.gitlens','oderwat.indent-rainbow')
forEach ($extension in $extensionList) {
    code --install-extension $extension
    Write-Output `r " " # Required for Verbose spacing
}

Please reload the Windows Terminal profile/pwsh window before running the az command

Azure CLI Bicep Module Install

1
az bicep install

Now that we have installed the required packages, We are start looking at Bicep! Starting off with Target Scopes!

Target Scopes

In Azure Bicep, the target scope refers to the type of Azure resource or management level at which the Bicep deployment operates. The scope defines what the deployment applies to and how the resources are deployed within Azure.

There are several options for setting the target scope in a Bicep file, and it impacts how resources are managed and where the Bicep template is deployed.

Types of Bicep Target Scope

Tenant

Purpose: Defines the deployment at the tenant level.
Use case: This is used when you need to deploy resources or configurations that are related to the entire Azure Active Directory (Azure AD) tenant, like role assignments or policy configurations that apply globally across all subscriptions in the tenant.
Example: Deploying a custom policy across all subscriptions, managing Azure AD resources.

targetScope = ’tenant’

Management Group

Purpose: Defines the deployment at the management group level.
Use case: This is used for large-scale deployments that span across multiple subscriptions within a management group. It is often used for applying governance controls, such as policies or role assignments across multiple subscriptions.
Example: Assigning a policy to a management group, or configuring settings that apply to multiple subscriptions in a management group.

targetScope = ‘managementGroup’

Subscription

Purpose: Defines the deployment at the subscription level.
Use case: This is used when you want to deploy resources across an entire subscription, like creating resources that are at the subscription boundary (e.g., policy assignments, role assignments).
Example: Assigning a policy to the subscription or managing role-based access control (RBAC) at the subscription level.

targetScope = ‘subscription’

Resource Group

Purpose: Defines the deployment at the resource group level.
Use case: This is the most common scope, and it’s used when you want to deploy resources like VMs, networks, databases, etc., within a specific resource group.
Example: Deploying a virtual machine, a storage account, or any other resource group-scoped resource.

targetScope = ‘resourceGroup’

None (optional)

Purpose: The default scope when none is explicitly defined.
Use case: This can be used in cases where the Bicep file doesn’t have a specific scope and works as a standalone resource. It typically applies to resources that don’t need a management group, resource group, or subscription context.
Example: Single resource definitions or when deploying a resource without a parent scope.

targetScope = ’none’

Deployment Types

Visual Studio Code

In this method, you use Visual Studio Code (VSCode) with the Azure Bicep extension to deploy your Bicep file directly from within the editor.

Steps for Deploying from VSCode:

Install the Azure Bicep Extension: First, you need to install the Azure Bicep extension for VSCode. You can find it in the VSCode Marketplace.

Install the Azure CLI (if you haven’t already): The Azure CLI must be installed on your system for the VSCode extension to use it for deployments.

Open the Bicep File: Open your .bicep file in VSCode.

Deploy the Bicep File:

Right-click on the Bicep file in VSCode and select Deploy to Azure. This will open the Azure CLI prompt for login if you haven’t logged in yet. You can also use the F1 Command Palette (press Ctrl+Shift+P or Cmd+Shift+P on macOS) and type Azure: Deploy to Resource Group. When prompted, select the Subscription, Resource Group, and the Bicep file you want to deploy. Enter Parameters (if required):

If your Bicep file requires parameters (either in the form of a JSON parameter file or interactive input), you will be prompted to provide these during the deployment process. Monitor Deployment:

You can track the deployment’s progress and output directly within the VSCode terminal or Azure portal. If the deployment fails, error details are typically shown in the Output pane in VSCode.

Pros:

  • Easy integration with Azure CLI, enabling direct deployment from within VSCode.
  • Convenient if you’re already working in VSCode and want to streamline the process.
  • Supports Bicep language features like IntelliSense, syntax validation, and error checking, which is especially useful during development.

Cons:

  • Requires manual interaction, which isn’t suitable for fully automated processes unless incorporated into a script or pipeline.
  • Less flexible compared to using the Azure CLI directly in some cases, especially if you’re not using VSCode.

Azure Command Line

In this method, you use the Azure CLI to deploy a Bicep file to Azure. The az deployment command is part of the Azure CLI and allows you to deploy resources defined in a Bicep or ARM template.

Steps for Deploying with Azure CLI:

Install Azure CLI: Ensure that you have Azure CLI installed. You can install it from the Azure CLI installation page.

Login to Azure: Before running any deployment commands, you need to authenticate with your Azure account.

1
az login

If you are working with a service principal (for automation purposes), use:

1
az login --service-principal -u <AppID> -p <Password> --tenant <TenantID>

Run the az deployment Command: Use the az deployment command to deploy your Bicep file. You can specify the target scope such as a resource group, subscription, or management group.

Tenant Deployment:

Microsoft Docs

1
2
3
4
az deployment tenant create \
  --location <Region> \
  --template-file <Path-to-Bicep-File>.bicep \
  --parameters <Path-to-Parameters-File>.bicepparam

Management Group Deployment:

Microsoft Docs

1
2
3
4
az deployment mg create \
  --management-group-id <Management-Group-ID> \
  --template-file <Path-to-Bicep-File>.bicep \
  --parameters <Path-to-Parameters-File>.bicepparam

Subscription Deployment:

Microsoft Docs

1
2
3
4
az deployment sub create \
  --location <Region> \
  --template-file <Path-to-Bicep-File>.bicep \
  --parameters <Path-to-Parameters-File>.bicepparam

Resource Group Deployment:

Microsoft Docs

1
2
3
4
az deployment group create \
  --resource-group <Resource-Group-Name> \
  --template-file <Path-to-Bicep-File>.bicep \
  --parameters <Path-to-Parameters-File>.bicepparam

Verify Deployment: After running the command, check the output in the terminal to ensure the deployment was successful. If there are errors, the Azure CLI will provide logs to help diagnose issues.

Pros:

Direct and flexible way to deploy using the Azure CLI. Can be automated via scripts for deployment. Supports various scopes, such as resource groups, subscriptions, and management groups.

Cons:

Requires manual execution unless integrated into a script or pipeline.

Deployment Script

The DeploymentScript wrapper is a PowerShell script used to automate deployment processes, usually in conjunction with Azure CLI or PowerShell cmdlets. The deployNow.ps1 script can be used as a wrapper to call Azure deployment commands for deploying resources via Bicep or ARM templates.

How it works:

A PowerShell script (e.g., deployNow.ps1) is created to wrap Azure CLI or Azure PowerShell commands that perform the actual deployment. The script can include parameters to make the deployment flexible and reusable. Typically used for more complex or controlled deployments where a set of prerequisites or custom logic (e.g., logging, checks) must be implemented before deploying resources.

Example PowerShell Script (deployNow.ps1):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
param (
    [Parameter(Mandatory = $true, Position=0, HelpMessage = 'Please enter an Azure Subscription Id')] [string]$subscriptionId,
    [Parameter(Mandatory = $true, Position=1, HelpMessage = 'Please enter a valid location')] [string]$location,
    [Parameter(Mandatory = $true, Position=2, HelpMessage = 'Please choose an environmentType')] [ValidateSet("acc", "test", "prod")] [string]$environmentType,
    [switch] $deploy
)

# Authenticate to Azure
az login

# Set Azure Subscription from Parameter
az account set --subscription $subscriptionId

# Get User Guid and User Principal Name
$azUserGuid = az ad signed-in-user show --query 'id' -o tsv
$azUserPn = az ad signed-in-user show --query 'userPrincipalName' -o tsv

# Generate Deployment Guid
$deployGuid = New-Guid

if ($deploy) {
    $deployStartTime = Get-Date -Format 'HH:mm:ss'

    # Deploy Bicep Template
    $azDeployGuidLink = "`e]8;;https://portal.azure.com/#view/HubsExtension/DeploymentDetailsBlade/~/overview/id/%2Fsubscriptions%2F$subscriptionId%2Fproviders%2FMicrosoft.Resources%2Fdeployments%2Fiac-bicep-$deployGuid`e\iac-bicep-$deployGuid`e]8;;`e\"
    Write-Output `r "> Deployment [$azDeployGuidLink] Started at $deployStartTime"

    az deployment sub create `
    --name $deployGuid `
    --location $location `
    --template-file .\main.bicep.json `
    --parameters `
    azUserGuid=$azUserGuid `
    azUserPn=$azUserPn `
    subscriptionId=$subscriptionId `
    environmentType=$environmentType `
    --confirm-with-what-if

    $deployEndTime = Get-Date -Format 'HH:mm:ss'
    $timeDifference = New-TimeSpan -Start $deployStartTime -End $deployEndTime ; $deploymentDuration = "{0:hh\:mm\:ss}" -f $timeDifference
    Write-Output "> Deployment [iac-bicep-$deployGuid] Started at $deployEndTime - Deployment Duration: $deploymentDuration"
}

Pros:

  • More control over the deployment process.
  • Can include custom logic, error handling, logging, and notifications.
  • Easily executable on local machines or CI/CD agents without the need for an IDE.
  • Suitable for more complex deployments requiring multiple steps or parameterization.

Cons:

  • Requires maintaining the PowerShell script.
  • Manual execution or integration into a pipeline is necessary.
  • Not as seamless for continuous or multi-stage deployments as using a full pipeline.

CI/CD Pipeline

Pipeline deployments involve automating the deployment process as part of a CI/CD pipeline. Using tools like Azure DevOps or GitHub Actions, you can set up workflows to deploy infrastructure (using Bicep, ARM templates, or other resources) whenever there’s a change in the repository or based on a schedule.

How it works:

A pipeline configuration (e.g., YAML in Azure DevOps or GitHub Actions) defines the steps for the deployment, including tasks for logging in to Azure, validating templates, and deploying resources. The pipeline can be triggered by changes in code repositories (e.g., commits to main branches, pull requests) or scheduled for regular deployment cycles. Azure DevOps and GitHub Actions both provide built-in tasks for deploying Bicep/ARM templates or running custom deployment scripts.

Example Azure DevOps Pipeline (YAML):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
trigger:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-24.04'

steps:
- task: AzureCLI@2
  displayName: 'Deploy Bicep template at Subscription level'
  inputs:
    azureSubscription: 'sp-gitops-dev'
    scriptType: 'pscore'
    scriptLocation: 'inlineScript'
    inlineScript: |
        az deployment sub create `
          --location <Region>
          --template-file ./main.bicep `
          --parameters ./parameters.bicepparam

Example GitHub Actions Pipeline (YAML):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
name: 'Azure Bicep - Sub Deployment'

on:
  push:
    branches:
      - main  # Trigger on push to the main branch

jobs:
  deploy:
    runs-on: ubuntu-24.04

    steps:
      # Step 1: Checkout the repository
      - name: Checkout code
        uses: actions/checkout@v4

      # Step 2: Azure login using Service Principal credentials
      - name: Login to Azure
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      # Step 3: Deploy Bicep file at Tenant level
      - name: Deploy Bicep template at Tenant level
        run: |
          az deployment sub create `
            --location <Region> `
            --template-file ./main.bicep `
            --parameters ./parameters.bicepparam

      # Step 4: Verify deployment (optional)
      - name: Verify deployment
        if: always()
        run: |
          echo "Deployment completed successfully."

Pros:

  • Fully automated deployment.
  • Can be triggered by code changes, merges, or scheduled tasks.
  • Supports multiple environments (dev, test, prod) with conditional logic.
  • Centralized logging and monitoring for all deployments.
  • Supports integration with other build and release tools for end-to-end CI/CD pipelines.

Cons:

  • Initial setup may require more time and understanding of CI/CD concepts.
  • Requires Azure DevOps or GitHub Actions infrastructure and integration with version control.
  • More complex for small, one-off deployments.

Deployment Time!

We are now going to look at deploying a resource group and storage account.

Resource

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
param location string
param storageAccountName string

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {}
}

to deploy:

1
2
az group create --name 'rg-iac-storage-weu' --location 'westeurope'
az deployment group create --resource-group 'rg-iac-storage-weu' --template-file './example-resource.bicep' --parameters storageAccountName='mystoracc2024' location='westeurope'

Modules

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
targetScope = 'subscription'

param location string
param storageAccountName string
param resourceGroupName string

module createResourceGroup 'br/public:avm/res/resources/resource-group:0.4.0' = {
    name: 'create-resourceGroupDeployment'
    params: {
        location: location
        name: resourceGroupName
    }
}

module storageAccountModule 'br/public:avm/res/storage/storage-account:0.14.3' = {
    name: 'create-storageAccountDeployment'
    scope: resourceGroup(resourceGroupName)
    params: {
      name: storageAccountName
      location: location
      skuName: 'Standard_LRS'
    }
    dependsOn: [
        createResourceGroup
    ]
}

to deploy:

1
az deployment sub create --location 'westeurope' --template-file './example-avm.bicep' --parameters location=westeurope storageAccountName=mystoracc2024 resourceGroupName=rg-iac-storage-weu

Modules with BicepParam

A .bicepparam file is used with Azure Bicep templates to simplify parameter management, promote reusability, and streamline deployment processes. Here are some key reasons to use a .bicepparam file:

  • Separation of Parameters from Logic
    It decouples the parameter values from the Bicep template itself, making the template easier to maintain and reusable across multiple environments. This approach follows the principle of “separation of concerns,” where the infrastructure definition (template) is kept separate from the input values (parameters).

  • Environment-Specific Configurations
    You can define different .bicepparam files for each environment, such as dev, test, or prod. This makes switching between environments seamless by simply referencing the appropriate parameter file during deployment.

  • Improved Readability and Organization
    Parameters in a .bicepparam file are structured and easily readable, using JSON-like syntax. This reduces clutter in the main Bicep template and makes it easier to focus on the infrastructure logic.

  • Consistent and Reusable Deployments
    When deploying the same infrastructure repeatedly, you can use the same .bicepparam file to ensure consistency. This is particularly useful in CI/CD pipelines, where deployments must be automated and predictable.

Bicep Parameter File

1
2
3
4
5
using './example-avm.bicep'

param location = 'westeurope'
param resourceGroupName = 'rg-iac-storage-weu'
param storageAccountName = 'mystoracc2024'

example-avm.bicep

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
targetScope = 'subscription'

param location string
param storageAccountName string
param resourceGroupName string

module createResourceGroup 'br/public:avm/res/resources/resource-group:0.4.0' = {
    name: 'create-resourceGroupDeployment'
    params: {
        location: location
        name: resourceGroupName
    }
}

module storageAccountModule 'br/public:avm/res/storage/storage-account:0.14.3' = {
    name: 'create-storageAccountDeployment'
    scope: resourceGroup(resourceGroupName)
    params: {
      name: storageAccountName
      location: location
      skuName: 'Standard_LRS'
    }
    dependsOn: [
        createResourceGroup
    ]
}

to deploy

1
az deployment sub create --location 'westeurope' --template-file './example-avm.bicep' --parameters ./paramters.bicepparam

Wrap Up

When deploying resources using Bicep, it’s crucial to consider both best practices and scalability. Leveraging Azure Verified Modules (AVM) ensures your Infrastructure as Code (IaC) aligns with Microsoft’s standards for compliance, security, and reliability, streamlining deployments with reusable, validated components. Complementing this with the Well-Architected Framework (WAF) helps design solutions that are operationally efficient, secure, cost-optimized, and resilient. Additionally, choosing the appropriate deployment scopeā€”tenant, management group, subscription, or resource groupā€”is vital. Tenant or management group scopes are ideal for cross-subscription governance, subscriptions work well for isolated environments or billing boundaries, and resource groups suit tightly coupled workloads. Combining AVMs and WAF with these scope decisions ensures robust, scalable, and maintainable deployments.

Share with your network!

Built with Hugo - Theme Stack designed by Jimmy