Back to Blog
3 min read

Bicep Registry: Sharing and Reusing Infrastructure Modules

The Bicep Registry enables you to share and reuse Bicep modules across your organization. Using Azure Container Registry (ACR), you can version, publish, and consume modules just like container images.

Setting Up a Bicep Registry

Create an Azure Container Registry:

// registry.bicep
param location string = resourceGroup().location
param registryName string

resource acr 'Microsoft.ContainerRegistry/registries@2021-12-01-preview' = {
  name: registryName
  location: location
  sku: {
    name: 'Basic'
  }
  properties: {
    adminUserEnabled: false
  }
}

output registryLoginServer string = acr.properties.loginServer

Publishing Modules

Publish a module to your registry:

# Login to Azure
az login

# Publish module to registry
az bicep publish \
  --file modules/storage-account.bicep \
  --target br:myregistry.azurecr.io/bicep/storage-account:v1.0.0

Create a publishing script for automation:

#!/bin/bash
# publish-modules.sh

REGISTRY="myregistry.azurecr.io"
MODULES_DIR="./modules"

for module in "$MODULES_DIR"/*.bicep; do
  filename=$(basename "$module" .bicep)
  version=$(cat "$MODULES_DIR/$filename.version" 2>/dev/null || echo "v1.0.0")

  echo "Publishing $filename:$version"

  az bicep publish \
    --file "$module" \
    --target "br:$REGISTRY/bicep/$filename:$version"
done

Consuming Registry Modules

Use modules from your registry:

// main.bicep
param environment string
param location string = 'australiaeast'

// Use module from registry
module storageAccount 'br:myregistry.azurecr.io/bicep/storage-account:v1.0.0' = {
  name: 'storage-deployment'
  params: {
    name: 'st${environment}${uniqueString(resourceGroup().id)}'
    location: location
    sku: 'Standard_LRS'
  }
}

module appService 'br:myregistry.azurecr.io/bicep/app-service:v2.1.0' = {
  name: 'app-deployment'
  params: {
    name: 'app-${environment}'
    location: location
    sku: environment == 'prod' ? 'P1v3' : 'B1'
  }
}

output storageEndpoint string = storageAccount.outputs.primaryEndpoint
output appUrl string = appService.outputs.url

Module Aliasing

Configure aliases in bicepconfig.json:

{
  "moduleAliases": {
    "br": {
      "myregistry": {
        "registry": "myregistry.azurecr.io"
      },
      "publicregistry": {
        "registry": "mcr.microsoft.com"
      }
    },
    "ts": {
      "myspecs": {
        "subscription": "00000000-0000-0000-0000-000000000000",
        "resourceGroup": "rg-template-specs"
      }
    }
  }
}

Use aliases in your Bicep files:

// Using alias
module storage 'br/myregistry:bicep/storage-account:v1.0.0' = {
  name: 'storage'
  params: {
    name: storageName
    location: location
  }
}

Creating Enterprise-Grade Modules

// modules/storage-account.bicep
@description('Name of the storage account')
@minLength(3)
@maxLength(24)
param name string

@description('Location for the storage account')
param location string = resourceGroup().location

@description('Storage account SKU')
@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_RAGRS'
  'Standard_ZRS'
  'Premium_LRS'
])
param sku string = 'Standard_LRS'

@description('Enable blob versioning')
param enableVersioning bool = true

@description('Enable soft delete for blobs')
param enableBlobSoftDelete bool = true

@description('Soft delete retention days')
@minValue(1)
@maxValue(365)
param softDeleteRetentionDays int = 7

@description('Resource tags')
param tags object = {}

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
  name: name
  location: location
  tags: tags
  sku: {
    name: sku
  }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    supportsHttpsTrafficOnly: true
    allowBlobPublicAccess: false
    networkAcls: {
      defaultAction: 'Deny'
      bypass: 'AzureServices'
    }
  }
}

resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2021-08-01' = {
  parent: storageAccount
  name: 'default'
  properties: {
    isVersioningEnabled: enableVersioning
    deleteRetentionPolicy: {
      enabled: enableBlobSoftDelete
      days: softDeleteRetentionDays
    }
  }
}

@description('The resource ID of the storage account')
output id string = storageAccount.id

@description('The name of the storage account')
output name string = storageAccount.name

@description('The primary blob endpoint')
output primaryEndpoint string = storageAccount.properties.primaryEndpoints.blob

@description('The primary connection string')
output connectionString string = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'

CI/CD for Module Publishing

# .github/workflows/publish-modules.yml
name: Publish Bicep Modules

on:
  push:
    branches: [main]
    paths:
      - 'modules/**'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Get changed modules
        id: changes
        run: |
          echo "modules=$(git diff --name-only HEAD~1 | grep '^modules/.*\.bicep$' | tr '\n' ' ')" >> $GITHUB_OUTPUT

      - name: Publish changed modules
        run: |
          for module in ${{ steps.changes.outputs.modules }}; do
            name=$(basename "$module" .bicep)
            version="v1.0.${{ github.run_number }}"

            az bicep publish \
              --file "$module" \
              --target "br:${{ secrets.ACR_NAME }}.azurecr.io/bicep/$name:$version" \
              --target "br:${{ secrets.ACR_NAME }}.azurecr.io/bicep/$name:latest"
          done

The Bicep Registry is essential for scaling infrastructure as code across enterprise organizations.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.