
August 20, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Managing cloud infrastructure through manual portal clicks creates drift, security gaps, and recovery nightmares. Azure Bicep: Infrastructure as Code solves this by letting you define Azure resources in a clean, declarative DSL that transpiles to standard ARM JSON. For developers building Laravel or Node.js applications on Azure, Bicep offers a maintainable alternative to raw ARM templates while retaining full platform support. If you are evaluating cloud hosting services in Nepal or managing global Azure deployments, understanding Bicep is now essential for reliable operations.
What is Azure Bicep: Infrastructure as Code and why replace ARM?
ARM (Azure Resource Manager) templates have been the standard for Azure automation since 2015, but their JSON verbosity makes them painful to write and review. A simple storage account definition can span 50+ lines of nested JSON objects, parameters, and variables. Bicep abstracts this complexity into a readable syntax that compiles directly to valid ARM JSON at deployment time.
In practice, Bicep reduces template size by 40–60% compared to equivalent ARM JSON. More importantly, it eliminates common JSON errors like missing commas, bracket mismatches, and incorrect nesting that plague large ARM templates. The Azure Bicep CLI handles transpilation transparently — you never commit generated JSON to source control.
Key advantages over raw ARM templates include:
- Type safety and validation: VS Code Bicep extension provides real-time error checking and autocomplete for resource types, API versions, and properties
- Simplified syntax: No need for
parameters,variables, andresourceswrapper objects; declare everything at root level - Native module support: Split infrastructure into reusable components without complex linked template URLs or artifact staging
- Symbolic naming: Reference resources by logical name instead of
reference()functions with resourceId strings - Zero runtime dependency: Bicep CLI ships with Azure CLI; no separate installation or build server configuration needed
For teams already using Terraform, Bicep occupies a different niche. Terraform manages multi-cloud state and uses HCL; Bicep is Azure-native, stateless (relies on ARM state), and receives day-one support for new Azure features. Choose Bicep when your stack is Azure-only and you want tightest platform integration.
How do you write your first Azure Bicep template?
Start with the Azure CLI (v2.65+ recommended for 2026). Verify installation:
az bicep version
# Output: Bicep CLI version 0.30.x (latest stable 2026) Create a basic storage account deployment:
// main.bicep
param location string = resourceGroup().location
param storageAccountName string = 'stapp${uniqueString(resourceGroup().id)}'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
}
}
output storageAccountId string = storageAccount.id
output primaryEndpoint string = storageAccount.properties.primaryEndpoints.blob Deploy to a resource group:
# Create resource group if needed
az group create --name rg-demo-bicep --location eastus
# Deploy Bicep file directly (CLI transpiles automatically)
az deployment group create \
--resource-group rg-demo-bicep \
--template-file main.bicep \
--parameters location=eastus Notice the API version @2023-05-01 pinned explicitly. Always use specific API versions rather than latest to prevent breaking changes during redeployment. The Bicep extension shows available versions via autocomplete.
Handling dependencies and outputs
Bicep infers implicit dependencies when you reference one resource inside another. Explicit dependencies use dependsOn:
resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = {
parent: storageAccount // Implicit dependency established
name: 'default/images'
properties: {
publicAccess: 'None'
}
}
// Explicit dependency when no direct reference exists
resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
name: 'func-app-demo'
location: location
dependsOn: [storageAccount] // Wait for storage even without reference
} Outputs enable downstream consumption in CI pipelines or other modules. Use existing keyword to reference resources not managed by current template:
resource existingVnet 'Microsoft.Network/virtualNetworks@2023-11-01' existing = {
name: 'vnet-production'
}
output vnetId string = existingVnet.id How do you structure Azure Bicep modules for reusability?
Monolithic Bicep files become unmaintainable beyond 200–300 lines. Modules encapsulate related resources behind stable interfaces. This pattern mirrors service extraction in application code — something I apply regularly when architecting Laravel backends or configuring DevOps automation for web projects.
Create a reusable App Service module:
// modules/app-service.bicep
param name string
param location string
param appServicePlanId string
param runtimeStack string = 'DOTNET|8.0'
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' existing = {
name: split(appServicePlanId, '/')[8]
}
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: name
location: location
serverFarmId: appServicePlanId
identity: {
type: 'SystemAssigned'
}
properties: {
siteConfig: {
netFrameworkVersion: 'v8.0'
alwaysOn: true
ftpsState: 'Disabled'
minTlsVersion: '1.2'
}
httpsOnly: true
}
}
output principalId string = webApp.identity.principalId
output defaultHostName string = webApp.properties.defaultHostName Consume in main template:
module webApp './modules/app-service.bicep' = {
name: 'deploy-web-app'
params: {
name: 'app-frontend-${environment}'
location: location
appServicePlanId: appServicePlan.id
runtimeStack: 'NODE|22-lts'
}
}
// Use module output for RBAC assignment
resource blobContributor 'Microsoft.Authorization/roleAssignments@2023-04-01-preview' = {
scope: storageAccount
name: guid(storageAccount.id, webApp.outputs.principalId, 'BlobDataContributor')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
principalId: webApp.outputs.principalId
principalType: 'ServicePrincipal'
}
} Module best practices:
- Parameterize everything environment-specific — names, SKUs, regions, feature flags
- Expose only necessary outputs — avoid leaking internal resource IDs unless consumed externally
- Pin API versions per module — allows independent upgrade cycles
- Use Bicep registries (ACR or MCR) for cross-team sharing instead of Git submodules
- Document parameters with decorators:
@description(),@allowed(),@minLength()
Azure Bicep vs Terraform vs ARM: Which should you choose?
Selecting an IaC tool depends on team skills, cloud commitment, and operational requirements. Here is a practical comparison based on production usage patterns:
| Criteria | Azure Bicep | Terraform | ARM Templates |
|---|---|---|---|
| Learning curve | Low (Azure-native syntax) | Medium (HCL + providers) | High (verbose JSON) |
| Multi-cloud support | Azure only | All major clouds | Azure only |
| State management | ARM-managed (stateless) | Explicit state files/backend | ARM-managed (stateless) |
| New Azure feature support | Day-zero | Provider-dependent (weeks) | Day-zero |
| Tooling maturity | VS Code ext + CLI | Mature ecosystem | Legacy tooling |
| Community modules | AVM (growing) | Registry (extensive) | Quickstarts (dated) |
| Drift detection | Manual / What-if | Native plan command | Manual / What-if |
| Best for | Azure-pure shops, MSFT partners | Multi-cloud, complex state | Legacy maintenance only |
Choose Azure Bicep: Infrastructure as Code when your organization is committed to Azure, wants minimal abstraction overhead, and values immediate access to new platform capabilities. Choose Terraform when managing resources across AWS/GCP/Azure simultaneously or when mature state locking and drift detection are non-negotiable. Avoid starting new projects with ARM templates in 2026 — migrate existing ones to Bicep using az bicep decompile.
How do you integrate Azure Bicep into CI/CD pipelines?
Infrastructure changes must flow through the same review and testing process as application code. Here is a GitHub Actions workflow validated for 2026:
# .github/workflows/bicep-deploy.yml
name: Deploy Azure Infrastructure
on:
push:
branches: [main]
paths: ['infra/**']
permissions:
id-token: write
contents: read
jobs:
validate-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login (OIDC)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Validate Bicep
run: |
az deployment group validate \
--resource-group rg-production \
--template-file infra/main.bicep \
--parameters infra/prod.parameters.json
- name: Preview Changes (What-If)
run: |
az deployment group what-if \
--resource-group rg-production \
--template-file infra/main.bicep \
--parameters infra/prod.parameters.json \
--result-format FullResourcePayloads
- name: Deploy
run: |
az deployment group create \
--resource-group rg-production \
--template-file infra/main.bicep \
--parameters infra/prod.parameters.json \
--name "deploy-${{ github.run_number }}" Critical pipeline practices:
- Use OIDC federation instead of long-lived service principal secrets — eliminates credential rotation burden
- Always run
what-ifbefore deploy — catches destructive changes (deletions, replacements) in PR reviews - Parameterize environments separately —
dev.parameters.json,prod.parameters.jsonwith distinct SKUs and naming - Tag deployments with Git SHA — enables rollback correlation between app and infra versions
- Enforce policy via Azure Policy — Bicep cannot prevent post-deployment drift; policies guard compliance continuously
For teams using CI/CD pipeline setups with GitLab CI (common in Nepal-based dev shops), replace GitHub Actions with equivalent azure-cli container jobs and OIDC via workload identity federation. The core commands remain identical.
Common Azure Bicep mistakes and how to avoid them
After reviewing dozens of Bicep implementations, these issues recur consistently:
Hardcoding resource names without uniqueness
Global resources (storage accounts, Key Vaults) require globally unique names. Always incorporate uniqueString() or environment prefixes:
// BAD: Will fail on second deployment
resource kv 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'my-keyvault'
}
// GOOD: Deterministic uniqueness per resource group
resource kv 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'kv-${uniqueString(resourceGroup().id)}'
} Ignoring deployment scope mismatches
Bicep defaults to resource group scope. Subscription-level resources (RBAC, policy, resource groups themselves) require explicit scope:
// In main.bicep deployed at subscription scope
targetScope = 'subscription'
resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
name: 'rg-app-${environment}'
location: 'eastus'
}
// Module targeting specific resource group
module network './networking.bicep' = {
name: 'network-deploy'
scope: rg // Explicit scope override
params: { /* ... */ }
} Missing secret handling discipline
Never pass secrets as plain parameters. Use Key Vault references or managed identities:
// Secure parameter declaration
@secure()
param dbPassword string
// Better: Reference existing Key Vault secret directly
resource kv 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
name: 'kv-shared-secrets'
}
module sqlDb './modules/sql-database.bicep' = {
name: 'sql-deploy'
params: {
adminPassword: kv.getSecret('sql-admin-password')
}
} Neglecting what-if before production deploys
The what-if operation reveals unintended deletions caused by renamed resources or changed scopes. Treat any "Delete" action in what-if output as a mandatory review checkpoint. Automate this check in PR comments so reviewers see infrastructure impact alongside code diffs.
Implementing Azure Bicep: Infrastructure as Code in production
Azure Bicep: Infrastructure as Code delivers tangible value when treated as engineering discipline, not just syntax. Start small — migrate a single resource group or non-production environment first. Establish module conventions and CI validation before tackling production workloads. Invest time in learning the Bicep registry and Azure Verified Modules (AVM) to avoid reinventing networking and identity patterns.
For Nepal-based teams adopting Azure, remember that Bicep's stateless nature means lower operational overhead than Terraform state backends — beneficial when infrastructure expertise is limited. Pair Bicep with Azure Policy for governance, and always version-control parameter files alongside templates.
If you need hands-on guidance implementing Azure Bicep for your application infrastructure, integrating it with existing Laravel or Node.js deployments, or setting up compliant CI/CD pipelines, reach out to discuss your specific requirements. Practical experience beats documentation reading every time.

