Playbook for writing production-ready Bicep modules — parameter hygiene, AVM alignment, what-if verification, output contracts, and the CI/CD integration checklist. Covers both standalone and AVM-wrapper patterns.
Scanned 9/23/2026
npx -y skills add mcorbett51090/RavenClaude --skill bicep-module-authoring --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Bicep Module Authoring?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mcorbett51090-bicep-module-authoring)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: bicep-module-authoring
description: "Playbook for writing production-ready Bicep modules — parameter hygiene, AVM alignment, what-if verification, output contracts, and the CI/CD integration checklist. Covers both standalone and AVM-wrapper patterns."
---
# Bicep Module Authoring
## When to Use This Skill
Use when writing a new Bicep module from scratch, wrapping an AVM module with project-specific defaults, or reviewing a module before it enters a shared library.
## 1. Module Skeleton
```bicep
// modules/storage-account/main.bicep
@description('Name of the Storage Account (3-24 chars, lowercase alphanumeric).')
param name string
@description('Azure region for the resource.')
param location string = resourceGroup().location
@description('Environment tag value (dev|test|prod).')
@allowed(['dev', 'test', 'prod'])
param environment string
@description('Resource tags merged with the required set.')
param tags object = {}
var requiredTags = {
environment: environment
managedBy: 'bicep'
}
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: name
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
tags: union(requiredTags, tags)
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
allowSharedKeyAccess: false
publicNetworkAccess: 'Disabled'
supportsHttpsTrafficOnly: true
}
}
@description('Resource ID of the Storage Account.')
output resourceId string = storageAccount.id
@description('Name of the Storage Account.')
output name string = storageAccount.name
```
## 2. Parameter Hygiene Rules
| Rule | Detail |
|---|---|
| Decorate every parameter | `@description`, `@allowed`, `@minLength`/`@maxLength` where applicable |
| No secrets as parameters | Passwords, keys, connection strings → Key Vault reference or Managed Identity |
| No hardcoded subscription/tenant GUIDs | Use `subscription().subscriptionId` / `tenant().tenantId` |
| Required vs optional | Required params have no default; optional ones do — be explicit |
| Naming | camelCase for params/vars, PascalCase for resource symbolic names |
## 3. AVM Alignment
```bicep
// Prefer an AVM module over a raw resource when one exists [verify-at-build]
module storageAccount 'br/public:avm/res/storage/storage-account:0.9.0' = {
name: 'storageAccountDeployment'
params: {
name: name
location: location
skuName: 'Standard_LRS'
allowBlobPublicAccess: false
publicNetworkAccessEnabled: false
tags: tags
}
}
```
Check the AVM registry at `aka.ms/avm` before writing a raw resource block.
## 4. What-If Before Apply
```bash
# Validate and preview changes — never skip this in a pipeline
az deployment group what-if \
--resource-group rg-myapp-prod-eastus \
--template-file main.bicep \
--parameters @params.prod.json \
--result-format FullResourcePayloads
```
Gate the apply step on human approval for `prod` environments; auto-apply is acceptable for `dev`/`test`.
## 5. Output Contract
Every module must export:
- `resourceId` — used by dependent modules to construct dependencies
- `name` — used by deployment scripts and observability config
- Role-specific outputs (e.g. `primaryEndpoint` for Storage, `fqdn` for App Service)
Never output secrets or connection strings. Reference Key Vault from the consuming module.
## 6. CI/CD Pipeline Gate Checklist
- [ ] `az bicep build` — compiles and validates syntax
- [ ] `az deployment group validate` — ARM schema validation (catches type mismatches)
- [ ] `az deployment group what-if` — diff review gate before apply
- [ ] Policy compliance check (`az policy state trigger-scan`) in pre-prod
- [ ] Deployment Stacks (not classic RG deployments) for managed lifecycle + `DenySettings`
## 7. Deployment Stacks Pattern
```bash
az stack group create \
--name myapp-prod \
--resource-group rg-myapp-prod-eastus \
--template-file main.bicep \
--parameters @params.prod.json \
--deny-settings-mode DenyWriteAndDelete \
--action-on-unmanage DetachAll
```
Deployment Stacks tracks all resources in the deployment and enforces `DenySettings` — the replacement for Blueprints. [verify-at-build]
## Pitfalls
- Outputting a connection string or storage key — use Managed Identity or Key Vault reference instead
- Writing a raw `Microsoft.*` resource block when an AVM module exists — duplicates maintenance burden
- Skipping `what-if` and applying directly — produces undocumented drift
- Using `Contributor` or `Owner` role assignments at subscription scope in a module — the anti-patterns hook flags these
- Hard-coding the API version as `@latest` — lock to a specific version to prevent silent schema changes
## See Also
- [`../../agents/bicep-iac-engineer.md`](../../agents/bicep-iac-engineer.md) — Bicep vs Terraform decision, Deployment Stacks, CI/CD pipeline
- [`../../agents/azure-architect.md`](../../agents/azure-architect.md) — landing zone and subscription topology
- [`../../CLAUDE.md`](../../CLAUDE.md) — house opinions on IaC and passwordless defaults
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!