'Bicep coding standards, patterns, and governance for Azure deployments across Learning, Project, and Client workspaces. USE FOR: Bicep Azure code generation, module scaffolding, deployment stacks, parameter files, tagging, naming conventions, validation, security, Bicep style, Bicep best practices, ARM template alternatives, subscription-scoped deployments. DO NOT USE FOR: Terraform deployments (use terraform-azure), non-Azure IaC, ARM JSON templates.'
Install
npx skillscat add greg-t8/agent-toolkit/bicep-azure Install via the SkillsCat registry.
SKILL: Bicep on Azure
Goal
Provide authoritative Bicep coding standards, patterns, and governance for all Azure deployments. This skill consolidates guidance from workspace-level governance documents, shared-contract rules, and real-world patterns observed across the Learning, Project, and Client workspaces.
When to Use
- Generating or reviewing Bicep code for Azure resources
- Scaffolding a new Bicep project or module
- Applying deployment scope, parameter, naming, or tagging patterns
- Configuring deployment stacks and wrapper scripts
- Validating Bicep configurations against governance standards
- Advising on Bicep best practices, security, or cost optimization
Source References
This skill synthesizes guidance from:
instructions/Azure Governance Guidelines.instructions.md— cross-workspace naming, tagging, cost, securityLearningAzure/.github/skills/bicep-scaffolding/SKILL.md— lab-specific scaffolding rules (R-130 through R-138)LearningAzure/.github/skills/shared-contract/SKILL.md— cross-cutting lab rules (R-001 through R-030)LearningAzure/Governance-Lab.md— Learning workspace governanceLearningAzure/.assets/shared/bicep/bicep.ps1— shared deployment wrapper script- Real Bicep modules from
LearningAzure/AZ-104/hands-on-labs/— proven patterns in production labs
1. General Principles
- Use Bicep as the preferred ARM abstraction for Azure resource deployments.
- Prioritize readability, clarity, and maintainability in all templates.
- Write concise, idiomatic Bicep that is easy to understand.
- Use
bicep buildto validate syntax before deployment. - Use
bicepconfig.jsonfor lint/compile configuration. - Ensure idempotency — multiple deployments should produce the same result.
- Follow the principle of least privilege for all RBAC and network rules.
- Prefer Bicep over raw ARM JSON templates in all cases.
2. File Structure
Standard Project Layout
bicep/
├── main.bicep # Root module (subscription or resource group scope)
├── main.bicepparam # Parameter values file
├── bicepconfig.json # Lint and compile configuration
├── bicep.ps1 # Deployment wrapper script (shared, never modify)
└── modules/
└── <module>.bicep # Individual module filesFile Responsibilities
| File | Content |
|---|---|
main.bicep |
Root orchestration — target scope, parameters, resource group, module calls |
main.bicepparam |
Parameter values using using './main.bicep' syntax |
bicepconfig.json |
Bicep linter and compiler settings |
bicep.ps1 |
Shared deployment wrapper — copy from .assets/shared/bicep/bicep.ps1, never create custom |
modules/*.bicep |
Individual module files — one concern per module |
Learning Workspace Layout (Labs)
<EXAM>/hands-on-labs/<domain>/lab-<topic>/
├── README.md
├── bicep/
│ ├── main.bicep
│ ├── main.bicepparam
│ ├── bicepconfig.json
│ ├── bicep.ps1
│ └── modules/
│ └── <module>.bicep
└── validation/
└── <validation-script>.ps13. Deployment Scope and Stacks
Subscription-Scoped Deployments
Use targetScope = 'subscription' for labs and projects that create resource groups:
targetScope = 'subscription'
resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = {
name: resourceGroupName
location: location
tags: commonTags
}
module networking 'modules/networking.bicep' = {
name: 'networking-deployment'
scope: rg
params: {
commonTags: commonTags
}
}Deployment Stack Naming (Learning Workspace)
Pattern: stack-<domain>-<topic> — no exam code in stack name.
Wrapper Script Commands
The shared bicep.ps1 supports these actions:
| Command | Purpose |
|---|---|
validate |
Syntax and schema validation |
plan |
Deployment preview (what-if) |
apply |
Create or update deployment stack |
destroy |
Delete deployment stack |
show |
Show stack details |
list |
List all stacks |
output |
Show stack outputs |
# Validate
.\bicep.ps1 validate
# Preview
.\bicep.ps1 plan
# Deploy
.\bicep.ps1 apply
# Cleanup
.\bicep.ps1 destroyThe wrapper script auto-derives the stack name from the parameters file and validates subscription context before any deployment action.
Cleanup destroy command:
az stack sub delete --name $stackName --yes --force-deletion-types $forceTypes4. Naming Conventions
4.1 Resource Group Naming by Workspace
| Workspace | Pattern | Example |
|---|---|---|
| Learning | <exam>-<domain>-<topic>-bicep |
az104-compute-enable-boot-diagnostics-bicep |
| Project | project-<workspace>-<environment>-<purpose>[-<instance>]-bicep |
project-docwriter-dev-data-01-bicep |
| Client | client-<client>-<environment>-<purpose>[-<instance>]-bicep |
client-tcu-prod-security-01-bicep |
4.2 Resource Naming
Pattern: <prefix>-<descriptive-name>[-<instance>]
Use Azure CAF-aligned abbreviations:
| Resource Type | Prefix | Example |
|---|---|---|
| Virtual Network | vnet |
vnet-core-01 |
| Subnet | snet |
snet-app-01 |
| Network Security Group | nsg |
nsg-web-01 |
| Network Interface | nic |
nic-vm-web-01 |
| Virtual Machine | vm |
vm-web-01 |
| Storage Account | st |
staz104bootdiag |
| Key Vault | kv |
kv-secrets-01 |
| App Service Plan | asp |
asp-web-01 |
| Function App | func |
func-processor-01 |
| Log Analytics | log |
log-monitor-01 |
| Application Insights | appi |
appi-web-01 |
| OpenAI Account | oai |
oai-gpt-01 |
| Cognitive Services | cog |
cog-lang-7k3m |
| Recovery Services Vault | rsv |
rsv-backup-4h8n |
| Bastion Host | bas |
bas-hub-01 |
4.3 Style Rules
- All segments use lowercase with hyphens as delimiters.
- Resources disallowing hyphens (Storage Accounts, Container Registries) use contiguous lowercase alphanumeric.
- Deployment moniker (
-bicep) is mandatory and always last for resource groups. - Use
camelCasefor Bicep parameter names and local variables.
4.4 Static Names by Default
All resource names must be static and predictable. Random suffixes are only permitted for resources subject to soft-delete name reservation:
| Resource | Retention | Random Suffix Required |
|---|---|---|
| Cognitive Services | 48 hrs | Yes |
| Key Vault | 7–90 days | Yes |
| API Management | 48 hrs | Yes |
| Recovery Vault | 14 days | Yes |
Random suffix format: uniqueString(resourceGroup().id) truncated or scoped.
var suffix = uniqueString(resourceGroup().id)
var cogName = 'cog-${topic}-${suffix}'5. Tagging Standards
5.1 Common Tags Pattern
var commonTags = {
Environment: environment
Category: category // 'Learning', 'Project', 'Client'
Workspace: workspace
Purpose: purpose
Owner: owner
DateCreated: dateCreated // Static YYYY-MM-DD — never use utcNow()
DeploymentMethod: 'Bicep'
ManagedBy: 'bicep'
}5.2 Tag Rules
DateCreatedmust be a static string. Never useutcNow()or any dynamic function.- Tag keys use PascalCase.
- Apply tags to all taggable resources via
commonTags. - Pass
commonTagsas an explicit parameter to all modules. - Workspace-specific governance may add required tags.
5.3 Learning Workspace Tags
var commonTags = {
Environment: 'Lab'
Project: '<EXAM>' // 'AI-102', 'AZ-104'
Domain: '<Domain>' // 'Networking', 'Compute'
Purpose: '<Purpose>' // 'Enable Boot Diagnostics'
Owner: owner
DateCreated: dateCreated
DeploymentMethod: 'Bicep'
}6. Parameters and Code Style
6.1 Parameter Declarations
Every parameter must have:
@description()decorator — clear, concise purpose- Explicit type
@allowed()decorator when values are constrained@secure()decorator for sensitive values
@description('Azure region for resource deployment')
param location string = 'eastus'
@description('Resource owner name')
param owner string = 'Greg Tate'
@description('Static date for DateCreated tag (YYYY-MM-DD)')
param dateCreated string
@description('Administrator password for virtual machines')
@secure()
param adminPassword string6.2 Parameter File (.bicepparam)
using './main.bicep'
param location = 'eastus'
param owner = 'Greg Tate'
param dateCreated = '<YYYY-MM-DD>'6.3 Local Variables
Use variables for computed values and repeated expressions:
var resourceGroupName = 'az104-${domain}-${topic}-bicep'
var commonTags = {
Environment: 'Lab'
Project: 'AZ-104'
Domain: 'Compute'
Purpose: 'Enable Boot Diagnostics'
Owner: owner
DateCreated: dateCreated
DeploymentMethod: 'Bicep'
}6.4 Naming Style
- Use
camelCasefor parameter names, variable names, and resource symbolic names. - Use
@description()on every parameter — not inline comments. - Group parameters logically: scope/location first, then domain-specific, then secrets last.
7. Modules
7.1 When to Use Modules
Use modules when 2+ related resource types are deployed together.
- One concern per module (domain grouping)
- Self-contained with clear inputs/outputs
- Thin orchestration in root
main.bicep - Anti-pattern: consolidating unrelated resource types into a single module
7.2 Module File Pattern
Each module is a .bicep file in modules/:
- Accepts
location,tags(object), and cross-module references as parameters. - Outputs resource IDs, endpoints, principal IDs.
- Uses
@description()decorators on all parameters.
// modules/networking.bicep
@description('Common tags applied to all networking resources')
param commonTags object
@description('Location for networking resources')
param location string = resourceGroup().location
resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' = {
name: 'vnet-core-01'
location: location
tags: commonTags
properties: {
addressSpace: {
addressPrefixes: ['10.0.0.0/16']
}
}
}
@description('Virtual network resource ID')
output vnetId string = vnet.id
@description('Virtual network name')
output vnetName string = vnet.name7.3 Module Consumption from Root
module networking 'modules/networking.bicep' = {
name: 'networking-deployment'
scope: rg
params: {
commonTags: commonTags
}
}
module compute 'modules/compute.bicep' = {
name: 'compute-deployment'
scope: rg
params: {
commonTags: commonTags
subnetId: networking.outputs.subnetId
}
}7.4 Module Best Practices
- Keep modules focused — single responsibility principle.
- Define clear input/output contracts.
- Pass identity references (e.g.,
principalId) as explicit inputs for RBAC. - Use
dependsOnonly when implicit dependencies are insufficient. - Always scope module deployments explicitly (e.g.,
scope: rg).
8. Security
- Never hardcode secrets in Bicep files.
- Use
@secure()decorator for sensitive parameters. - Prefer Managed Identities over passwords or keys.
- Use Key Vault with RBAC authorization for secret storage.
- Enable encryption for storage accounts and disks.
- Use private endpoints where applicable (required for production; optional for lab/dev).
- Public network access is acceptable in lab/dev environments only.
- Implement least privilege with Azure RBAC.
- Never output sensitive values without marking them appropriately.
Password Implementation
Use uniqueString(), a static pattern, or supply in .bicepparam. Mark with @secure().
Target pattern for labs: AzureLab2026!
@description('Administrator password')
@secure()
param adminPassword string9. Soft-Delete and Purge Management
Disable Pattern (Lab/Dev)
resource keyVault 'Microsoft.KeyVault/vaults@2024-04-01-preview' = {
...
properties: {
...
enableSoftDelete: false
}
}For Cognitive Services:
// softDeleteState is not always available — check API version supportRandom Suffix for Soft-Delete Resources
var suffix = uniqueString(resourceGroup().id)
var cogName = 'cog-${topic}-${suffix}'
var kvName = 'kv-${topic}-${suffix}'10. Subscription Safety
Subscription Validation
The shared bicep.ps1 wrapper validates subscription context automatically before apply, destroy, and plan actions. It checks against the configured lab subscription ID.
Validation Sequence
# Switch to correct profile
Use-AzProfile Lab
# Validate syntax
.\bicep.ps1 validate
# Capacity tests for constrained services (R-019)
# Preview deployment
.\bicep.ps1 plan
# Deploy
.\bicep.ps1 apply11. Cost Governance
Default SKUs (Non-Production)
| Resource Type | Default SKU |
|---|---|
| Virtual Machine | Standard_B2s |
| Storage Account | Standard_LRS |
| Load Balancer | Basic |
| Public IP | Basic |
| SQL Database | Basic / S0 |
| Managed Disk | Standard_HDD |
| Bastion | Developer |
| AI Services | F0 → S0 fallback |
Auto-Shutdown Schedule
All VMs must include auto-shutdown via Microsoft.DevTestLab/schedules:
| Setting | Default Value |
|---|---|
| Time | 0800 (8:00 AM) |
| Time Zone | Central Standard Time |
Cleanup Policy
- Destroy lab/dev resources within 7 days.
- Track via
DateCreatedtag. - README cleanup section must reference the 7-day policy.
12. Dependencies
Implicit Dependencies
Bicep automatically infers dependencies when one resource references another. Prefer implicit dependencies.
Explicit dependsOn
Use dependsOn only when implicit dependencies are insufficient:
resource bastion 'Microsoft.Network/bastionHosts@2024-10-01' = {
name: bastionName
...
dependsOn: [
networkingModule // Bastion requires VNet in Succeeded state
]
}Bastion exception: Always declare explicit dependency so Bastion creation waits for all networking resources (race condition with Developer SKU).
13. API Versions
- Use current, stable API versions for all resources.
- Prefer the latest GA API version available at time of authoring.
- Document the API version choice if deviating from latest.
- Update API versions when authoring new code — don't carry forward stale versions.
Common current API versions:
| Resource Type | Example API Version |
|---|---|
Microsoft.Resources/resourceGroups |
2024-03-01 |
Microsoft.Network/* |
2024-05-01 |
Microsoft.Compute/* |
2024-07-01 |
Microsoft.Storage/* |
2024-01-01 |
Microsoft.KeyVault/vaults |
2024-04-01-preview |
Microsoft.CognitiveServices/* |
2024-10-01 |
14. Code Header
Include in all .bicep files using // comment syntax:
// -------------------------------------------------------------------------
// Program: <filename>
// Description: <purpose>
// Context: <workspace context>
// Author: Greg Tate
// Date: <YYYY-MM-DD>
// -------------------------------------------------------------------------15. Outputs
- Include
@description()on every output. - Output resource group names, key endpoints, and resource IDs.
- Mark sensitive outputs appropriately.
@description('Resource group name')
output resourceGroupName string = rg.name
@description('Virtual machine name')
output vmName string = compute.outputs.vmName
@description('Storage account name for boot diagnostics')
output storageAccountName string = storage.outputs.storageName16. Anti-Patterns
- MUST NOT hardcode values that should be parameterized.
- MUST NEVER store secrets in Bicep files.
- MUST NOT disable security features for convenience.
- MUST NOT use default passwords or keys.
- MUST NOT use
utcNow()forDateCreatedtags. - SHOULD NOT create custom
bicep.ps1wrapper scripts — always copy from shared. - SHOULD avoid overly complex conditional logic that obscures intent.
- Anti-pattern: using ARM JSON templates when Bicep is available.
- Anti-pattern: inlining all resources in
main.bicepinstead of using modules.
17. Governance Override Hierarchy
When rules conflict, apply this precedence (highest wins):
- Workspace-specific governance (e.g.,
Governance-Lab.md) - Azure Governance Guidelines (
instructions/Azure Governance Guidelines.instructions.md) - This skill (general Bicep on Azure patterns)
- General Coding Guidelines (
instructions/General Coding Guidelines.instructions.md)
18. Region Rules
| Setting | Value |
|---|---|
| Default (Lab) | centralus (supports Bastion Developer) |
| Default (Other) | eastus |
| Fallback | eastus2 → westus2 → northcentralus |
| Allowed | Any US region |
Use default region unless capacity requires fallback. Document the choice if deviating.