Enterprise Bicep Guide for Azure Infrastructure as Code
Prerequisites
- Azure subscription with deployment permissions
- Azure CLI with Bicep support installed
Steps
Bicep is Microsoft’s domain-specific language for declarative Azure infrastructure deployment with improved readability over raw ARM templates. This guide shows enterprise teams how to structure, secure, deploy, and troubleshoot Bicep in production CI/CD workflows.
Overview
Bicep is a declarative Infrastructure as Code (IaC) language for Azure that compiles to ARM JSON templates. Its core purpose is to simplify Azure resource deployment by providing concise syntax, modularity, strong typing, and native integration with Azure Resource Manager.
Enterprises use Bicep because it reduces template complexity, standardizes cloud provisioning, and supports repeatable deployments across subscriptions, management groups, and tenants. It fits well in regulated environments where version control, policy enforcement, and change traceability are mandatory.
Architecture
Core components
- Bicep files:
.bicepdefinitions for resources, modules, parameters, and outputs. - Modules: Reusable deployment units stored locally or in private registries such as Azure Container Registry.
- Parameter files:
.bicepparamor JSON parameter files for environment-specific values. - Azure Resource Manager: The control plane that validates and executes deployments.
- Deployment scopes: Resource group, subscription, management group, and tenant.
Deployment models
- Central platform model: Shared modules published by a platform team and consumed by application teams.
- Environment promotion model: Same Bicep code promoted from dev to test to prod with different parameter files.
- Landing zone model: Management group and subscription-level deployments for policy, RBAC, and networking baselines.
Data flow
- Developer authors
.bicepand.bicepparamfiles. - CI pipeline runs
bicep build, linting, andaz deployment what-if. - Azure Resource Manager validates schema, dependencies, and policy compliance.
- Deployment executes with managed identity or service principal.
- Outputs are returned to the pipeline for downstream configuration.
Implementation Guide
- Install prerequisites.
az version
az bicep install
az bicep version
- Create a resource group and project structure.
az group create --name rg-platform-prod-we --location westeurope
mkdir -p infra/modules infra/params
- Create
main.bicep.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0"
}
- Author a Bicep deployment and parameter file.
az bicep build --file main.bicep
az deployment group what-if --resource-group rg-platform-prod-we --template-file main.bicep --parameters @infra/params/prod.bicepparam
- Deploy with a workload identity or service principal.
az login
az account set --subscription "Production-Subscription"
az deployment group create --resource-group rg-platform-prod-we --template-file main.bicep --parameters @infra/params/prod.bicepparam
- Add CI validation in Azure DevOps or GitHub Actions.
name: bicep-validate
on:
pull_request:
branches: [ main ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- run: az bicep install
- run: az bicep build --file main.bicep
- run: az deployment group what-if --resource-group rg-platform-prod-we --template-file main.bicep --parameters @infra/params/prod.bicepparam
Code Examples
Example 1: Resource group scoped storage account
{
"example": "main.bicep",
"content": "param location string = resourceGroup().location\nparam storageName string\nresource sa 'Microsoft.Storage/storageAccounts@2023-05-01' = {\n name: storageName\n location: location\n sku: { name: 'Standard_LRS' }\n kind: 'StorageV2'\n properties: {\n allowBlobPublicAccess: false\n minimumTlsVersion: 'TLS1_2'\n supportsHttpsTrafficOnly: true\n }\n}\noutput storageId string = sa.id"
}
Example 2: Parameter file for production
{
"using": "./main.bicep",
"parameters": {
"storageName": "stplatformprodwe01"
}
}
Example 3: Publish and consume a module from ACR
az acr login --name acrplatformprod
az bicep publish --file modules/storage.bicep --target br:acrplatformprod.azurecr.io/bicep/modules/storage:v1
az bicep restore --file main.bicep
Security Hardening
- Use managed identities or federated workload identities instead of client secrets in pipelines.
- Store secrets in Azure Key Vault and reference them at deployment time rather than hardcoding values.
- Restrict deployment permissions with least privilege RBAC, such as
Contributoronly at the required scope andUser Access Administratoronly for RBAC deployments. - Enforce Azure Policy for allowed locations, mandatory tags, private endpoints, and encryption settings.
- Enable diagnostic settings and activity log export for deployment auditing.
- Use
what-ifbefore production releases to detect destructive changes. - Sign and control module provenance by publishing approved modules to a private ACR registry.
Comparison
| Feature | Bicep | Terraform | Pulumi |
|---|---|---|---|
| Pricing | Free language; Azure charges for deployed resources only | Open source; Terraform Cloud paid tiers for collaboration | Open source core; paid SaaS tiers for enterprise features |
| Deployment | Native ARM deployment engine for Azure scopes | Provider-based multi-cloud deployment | General-purpose languages with provider-based deployment |
| Scalability | Strong for Azure enterprise landing zones and policy-driven governance | Excellent multi-cloud scale and ecosystem breadth | Strong extensibility, depends on language practices |
| Security | Native Azure RBAC, Policy, Key Vault, management group support | Strong state controls but requires backend hardening | Flexible, but application-language risk surface is larger |
Troubleshooting
Error 1: Invalid template scope
Log sample:
ERROR: {"code":"InvalidDeploymentScope","message":"The resource scope 'resourceGroup' is not valid for this deployment. Please use a subscription level deployment."}
Fix: Match the deployment command to the Bicep target scope. Use az deployment sub create for subscription-scope files.
Error 2: Missing module restore
Log sample:
BCP192: Unable to restore the artifact with reference "br:acrplatformprod.azurecr.io/bicep/modules/storage:v1". Authentication failed for the remote registry.
Fix: Run az acr login --name acrplatformprod and verify the pipeline identity has AcrPull on the registry.
Error 3: Policy denial
Log sample:
ERROR: {"code":"RequestDisallowedByPolicy","message":"Resource 'stplatformprodwe01' was disallowed by policy. Policy identifiers: '[{"policyAssignment":{"name":"enforce-private-endpoints"}}]'"}
Fix: Update the template to meet policy requirements, such as adding private endpoints, approved SKUs, or mandatory tags.
Best Practices
Do
- Create reusable modules for networking, storage, and monitoring baselines.
- Separate code and environment data using
.bicepparamfiles. - Validate with
what-ifin every pull request. - Pin API versions explicitly to avoid drift.
- Use private registries for approved enterprise modules.
Don't
- Do not embed secrets in parameters or source control.
- Do not grant subscription-wide Owner to CI identities unless absolutely necessary.
- Do not mix scopes carelessly; management group, subscription, and resource group deployments should be intentional.
- Do not bypass policy failures; fix the architecture to comply.
- Do not leave modules unversioned; publish semantic versions like
v1,v1.1.0, orv2.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI