Speed up deployments by not running your deploymentScripts every time

In one of our deployments we use deploymentScripts. We use it to apply SQL migration scripts and assign SQL principals and roles in a piece of test software that persists metrics for specific test runs.
What I noticed recently is that the deployments are taking 7+ minutes to finish, even when nothing has changed in the infrastructure.

The problem

After reading the docs for a bit, I discovered we set the forceUpdateTag to the current deployment time via a parameter (param deploymentTimestamp string = utcNow('yyyyMMdd-HHmmss')). This property states the following:

Gets or sets how the deployment script should be forced to execute even if the script resource has not changed. Can be current time stamp or a GUID.

Because of the way we set the property, the deployment scripts are invoked EVERY time we run a new deployment. While that might not sound like it should have much impact because it’s just a small PowerShell script, it’s important to understand what a deployment script is doing under the hood.

A deploymentScripts resource does not run inline. It provisions a real Azure Container Instance (ACI) as the execution environment. The full lifecycle per script execution is:

  1. ARM provisions an ACI container and storage account
  2. The ACI pulls the specified container image, in our case azPowerShellVersion: '14.0', a multi-hundred-MB image
  3. The PowerShell script executes inside the container
  4. On success (cleanupPreference: 'OnSuccess'), the ACI is torn down along with the storage account

This lifecycle can take 3-5 minutes per script under normal conditions, not because the SQL work takes a long time, but because of the ACI cold-start overhead. Because we have two deployment scripts with a dependency between them, they run sequentially and each deploys its own infrastructure, doubling the total time.

Read more →

Provision Grafana dashboards in your pipelines

For an evaluation tool that measures regressions and improvements in agent responses, I need to visualize the output. At first, I figured it would be nice to create a web application with lots of charts and grids. Then it occurred to me that we also have Azure Managed Grafana nowadays. This software is built for data visualization and supports a ton of data sources, one of them being SQL Server.
As I had never worked with this before, and because we need this visualization to analyze our measurements, I had a good excuse to start working with it.

Deploy the resource

The Managed Grafana offering is a regular Azure resource and is deployed as such. It is important to assign an identity to this resource, either a system-assigned identity or a user-assigned managed identity (UAMI). For demo purposes, I’m using a system-assigned identity, but for production, a UAMI is the way to go in my opinion.

resource grafana 'Microsoft.Dashboard/grafana@2024-10-01' = {
  name: name
  location: location
  tags: allTags
  identity: {
      type: 'SystemAssigned'
    }
  sku: {
    name: skuName
  }
  properties: {
    apiKey: enableApiKey ? 'Enabled' : 'Disabled'
    autoGeneratedDomainNameLabelScope: 'TenantReuse'
    deterministicOutboundIP: deterministicOutboundIp ? 'Enabled' : 'Disabled'
    publicNetworkAccess: publicNetworkAccess ? 'Enabled' : 'Disabled'
    zoneRedundancy: zoneRedundancy ? 'Enabled' : 'Disabled'
  }
}

Assigning permissions to Grafana’s data plane

I’m running the entire deployment via my deployment pipelines, including the dashboards in the Grafana resource. Because of this, I also need to make sure the deployment principal has enough permissions to create resources on the Grafana data plane. Choose the Grafana Editor (a79a5197-3a5c-4973-a920-486035ffd60f) or Grafana Admin (22926164-76b3-42b3-bc55-97df8dab3e41) role for this purpose. From a least-privilege standpoint, the Grafana Editor role is preferred.

Read more →

Invoke different versions of your service using APIM

I’m working on something where I need to deploy multiple versions of my software and validate whether there’s an improvement or regression. The solution heavily relies on deployed language models, so that’s something we want to evaluate first.
The solution I’m working on looks fairly similar to the setup in my Trial & Error GitHub repository, so I have a .NET frontend service and a Python backend service, both invoking language models deployed in Microsoft Foundry.

The baseline

I’ve deployed this using the following services:

  • Azure API Management
    This is the public entry point for my services
  • Azure Container Apps Environment
    Hosting two Container Apps for both .NET and Python
  • Microsoft Foundry + some models For my agents & MAF to work with

Of course, there are some other resources deployed too, but those aren’t important for now.

The way this works is:

  1. A client makes an HTTP request
  2. APIM processes the request and routes it to the default backend, the .NET Container App
  3. The .NET Container App takes the request and processes it
  4. The request gets forwarded to the Python Container App, which processes it
  5. The processed request is passed along to a language model in Microsoft Foundry
  6. The response gets processed and sent back to the client through the .NET Container App and APIM
  sequenceDiagram
    participant Client
    participant APIM as Azure API Management
    participant NET as .NET Container App
    participant Python as Python Container App
    participant Foundry as Microsoft Foundry (LLM)

    Client->>APIM: HTTP Request
    APIM->>NET: Route to default backend
    NET->>Python: Forward request
    Python->>Foundry: Send to language model
    Foundry-->>Python: Model response
    Python-->>NET: Processed response
    NET-->>APIM: Return response
    APIM-->>Client: HTTP Response

I think you’ll find this to be a common design. Obviously, the .NET and Python services also provide some additional value on top of forwarding requests, but that’s not relevant to this post.

Read more →

Build your own Fabric Capacity usage functionality

I use Microsoft Fabric on a project to store all customer data. Each customer gets their own workspace, so data is isolated. While Microsoft Fabric has its challenges and comes with a hefty price, it does bring quite a lot of useful data solutions under one umbrella. If you only need to store data in a (simple) database there are many more solutions that will fit your use case better. For my project we need to do ingestion, transformation, cleansing, store structured data, store unstructured data, etc. With all of these requirements, using Fabric makes more sense.

During development and testing we ran into capacity issues, which resulted in strange errors when querying or ingesting data.
To keep track of capacity usage you can install the Microsoft Fabric Capacity Metrics app from the store. This app provides useful insights, but it does require users to have a Power BI Pro license. If your users already have this, make sure to install the app. If not, you might want to get this for the users managing the solution, so be sure to discuss that.
I wanted to see if I could create something to deliver the same or similar insights. The Metrics app is based on the available data, so I figured it should be possible to query this myself too. Spoiler: it’s possible, but it does require a bit of setup.

Read more →

App Configuration emulator on macOS

The project I’m working on is in a maturing state. This means it needs to remain stable while still delivering new features. This is where feature toggles come into play. By adding these toggles and conditional execution paths to your code, you can keep the functionality unchanged until you turn a toggle on and then return to the previous behavior by turning it off again.

In the Azure ecosystem, we have the App Configuration resource with fairly basic feature toggle capabilities, so that’s what I’m using because it fits our current needs.

July 2026 update

There is no need to create your own App Configuration emulator image for macOS anymore.
The team has pushed an official image to the MCR.

https://mcr.microsoft.com/en-us/artifact/mar/azure-app-configuration/app-configuration-emulator/tags amd64 arch image has been released!

Setup in Aspire

For ease of local development, we use Aspire. To enable App Configuration, you’ll need the Aspire.Hosting.Azure.AppConfiguration package.
This lets you add a new resource to the AppHost: var appConfig = builder.AddAzureAppConfiguration("config");.

On your local machine, you probably don’t want to use an actual Azure resource, but an emulator. This can be configured using these lines of code:

appConfig.RunAsEmulator(emulator =>
{
    emulator.WithLifetime(ContainerLifetime.Persistent);
    emulator.WithDataVolume();
});

This works like a charm on Windows.
If you’re on Apple Silicon, like I am, it won’t.

Read more →

Add Azure OpenAI and Foundry models to OpenCode

GitHub Copilot is great, but somehow I get better results and a nicer UX/DX with OpenCode. It integrates with all the models I have available via GitHub Copilot and more.
There’s also a great ecosystem around this software and the documentation is quite good as well. The Awesome OpenCode repository lists quite a few useful tools, plugins and agents. Currently, I use the Smart Title Plugin and Open Agents Control, but Oh My OpenCode also has my interest even though it has a bit of overlap.

All of this is awesome and greatly enhances my joy in creating solutions during my dayjob and side projects. However, it does cost quite a bit of (premium) requests. Especially when using the rather expensive (and good) models.

What is great about OpenCode is you can connect it to your models deployed in Azure too!
This way, when your premium requests are all used up, you can use a GPT-Codex or Kimi model deployed in your own environment. Or even when you do still have premium requests available, you can offload your questions to a model of your choice.

When I was searching for the correct configuration I couldn’t find the correct documentation for this, so decided to post it over here.
If you have OpenCode installed, there should be a folder with an opencode.json file on your system. On a Mac it’s at ~/.config/opencode/opencode.json. In this file you can add a property called providers, if it doesn’t exist already, and configure your models.

Read more →

List everyone who has an Application Role for applications in Azure

Some time ago I had to validate who or what has access to the applications we created in our Azure environment.
There were hundreds of different applications with each their own specific Application Roles. Both users and service principals had roles assigned to the applications to perform the required operations.

It is possible to click through every application in Entra ID and validate the assigned roles. However, this takes quite a bit of time.
So I figured “Is it possible to iterate through all my applications in Entra ID and see who or what has an application role assigned?”
Needless to say, the answer is “Yes!”.

By using the Azure CLI and the Graph API I was able to accomplish what is required.

#########################################################
# Login if running local and you haven't done so already
#########################################################
Function Login {
    # Subscription used for logging in. Necessary to get a context to work in.
    $loggedInSubscription = "The subscription name"
    $tenantId = "73fefcf4-c062-45ef-9607-e19aba33f82d"
    az login --tenant $tenantId
    # # Log in to a subscription which resides in the tentant we want to add & configure MG's to.
    az account set --subscription $loggedInSubscription
}
Function Get-AppRoleAssignments {
    [CmdletBinding()]Param()
    Write-Verbose -Message "Starting to retrieve all Enterprise Applications matching the naming convention."
    $roleAssignmentsForAllApplications = $null
    $enterpriseAppRegistrationCollection =
        az ad sp list --filter "startswith(displayname, 'jv-sample-app1') or startswith(displayname, 'jv-sample-app2')"
        | ConvertFrom-Json

    Write-Verbose -Message "Found $($enterpriseAppRegistrationCollection.Length) enterprise applications."
    foreach ($enterpriseAppRegistration in $enterpriseAppRegistrationCollection) {
        $servicePrincipalAppRoles = $null
        $assignedAppRolesForServicePrincipal = $null

        # The service principal from 'az ad sp list' already has all the data we need
        $servicePrincipalAppRoles = $enterpriseAppRegistration.appRoles

        Write-Verbose -Message "Found $($servicePrincipalAppRoles.Length) appRoles for $($enterpriseAppRegistration.id)."
        $assignedAppRolesForServicePrincipal =
            az rest `
            --method get `
            --uri https://graph.microsoft.com/v1.0/servicePrincipals/$($enterpriseAppRegistration.id)/appRoleAssignedTo
            | ConvertFrom-Json
            | Select-Object -expand value

            Write-Verbose -Message "Found $($assignedAppRolesForServicePrincipal.Length) assigned identities for $($enterpriseAppRegistration.id)."
        $roleAssignmentsForAllApplications += foreach($assignedAppRole in $assignedAppRolesForServicePrincipal) {
            $role = $servicePrincipalAppRoles | Where-Object{ $_.id -eq $assignedAppRole.appRoleId}
            Write-Verbose -Message "Adding $($role.displayName) to collection for $($assignedAppRole.principalDisplayName) on application $($enterpriseAppRegistration.displayName)."
            New-Object PsObject -Property @{
                AppRoleId = $assignedAppRole.appRoleId
                AppRoleDisplayName = $role.displayName
                PrincipalObjectId = $assignedAppRole.principalId
                PrincipalDisplayName = $assignedAppRole.principalDisplayName
                ApplicationDisplayName = $enterpriseAppRegistration.displayName
                ApplicationApplicationId = $enterpriseAppRegistration.appId
                ApplicationObjectId = $enterpriseAppRegistration.id
            }
        }
    }
    return $roleAssignmentsForAllApplications
}
#Login
Get-AppRoleAssignments -Verbose | Sort-Object -Property ApplicationDisplayName | Format-Table -Property ApplicationDisplayName, ApplicationApplicationId, ApplicationObjectId, PrincipalObjectId, PrincipalDisplayName, AppRoleId, AppRoleDisplayName | Out-File "AppRoleAssignments.md"

The output looks pretty much like this

Read more →

Container Apps don’t work with NSG-rule and needs more ports open

The project I am working on requires me to deploy our compute solution, .NET and Python, to an Azure service and it should only expose specific endpoints via Azure API Management (APIM). To accomplish this, I have set up some networking services including NSG-rules. The goal is to set up the network boundaries as strict as possible.

One of the things I started with is set up APIM in a subnet, the Container Apps in another subnet and use an NSG to limit traffic to only use port 443. This is based on the knowledge of my containers and what is mentioned on the overview pages of the Container Apps networking page.

   ┌─────────────────┐
   │      APIM       │
   │   (Subnet A)    │
   └─────────────────┘
           │ HTTPS Traffic
           │ Port 443
           │ NSG: Allow
           │ Port 443 Only
   ┌─────────────────┐
   │  Container Apps │
   │   (Subnet B)    │
   └─────────────────┘

Long story short: This does not work!
When trying to invoke any endpoint on the Container App via the APIM test page I continuously received an error.

Error occured while calling backend service.", “connection timed out: 10.0.6.139:443

As the IP-address is resolved, I know the DNS resolution works. Just a timeout on port 443.
Obviously, I’ve exposed port 443 in my containers. That’s the first thing you should probably validate when running into this issue.

Read more →

Create an AI Assistant with your own data

The current large language models, like GPT-4, GPT-4 Turbo and GPT-4o are great when you need some output generated based on data you feed in the prompt. Even the small language models, like Phi-3, are doing a great job at this. However, these models often don’t know a lot about the data within your company. Because of this, they can’t do a good job at answering questions that required data from your organization.

There is of course the M365 Copilot available, which is able to index all of the organization its data and provide answers based on it. On a high level, what this is doing, is using Retrieval-Augmented Generation (RAG). There’s a decent post about this on the IBM Research site and there’s also a good post on the AWS site on it.

By using RAG in combination with your LLM, you are able to index your own data and let the model interpret it.
A great way to get started with this, is by using the Azure Open AI Assistants feature. The MS Learn page on this topic is quite good. If you’re interested in the topic, I’d suggest to check it out: https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/assistant.

Get your data

The first thing you need to do is make sure all data is available to the assistant. At this moment, there’s a large list of supported file types, like docx, pptx, pdf, png, txt, etc. The most important file types for us engineers are CSV, JSON, and XML, because these are able to hold (semi-)structured data so the LLM can infer relationships and create appropriate answers.

Read more →

Let Azure API Management its identity authenticate with your backend services

Aside from Azure Traffic Manager, Azure Functions, and Azure Service Bus, Azure API Management (APIM) is one of my favourite services to use in just about any solution.

A useful little nugget for APIM is it’s able to have its own Managed Identity. You can choose to use a System Managed Identity or a User Managed Identity. Both options have pros and cons.

When you have configured APIM with a managed identity, this identity can be used to authenticate with the backend services.
This can be useful in a wide variety of scenarios, but do be careful configuring this. By using this feature, every request to the backend will use the token of the Managed Identity and not of your users or services making authenticated requests to APIM.

As mentioned in the docs, to set this up, you can use the authentication-managed-identity policy for inbound requests.
When doing so, you need to specify which backend resource to use (App URI ID of an App Registration), and the name of the variable to put the token into.

<policies>
    <inbound>
        <base />
        <authentication-managed-identity resource="api://07601ff2-0b86-40f2-b5d9-7f8db33c9fb7/" output-token-variable-name="msi-access-token" ignore-error="false" />
        <set-header name="Authorization" exists-action="override">
            <value>@("Bearer " + (string)context.Variables["msi-access-token"])</value>
        </set-header>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Very useful in many scenarios, but do be careful of the downsides of using the APIM managed identity to be the authenticated party for backend services.

Read more →