Add Azure VM + Network Bicep IaC with modular structure#11
Add Azure VM + Network Bicep IaC with modular structure#11
Conversation
Co-authored-by: robpitcher <13648061+robpitcher@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a demo Azure Bicep infrastructure stack under infra/bicep/ to deploy a Windows Server VM with supporting network resources using a subscription-scope entrypoint template.
Changes:
- Introduces subscription-scope
main.bicepthat creates a resource group and deploys network + VM modules. - Adds
modules/network.bicep(VNet/Subnet/NSG) andmodules/vm.bicep(Public IP/NIC/Windows VM) with outputs. - Adds deployment/validation documentation plus parameter and
.gitignoreupdates to support local Bicep builds.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| infra/bicep/main.bicep | Subscription-scope orchestration that creates the RG and calls the network/VM modules. |
| infra/bicep/main.bicepparam | Example parameter values for demo deployment. |
| infra/bicep/modules/network.bicep | Creates VNet + subnet and an NSG with inbound allow rules; outputs subnet/NSG IDs. |
| infra/bicep/modules/vm.bicep | Creates Public IP + NIC + Windows VM and outputs VM name/IP/FQDN. |
| infra/bicep/README.md | Deployment, validation, usage, cost, and security documentation for the demo stack. |
| .gitignore | Ignores generated ARM JSON artifacts under infra/bicep/. |
| osProfile: { | ||
| computerName: vmName | ||
| adminUsername: adminUsername | ||
| adminPassword: adminPassword |
There was a problem hiding this comment.
For Windows VMs, osProfile.computerName must be <= 15 characters. vmName includes the prefix + "-vm-" + uniqueSuffix (13 chars), so it will typically exceed 15 and cause deployment failure. Use a shorter computerName (e.g., separate var truncated to 15) instead of vmName.
| subnets: [ | ||
| { | ||
| name: subnetName | ||
| properties: { | ||
| addressPrefix: subnetAddressPrefix | ||
| networkSecurityGroup: { | ||
| id: networkSecurityGroup.id | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
|
|
||
| // Outputs | ||
| @description('Virtual network name') | ||
| output vnetName string = virtualNetwork.name | ||
|
|
||
| @description('Subnet resource ID') | ||
| output subnetId string = virtualNetwork.properties.subnets[0].id |
There was a problem hiding this comment.
The subnetId output relies on virtualNetwork.properties.subnets[0], which is brittle if the subnet list changes (ordering, additional subnets). Prefer defining the subnet as an explicit child resource (or otherwise selecting by subnetName) and outputting that resource's .id.
| subnets: [ | |
| { | |
| name: subnetName | |
| properties: { | |
| addressPrefix: subnetAddressPrefix | |
| networkSecurityGroup: { | |
| id: networkSecurityGroup.id | |
| } | |
| } | |
| } | |
| ] | |
| } | |
| } | |
| // Outputs | |
| @description('Virtual network name') | |
| output vnetName string = virtualNetwork.name | |
| @description('Subnet resource ID') | |
| output subnetId string = virtualNetwork.properties.subnets[0].id | |
| } | |
| } | |
| // Subnet as explicit child resource | |
| resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-05-01' = { | |
| name: subnetName | |
| parent: virtualNetwork | |
| properties: { | |
| addressPrefix: subnetAddressPrefix | |
| networkSecurityGroup: { | |
| id: networkSecurityGroup.id | |
| } | |
| } | |
| } | |
| // Outputs | |
| @description('Virtual network name') | |
| output vnetName string = virtualNetwork.name | |
| @description('Subnet resource ID') | |
| output subnetId string = subnet.id |
| var vnetAddressPrefix = '10.0.0.0/16' | ||
| var subnetAddressPrefix = '10.0.0.0/24' | ||
|
|
||
| // Network Security Group with default rules |
There was a problem hiding this comment.
The comment says "Network Security Group with default rules", but this module defines explicit inbound allow rules (RDP/HTTP/HTTPS). Update the comment to avoid implying Azure's built-in default NSG rules are being used.
| // Network Security Group with default rules | |
| // Network Security Group with explicit inbound demo rules (RDP/HTTP/HTTPS) |
| // VM administrator password - MUST be provided at deployment time | ||
| // Use: az deployment sub create --parameters main.bicepparam --parameters adminPassword='YourSecurePassword123!' | ||
| // Or use: az deployment sub create --parameters main.bicepparam (will prompt for password) | ||
| param adminPassword = readEnvironmentVariable('ADMIN_PASSWORD', '') |
There was a problem hiding this comment.
adminPassword is set in the .bicepparam file with a default of empty string when ADMIN_PASSWORD isn't present. This prevents the CLI from prompting and will fail the template's @minLength(12) validation (and Azure password rules). Remove adminPassword from the parameter file (provide via CLI at deployment time), or make missing env var an explicit error rather than defaulting to ''.
| param adminPassword = readEnvironmentVariable('ADMIN_PASSWORD', '') |
| 1. **Get deployment outputs:** | ||
| ```bash | ||
| az deployment sub show \ | ||
| --name main \ |
There was a problem hiding this comment.
The docs show az deployment sub show --name main, but the earlier az deployment sub create examples don't specify --name main, so this command likely won't find the deployment. Either add --name main to the create/what-if/validate commands or update the "show outputs" step to use the actual deployment name.
| 1. **Get deployment outputs:** | |
| ```bash | |
| az deployment sub show \ | |
| --name main \ | |
| 1. **Get deployment outputs** (replace `<deploymentName>` with the name you used in `az deployment sub create`, for example `main`): | |
| ```bash | |
| az deployment sub show \ | |
| --name <deploymentName> \ |
Implements demo infrastructure for deploying a Windows Server VM with networking components in Azure using Bicep at subscription scope.
Structure
readEnvironmentVariable()for secure password handlingKey Decisions
Deployment Example
az deployment sub create \ --location swedencentral \ --template-file main.bicep \ --parameters main.bicepparam \ --parameters adminPassword='SecurePassword123!'Comprehensive deployment instructions, cost breakdown, and security notes added to
infra/bicep/README.md. Bicep build artifacts (*.json) excluded from version control via.gitignore.Original prompt