Anti-Pattern 1: Trying to make Azure Bicep do everything End-to-End
Trent Steenholdt
November 29, 2024
7 minutes to read
In the world of Infrastructure as Code (IaC), Azure Bicep has emerged as a powerful tool for declaratively deploying Azure resources. However, a common anti-pattern is attempting to use Bicep for everything in your deployment pipeline—essentially trying to make it an end-to-end solution. In this first installment of our nine-part series on Azure Bicep anti-patterns, we’ll explore why this approach can lead to brittle and overcomplicated deployments, and how to avoid falling into this trap.
Understanding the Limitations of Azure Bicep
Azure Bicep excels at resource deployment but isn’t designed to handle every aspect of your infrastructure setup. Microsoft explicitly states this in the Bicep non-goals. Bicep has and never will be intended to replace scripting or automation tools like Azure CLI or PowerShell for procedural tasks. Every time I personally see complicated Bicep, this is always my go to reponse.
“Bicep is not a general-purpose scripting language and does not aim to become one.”
— Microsoft’s Bicep Documentation
Attempting to force Bicep into roles it’s not suited for can lead to complex, hard-to-maintain code and deployments that are fragile and difficult to troubleshoot.
The Pitfalls of Overusing Bicep
Misuse of Deployment Scripts
One common mistake in Bicep I see with using it end-to-end is over-reliance on deploymentScripts
within Bicep files. While deployment scripts can execute Azure CLI or PowerShell commands during deployment, they should be used sparingly and appropriately.
When to use deploymentScripts
- If you are deploying managed identity in your Bicep code, this is an ideal scenario to also use
deploymentScripts
, using that freshly created identity. - For tasks that are tightly coupled with the resources being deployed and can’t be accomplished afterward. E.g. You’re deploying an Application Gateway, you deploy a KeyVault to house the certificates for said Application Gateway. Here is where you can use
deploymentScripts
to pull or generate a certificate for your KeyVault deployment.
When to avoid deploymentScripts
- For tasks that can be easily executed before or after the Bicep deployment using traditional scripts. E.g. Adding members to an Entra AD security group which will be assigned an RBAC role.
- When the script requires elevated permissions or accesses resources not managed within the same deployment. E.g. You have a boundary on security that prevents crendential A from completing something that it can’t manage, like a virutal network (vNet) peering to a cross-subscription vNet.
Example of misusing deploymentScripts
Using a deploymentScript
to add a user to an Azure AD group:
resource addUserScript 'Microsoft.Resources/deploymentScripts@1.0.0' = {
name: 'addUserToGroup'
location: resourceGroup().location
kind: 'AzureCLI'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {}
}
}
properties: {
azCliVersion: '2.0.80'
scriptContent: '''
az ad group member add --group $groupId --member-id $userId
'''
arguments: '--groupId ${groupId} --userId ${userId}'
}
}
This script could be better handled using Azure CLI or PowerShell before or after the Bicep deployment.
Examples of tasks better suited for Scripts
-
Adding Users or Service Principals to Entra ID Groups:
# PowerShell script to add a user to an Azure AD group Connect-AzureAD Add-AzureADGroupMember -ObjectId $GroupId -RefObjectId $UserId
-
Data Seeding or Configuration:
# Bash script to initialize a database az sql db execute --server myserver --name mydb --command "CREATE TABLE..."
-
Complex Conditional Logic:
# PowerShell script with complex logic if ($resourceIDidntDeployExists) { # Do something with it, like a vNet peering } else { # Skip }
Be pragmatic with your Bicep code
Just because you can do something in Bicep doesn’t mean you should. Here’s how to decide the best tool for the job:
Use Bicep when:
- The task involves deploying or configuring Azure resources declaratively.
- You can define the desired state of resources without procedural steps.
- Bicep natively supports the resource or feature you’re deploying.
Use Scripts (Azure CLI or PowerShell) when:
- You’re performing actions that aren’t resource deployments, like data manipulation.
- You need to interact with external systems or services not managed by Bicep.
Best Practices for Integrating Bicep with Scripts
-
Sequence Your Deployments:
Use your CI/CD pipeline to orchestrate the sequence of Bicep deployments and scripts.
# Azure Pipelines example - stage: DeployInfrastructure jobs: - job: DeployBicep steps: - script: az deployment group create --template-file main.bicep - stage: PostDeployment dependsOn: DeployInfrastructure jobs: - job: RunScripts steps: - script: pwsh -File Deploy-PostDeploymentScript.ps1
-
Manage Permissions Carefully:
Ensure scripts run with appropriate permissions. Ideally with federated identity to avoid passwords.
# Run Azure CLI with a service principal az login --service-principal -u $APP_ID --tenant $TENANT_ID
-
Keep Bicep Files Focused:
Maintain clean, readable Bicep code by avoiding unnecessary embedded scripts.
// Bicep file focused on resource deployment resource storageAccount 'Microsoft.Storage/storageAccounts@2021-02-01' = { name: 'mystorageaccount' location: resourceGroup().location kind: 'StorageV2' sku: { name: 'Standard_LRS' } }
Conclusion
Azure Bicep is a powerful tool for deploying Azure resources declaratively, but it’s not a silver bullet for all deployment tasks. Overextending its use can lead to complicated and fragile deployments. By pragmatically integrating Bicep with scripting tools like Azure CLI or PowerShell, you can leverage the strengths of each to create robust, maintainable infrastructure deployments.
What’s Next?
In the next post of this series, we’ll delve into the importance of modularisation and how skipping it can hinder code reuse and scalability. Stay tuned as we continue to explore common anti-patterns and how to avoid them.