Bicep Generation
Generate Bicep IaC files from the approved infrastructure plan.
Important: All Bicep files must be created under
<project-root>/infra/. Never place.bicepfiles in the project root or in.azure/.
File Structure
Generate files under <project-root>/infra/:
infra/
├── main.bicep # Orchestrator — deploys all modules
├── main.bicepparam # Parameter values
└── modules/
├── storage.bicep # One module per resource or logical group
├── compute.bicep
├── networking.bicep
└── monitoring.bicepGeneration Steps
- Create
infra/directory — create<project-root>/infra/and<project-root>/infra/modules/directories. All files in subsequent steps go here. - Read plan — load
<project-root>/.azure/infrastructure-plan.json, verifymeta.status === "approved" - Fetch Bicep schemas — for each resource in the plan, use a sub-agent to call
bicepschema_getwithresource-typeset to the ARM type from the relevant resources/ category file (e.g.,Microsoft.ContainerService/managedClusters). Instruct the sub-agent: "Return the full property structure for {ARM type}: required properties, allowed values, child resources. ≤500 tokens." Use this output — not training data — to generate correct resource definitions.
The schema tool returns only the schema for the exact type requested. Sub-resource types (e.g.,
Microsoft.Network/virtualNetworks/subnets) return a smaller, focused schema but miss parent-level properties (e.g., VNetencryptionlives on the parent, not the subnet sub-resource). Strategy:
- Start with sub-resource types when validating child resources — smaller responses (~25KB vs ~95KB), easier to summarize
- Fetch the parent type separately when you need parent-level properties (encryption, tags, SKU) — delegate to a sub-agent with specific property extraction instructions to manage the large response
- Generate modules — group resources by category; one
.bicepfile per group underinfra/modules/. Use the schema from step 3 for property names, allowed values, and required fields. - Generate main.bicep — write
infra/main.bicepthat imports all modules and passes parameters - Generate parameters — create
infra/main.bicepparamwith environment-specific values
Bicep Conventions
- Use
@description()decorators on all parameters - Use
@secure()for secrets and connection strings - Choose
targetScopeinmain.bicepbased on the deployment plan:- For single resource group deployments, set
targetScope = 'resourceGroup'and deploy withaz deployment group create. - For subscription-scope deployments (for example, resources across multiple resource groups or subscription-level resources), set
targetScope = 'subscription'and deploy withaz deployment sub create.
- For single resource group deployments, set
- Use
existingkeyword for referencing pre-existing resources - Output resource IDs and endpoints needed by other resources
- Use
dependsOnonly when implicit dependencies are insufficient
Parameter File Format
using './main.bicep'
param location = 'eastus'
param environmentName = 'prod'
param workloadName = 'datapipeline'Multi-Environment
For multi-environment plans, generate one parameter file per environment:
infra/
├── main.bicep
├── main.dev.bicepparam
├── main.staging.bicepparam
└── main.prod.bicepparamValidation Before Deployment
Run az bicep build --file infra/main.bicep to validate syntax before deploying.
Correctness Checklist (must pass az bicep build with zero errors)
Generate against these rules, then run az bicep build and fix in-place until clean. These are the
failures that most often break validation:
- No undeclared symbols. Every
param,var,resource, andmodulesymbol you reference is declared in the same file. Cross-file values flow only throughmoduleparams andoutputs. - Cross-module outputs (BCP053). When one module consumes
moduleX.outputs.Y, that module MUST declareoutput Y .... Verify every consumed output exists on the producing module. existingreferences are complete. Referenced resources use theexistingkeyword with the correct type,name(andscope/parentwhere required); never emit a newresourcefor them.- Required properties present. Use the schema fetched in step 3 — include every required property
and use only allowed enum values and a valid, real
@apiVersionfor each type. - Types match. Parameter/variable types match their usage; no string passed where an object/int is expected; array vs. single-object usage is consistent.
main.bicepparammatchesmain.bicep. Everyparamassigned in.bicepparamexists inmain.bicep; every required (non-defaulted) param is assigned;usingpoints at./main.bicep.targetScopematches the deploy command and anyresourceGroup()/subscription()usage.- No secrets in output. Never
outputa secret; mark secret params@secure().
If az bicep build is unavailable, self-review every item above before presenting.