Skip to content
Back to Blog
1 min read

Bicep Registry: Sharing and Reusing Infrastructure Modules

I wrote “Bicep Registry: Sharing and Reusing Infrastructure Modules” to share practical, production-minded guidance on this topic.

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.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

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