Appearance
Bicep IaC Modules
Microtec ERP infrastructure is defined as code using Azure Bicep. The infrastructure is subscription-scoped (deploying multiple resource groups), organized into reusable modules per resource type, and driven by a PowerShell parameter-builder script that converts services-config.json into Bicep parameters.
Repository Location
All Bicep files are under Devops/azure/infrastructure/:
Devops/azure/infrastructure/
├── main.bicep # Subscription-scoped entry point
├── main.bicepparam # Default parameters (overridden per env)
├── modules/
│ ├── resource-groups.bicep # Creates 8 RGs per environment
│ ├── networking.bicep # VNet, subnets, NSGs, NAT Gateway
│ ├── container-apps.bicep # Public + Private CAE, container apps
│ ├── key-vault.bicep # Key Vault + RBAC assignments
│ ├── container-registry.bicep # ACR per environment
│ ├── redis.bicep # Azure Cache for Redis
│ ├── service-bus.bicep # Azure Service Bus namespace + topics
│ ├── storage-backend.bicep # Backend blob storage (attachments)
│ ├── storage-frontend.bicep # Frontend static hosting storage
│ ├── static-web-apps.bicep # One SWA per Angular app (9 total)
│ ├── managed-identity.bicep # User-assigned MI + role assignments
│ ├── front-door.bicep # AFD profile, origins, routes, WAF
│ ├── log-analytics.bicep # Log Analytics Workspace
│ └── dns.bicep # Private DNS zones
└── scripts/
└── infra/
└── Build-BicepParams.ps1 # services-config.json → Bicep paramsmain.bicep — Subscription-Scoped Entry Point
bicep
targetScope = 'subscription'
param environment string // dev | stage | preprod | uat | prod
param location string = 'uksouth'
param resourceConfig object // built from services-config.json
// ── Resource Group Creation ──────────────────────────────────────
module rgs 'modules/resource-groups.bicep' = {
name: 'rgs-${environment}'
params: {
environment: environment
location: location
}
}
// ── Networking (must deploy before apps) ──────────────────────────
module networking 'modules/networking.bicep' = {
name: 'networking-${environment}'
scope: resourceGroup(rgs.outputs.networkRgName)
dependsOn: [rgs]
params: {
environment: environment
vnetCidr: resourceConfig.vnetCidr // e.g. '10.0.0.0/16' for dev
location: location
}
}
// ── Managed Identity ──────────────────────────────────────────────
module identity 'modules/managed-identity.bicep' = {
name: 'identity-${environment}'
scope: resourceGroup(rgs.outputs.appsRgName)
dependsOn: [rgs]
params: { environment: environment, location: location }
}
// ── Data Layer ────────────────────────────────────────────────────
module redis 'modules/redis.bicep' = {
name: 'redis-${environment}'
scope: resourceGroup(rgs.outputs.dataRgName)
dependsOn: [networking]
params: {
environment: environment
location: location
subnetId: networking.outputs.dataSubnetId
}
}
module serviceBus 'modules/service-bus.bicep' = {
name: 'asb-${environment}'
scope: resourceGroup(rgs.outputs.dataRgName)
dependsOn: [rgs]
params: { environment: environment, location: location }
}
// ── Container Registry ────────────────────────────────────────────
module acr 'modules/container-registry.bicep' = {
name: 'acr-${environment}'
scope: resourceGroup(rgs.outputs.storageRgName)
dependsOn: [rgs]
params: {
environment: environment
location: location
managedIdentityId: identity.outputs.identityId
}
}
// ── Container Apps Environments ───────────────────────────────────
module cae 'modules/container-apps.bicep' = {
name: 'cae-${environment}'
scope: resourceGroup(rgs.outputs.appsRgName)
dependsOn: [networking, identity, acr, redis, serviceBus]
params: {
environment: environment
location: location
services: resourceConfig.services
// ... other params
}
}
// Additional modules: key-vault, front-door, storage, swa, dns, monitoring8 Resource Groups per Environment
The resource-groups.bicep module creates a consistent set of RGs:
| RG Name Pattern | Purpose |
|---|---|
mic-erp-be-{env}-network-rg | VNet, subnets, NSGs, NAT Gateway |
mic-erp-be-{env}-apps-rg | Container Apps Environments, container apps |
mic-erp-be-{env}-data-rg | Redis, Service Bus, private endpoints |
mic-erp-be-{env}-storage-rg | ACR, backend blob storage |
mic-erp-be-{env}-keyvault-rg | Key Vault |
mic-erp-be-{env}-monitor-rg | Log Analytics, Application Insights |
mic-erp-fr-{env}-storage-rg | Frontend storage (MFE blobs) |
mic-erp-fr-{env}-swa-rg | Static Web Apps (9 per env) |
Shared SQL RG
mic-backend-shared-sql-rg is NOT created by per-environment Bicep. It is a shared, manually managed resource group. Do NOT add it to the deprovision.yml teardown pipeline.
VNet CIDR Allocation
The vnetCidr parameter is set per environment in Build-BicepParams.ps1:
| Environment | VNet CIDR |
|---|---|
| dev | 10.0.0.0/16 |
| stage | 10.1.0.0/16 |
| preprod | 10.6.0.0/16 |
| uat | 10.5.0.0/16 |
| production | 10.2.0.0/16 |
Build-BicepParams.ps1 — Config to Parameters
This PowerShell script is the bridge between services-config.json (the master service configuration) and Bicep deployment parameters. It runs as the first step in every infrastructure pipeline.
Location: Devops/azure/scripts/infra/Build-BicepParams.ps1
powershell
param(
[string]$Environment,
[string]$ConfigPath = "Devops/azure/config/container-backend/services-config.json",
[string]$OutputPath = "infra.bicepparam"
)
$config = Get-Content $ConfigPath | ConvertFrom-Json
$envConfig = $config.environments.$Environment
# Build Bicep parameter file
$params = @{
environment = $Environment
location = $envConfig.location
vnetCidr = $envConfig.vnetCidr
redisSku = $envConfig.redisSku
acrSku = $envConfig.acrSku
resourceConfig = @{
services = $config.services | ForEach-Object {
@{
name = $_.name
image = "$($_.repository):$($envConfig.imageTag)"
minReplicas = $_.scaling.$Environment.minReplicas
maxReplicas = $_.scaling.$Environment.maxReplicas
cpu = $_.resources.cpu
memory = $_.resources.memory
envVars = $_.envVars.$Environment
}
}
vnetCidr = $envConfig.vnetCidr
}
}
$params | ConvertTo-Json -Depth 10 | Set-Content $OutputPath
Write-Host "Generated: $OutputPath for environment: $Environment"Usage in pipeline:
yaml
- task: PowerShell@2
displayName: 'Build Bicep Parameters'
inputs:
filePath: 'Devops/azure/scripts/infra/Build-BicepParams.ps1'
arguments: '-Environment $(environment)'
- task: AzureCLI@2
displayName: 'Deploy Bicep Infrastructure'
inputs:
script: |
az deployment sub create \
--location uksouth \
--template-file Devops/azure/infrastructure/main.bicep \
--parameters infra.bicepparam \
--name "infra-$(environment)-$(Build.BuildId)"Idempotency
All Bicep deployments are idempotent (ARM complete vs incremental mode):
- Default mode:
incremental— only changes are applied, existing resources not deleted deprovision.ymluses explicitaz group deletecommands (not Bicep complete mode)
Re-running a Bicep deployment on an unchanged environment is safe and produces no changes.
What-If Preview
Before applying infrastructure changes, the infra-only.yml pipeline runs a what-if preview:
bash
az deployment sub what-if \
--location uksouth \
--template-file Devops/azure/infrastructure/main.bicep \
--parameters infra.bicepparamThis outputs a diff of all planned resource changes without applying them. Review this output in the pipeline log before proceeding.
Module Output Reference
Key outputs passed between modules:
| Module | Output Name | Used By |
|---|---|---|
resource-groups | appsRgName, dataRgName, networkRgName | All subsequent modules |
networking | publicAppsSubnetId, privateAppsSubnetId, dataSubnetId | CAE, Redis, Service Bus |
managed-identity | identityId, identityPrincipalId | ACR, Key Vault, Container Apps |
container-registry | acrLoginServer, acrId | Container Apps |
key-vault | keyVaultUri, keyVaultId | Container Apps (secret refs) |
container-apps | publicCaeFqdn, privateCaeFqdn | Front Door origins |
Related Documentation
- Resource Groups — Detailed RG inventory per environment
- Networking — VNet and subnet architecture
- Services Config — Master configuration file
- Provision Infra Pipeline — Pipeline that runs Bicep
- Scripts Catalog —
Build-BicepParams.ps1and other scripts