Deploy from a Bicep Registry in Azure DevOps or GitHub Actions

Since October 2021, you are able to use a registry for your Bicep templates. This makes working on a larger scale a lot more approachable and can really help to create standards for your company. In this post, I want to focus on a specific part of registries: how do you use them in your pipelines. What permissions do your accounts need and can you deploy from one tenant to another one? Let’s see how you can deploy from a Bicep Registry in Azure DevOps or GitHub Actions.


For this post, it helps to be familiar with a Bicep registry and have one to work with. If you haven’t worked with one yet, there are some excellent tutorials in the Microsoft Docs.

Give your service connection the correct permissions

This is the most important part of working with a pipeline. When you deploy from your local environment, you are (often) using your own credentials. But for the pipeline, you are using a service principal that will have certain permissions. Of course, you can give it full permissions to everything, but that doesn’t fit the best practice of least amount of privileges.

So what is the least amount of privileges in this case? Your connection needs the following permissions:

  • Contributor permissions to the resource group/subscription that the resources will be deployed to
  • AcrPull permissions on the Bicep registry

You can set the AcrPull permissions directly or through an AzureAD group. You can do so through the portal or through one of the automation options. In the portal, you add the role through the Access Control (IAM) menu.

This role is available on all levels (directly on the registry, on the resource group, subscription or management group)

Another way to assign it is to use PowerShell, Azure CLI or the rest API. Below is an example of how you do it at subscription level in PowerShell
So for example:

$SPNId = "00000000-0000-0000-0000-000000000000"
$SubscriptionID = "00000000-0000-0000-0000-000000000000"
$Parameters = @{
    RoleDefinitionName = "ACRPull"
    scope = "/subscriptions/$($SubscriptionID)"
    ApplicationId = $SPNId
New-AzRoleAssignment @Parameters

Deploy through Azure DevOps

So let’s deploy! The good news is that deploying from a registry in a pipeline is exactly the same as when you would do it with a “regular” Bicep file. After all, the logic surrounding the registry is in the main.bicep file. The most important thing is that the Azure connection you have created has the permissions like written above. To find out how to set up that connection manually or with PowerShell, find my previous blog posts on setting it up with PowerShell or doing it manually.

The task itself can be running on PowerShell or the Az Cli. The PowerShell version could look like this:

- task: AzurePowerShell@5
    azureSubscription: '4bes'
    ScriptType: 'InlineScript'
    Inline: |
      $Parameters = @{
          ResourceGroupName = 'targetRG'
          TemplateFile =  'Example.bicep'
      New-AzResourceGroupDeployment @Parameters -verbose
    FailOnStandardError: true
    azurePowerShellVersion: 'LatestVersion'
    pwsh: true

To get a complete overview of how you can deploy Bicep through Azure DevOps, click here.

Deploy through GitHub Actions

And again for GitHub Actions: The actions themselves are the same as you might be used to. To find out how you set that up, find my blogpost here

     – name: Deploy bicep to Azure
        uses: Azure/cli@1.0.4
          # Specify the script here
          inlineScript: |
                az deployment group create  \
                –template-file  ${{ env.bicepfilePath }}  \
                –resource-group  ${{ env.resourceGroupName }}

Multi-tenant deployment

Now an interesting issue: How can you do that multi-tentant? IT service providers might recognize this use case. You want a central registry for all the Bicep files to deploy to your customers tenant.
So in short: How can you deploy a resource to tenant B from a registry in tenant A.

One of the ways to do this is through basic authentication, but that isn’t supported yet. So my colleague and I went looking for ways to accomplish it with a pipeline.

Azure LightHouse

My colleague Jannick Oeben came up with a very elegant solution to do this with Azure Lighthouse. That way you can add permissions to both tenants in on service connection. You can find his very thorough explanation on his blog.

Separate the tasks

My solution is less elegant, but it does work: separate the steps between creating the template from the registry and deploying it. To do this, you can do the following:

Let’s say the registry is in tenant A and the tenant that needs new resources is tenant B.

First, let’s create two service connections in Azure DevOps or two sets of secrets in GitHub Actions:

  • The first one needs to use a SPN in tenant A. It needs to have AcrPull permissions to the registry. Let’s call this service connection/Secret “AzureTenantA”
  • The second one will use a SPN in tenant B. This one needs to have permissions to deploy to the relevant resource groups/subscriptions in tenant B. We will call this one “AzureTenantB”

Now we will make two tasks:

  • The first one will use either Az CLI or Azure PowerShell and the command (Az) Bicep Build to create a template file. Connect it to AzureTenantA
  • The second one will use the generated json file to deploy to tenant B. You can use the default ARM deploy task in Azure Devops to do this, or Azure PowerShell/ Az CLI. When using GitHub Actions, set this up as a separate job so you can login to Azure separately.

In Azure DevOps, that would look like this

- task: AzureCLI@2
    azureSubscription: 'AzureTenantA'
    scriptType: 'pscore'
    scriptLocation: 'inlineScript'
    inlineScript: 'az bicep build --file BicepRegistryPipeline/sta.bicep --outfile sta.json'
- task: AzurePowerShell@5
    azureSubscription: 'AzureTenantB'
    ScriptType: 'InlineScript'
    Inline: |
      $Parameters = @{
          ResourceGroupName = 'targetRG'
          TemplateFile =  'sta.json'
      New-AzResourceGroupDeployment @Parameters -verbose
    FailOnStandardError: true
    azurePowerShellVersion: 'LatestVersion'
    pwsh: true

Notice how both tasks connect using a different Azure connection. This is the most simplistic way to do this, but while you are creating a json file, you can store it in the pipeline as an artifact and do some testing there as well.


So this is how you can deploy from a Bicep registry in Azure DevOps or GitHub Actions. This new feature is very useful when you are working on a larger scale with Bicep. If you have any questions, leave them in the comments.

This blog featured as part of Azure Week. Find more great Azure content here.

About the Author:

Barbara is the Azure Technical Lead for OGD IT Services in the Netherlands. Her focus is on Azure and automation. Think Serverless, Azure DevOps, PowerShell, GitHub and Infrastructure as Code. She loves teaching in an approachable way and has found multiple ways to reach people.

Barbara is co-founder of the Dutch DevOps & GitHub community (DDOG), as well as co-hosting the Dutch PowerShell User Group (DUPSUG).
She is a Microsoft certified trainer (MCT),  a Microsoft Most Valuable Professional (MVP) in the category Azure and a GitHub Star.

Barbara regularly write blog posts about the subjects mentioned above, as well as speaking  at user groups and events, talking about for example automation, Azure Functions, Azure DevOps and PowerShell.  If you are looking for a speaker for your event, you are welcome to contact me. Sessions can be found on Sessionize.


Forbes, B. (2021). Deploy from a Bicep Registry in Azure DevOps or GitHub Actions. Available at: [Accessed: 11th July 202].

Share this on...

Rate this Post: