Going All Muscley: Bicep and ALM FTW

Badge Claimed: Power of the Shell

For our EcoCraft solution, we are using quite a few different Azure resources…

  • A virtual machine, and various associated resources, to host our Minecraft server
  • Fabric capacity to host all of our dashboards and reports
  • An Azure function app to facilitate connections between Dataverse and our Minecraft server, to issue commands.
  • Application Insights to collect data from the various services we are using, such as Copilot Studio agents, Dataverse and more.

Managing all of these complex resources, their dependencies and to ensure we can deploy them cleanly across our development / test / production environments would usually be very challenging. But thanks to Azure Bicep, the whole task becomes ten times easier.

We start first by having our template files setup and prepared within our Azure DevOps Git repository:

This ensures our changes can be tracked effectively, and also provides support for hosting separate parameter files per environment. Next, we can bring in the following YAML deployment pipeline, to handle the deployment to our desired resource group:

trigger: none

parameters:
  # List of bicep deployments to run. Add as many items as you want.
  - name: bicepDeployments
    type: object
    default:
      - name: 'fabric'
        bicepFile: 'EcoCraft.Bicep/EcoCraft.Bicep.Fabric.bicep'
        scope: 'resourceGroup'          # supported: resourceGroup (below)
        subscriptionId: ''              
        resourceGroup: 'BicepDeployment'
        location: 'westeurope'
        parametersFile: ''              # optional: e.g. infra/network.dev.parameters.json
      - name: 'minecraftserver1'
        bicepFile: 'EcoCraft.Bicep/EcoCraft.Bicep.MinecraftServer1.bicep'
        scope: 'resourceGroup'
        subscriptionId: ''
        resourceGroup: 'BicepDeployment'
        location: 'westeurope'
        parametersFile: ''
      - name: 'minecraftserver2'
        bicepFile: 'EcoCraft.Bicep/EcoCraft.Bicep.MinecraftServer2.bicep'
        scope: 'resourceGroup'
        subscriptionId: ''
        resourceGroup: 'BicepDeployment'
        location: 'westeurope'
        parametersFile: ''

variables:
  # Azure DevOps Service Connection name
  azureServiceConnection: 'B&B Subscription'

stages:
- stage: Deploy
  displayName: Deploy Bicep
  jobs:
  - job: DeployBicep
    displayName: Deploy all Bicep files
    pool:
      vmImage: ubuntu-latest
    steps:
    - checkout: self

    - ${{ each d in parameters.bicepDeployments }}:
      - task: AzureCLI@2
        displayName: "Deploy: ${{ d.name }} (${{ d.bicepFile }})"
        inputs:
          azureSubscription: $(azureServiceConnection)
          scriptType: bash
          scriptLocation: inlineScript
          inlineScript: |
            set -euo pipefail

            echo "== Deployment: ${DEPLOYMENT_LABEL} =="
            echo "Bicep:        ${BICEP_FILE}"
            echo "RG:           ${RESOURCE_GROUP}"
            echo "Location:     ${LOCATION}"

            if [ -n "${SUBSCRIPTION_ID}" ]; then
              echo "Setting subscription: ${SUBSCRIPTION_ID}"
              az account set --subscription "${SUBSCRIPTION_ID}"
            fi

            # Ensure RG exists (optional—remove if RG is managed elsewhere)
            az group create --name "${RESOURCE_GROUP}" --location "${LOCATION}" 1>/dev/null

            DEPLOYMENT_NAME="${DEPLOYMENT_LABEL}-$(Build.BuildId)"

            # Build optional parameter args
            PARAM_FILE_ARGS=""
            if [ -n "${PARAMETERS_FILE}" ]; then
              echo "Using parameters file: ${PARAMETERS_FILE}"
              PARAM_FILE_ARGS="--parameters @${PARAMETERS_FILE}"
            fi

            EXTRA_ARGS=""
            if [ -n "${EXTRA_PARAMETERS}" ]; then
              echo "Using extra parameters: ${EXTRA_PARAMETERS}"
              EXTRA_ARGS="${EXTRA_PARAMETERS}"
            fi

            az deployment group create \
              --name "${DEPLOYMENT_NAME}" \
              --resource-group "${RESOURCE_GROUP}" \
              --template-file "${BICEP_FILE}" \
              ${PARAM_FILE_ARGS} \
              ${EXTRA_ARGS} \
              --only-show-errors
        env:
          DEPLOYMENT_LABEL: ${{ d.name }}
          BICEP_FILE: ${{ d.bicepFile }}
          RESOURCE_GROUP: ${{ d.resourceGroup }}
          LOCATION: ${{ d.location }}
          SUBSCRIPTION_ID: ${{ coalesce(d.subscriptionId, '') }}
          PARAMETERS_FILE: ${{ coalesce(d.parametersFile, '') }}
          EXTRA_PARAMETERS: ${{ coalesce(d.parameters, '') }}

From there, our deployments are completely automated – and green starts to become our favourite colour 🤩