Add Azure Bicep IaC for Windows Server VM with networking infrastructure#13
Add Azure Bicep IaC for Windows Server VM with networking infrastructure#13
Conversation
- Create main.bicep at subscription scope with resource group - Add network modules (VNet, Subnet, NSG with RDP rule) - Add VM module (Windows Server 2022, Standard_B2s) - Add supporting modules (Public IP, NIC) - Create main.bicepparam with example parameters - Update README.md with comprehensive deployment instructions - Add *.json to .gitignore for Bicep build artifacts Co-authored-by: robpitcher <13648061+robpitcher@users.noreply.github.com>
- Update Public IP to Standard SKU (Basic deprecated as of Sept 2025) - Change Public IP allocation from Dynamic to Static for stability - Add rdpSourceAddressPrefix parameter for RDP source IP restriction - Update .gitignore to specifically target Bicep artifacts (infra/bicep/**/*.json) - Enhance README with RDP source IP restriction examples - Update security considerations documentation Co-authored-by: robpitcher <13648061+robpitcher@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a subscription-scoped Azure Bicep deployment under infra/bicep/ to provision a demo Windows Server VM in Sweden Central, including the supporting network stack (VNet/Subnet, NSG, NIC, Public IP) plus documentation and parameter examples.
Changes:
- Introduces
main.bicep(subscription scope) to create a resource group and deploy modular network/compute components. - Adds reusable Bicep modules for NSG, VNet/Subnet, Public IP, NIC, and VM.
- Adds deployment documentation (
infra/bicep/README.md), an example.bicepparam, and ignores compiled ARM JSON outputs via.gitignore.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| infra/bicep/main.bicep | Subscription-scope orchestrator that creates the RG and wires together network + VM modules. |
| infra/bicep/main.bicepparam | Example parameter values for demo deployments (no secrets). |
| infra/bicep/modules/nsg.bicep | NSG with parameterized RDP source restriction. |
| infra/bicep/modules/vnet.bicep | VNet + subnet creation and outputs consumed by NIC module. |
| infra/bicep/modules/publicip.bicep | Standard SKU, static Public IP for VM access. |
| infra/bicep/modules/nic.bicep | NIC connected to subnet and Public IP. |
| infra/bicep/modules/vm.bicep | Windows Server 2022 VM definition using the NIC. |
| infra/bicep/README.md | End-to-end deployment, security guidance, and architecture documentation. |
| .gitignore | Ignores compiled Bicep ARM JSON artifacts under infra/bicep/**/*.json. |
| │ │ │ │ │ | ||
| │ │ ┌───────────┴───────┐ │ │ | ||
| │ │ │ Public IP │ │ │ | ||
| │ │ │ (Dynamic) │ │ │ |
There was a problem hiding this comment.
The architecture diagram labels the Public IP as “(Dynamic)”, but the deployed Public IP resource is configured as Static allocation. Update the diagram to avoid contradicting the actual template and the later note about static allocation.
| │ │ │ (Dynamic) │ │ │ | |
| │ │ │ (Static) │ │ │ |
| Approximate monthly costs (as of 2026, Sweden Central region): | ||
| - VM (Standard_B2s): ~$30-40/month | ||
| - Managed Disk (Standard LRS 127GB): ~$5/month | ||
| - Public IP (Dynamic): ~$3/month |
There was a problem hiding this comment.
Cost section lists “Public IP (Dynamic)”, but the Public IP module uses Standard SKU with Static allocation. Align the cost breakdown wording with the actual configuration (and update any cost assumptions if they differ for Static).
| - Public IP (Dynamic): ~$3/month | |
| - Public IP (Standard, Static): ~$3/month |
| subnets: [ | ||
| { | ||
| name: subnetName | ||
| properties: { | ||
| addressPrefix: subnetAddressPrefix | ||
| networkSecurityGroup: { | ||
| id: nsgId | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
|
|
||
| output vnetId string = virtualNetwork.id | ||
| output vnetName string = virtualNetwork.name | ||
| output subnetId string = virtualNetwork.properties.subnets[0].id | ||
| output subnetName string = virtualNetwork.properties.subnets[0].name |
There was a problem hiding this comment.
subnetId/subnetName outputs are derived via virtualNetwork.properties.subnets[0], which is brittle if the module is ever extended to multiple subnets or the order changes. Prefer outputting by declaring the subnet as an explicit child resource (or otherwise referencing the subnet by name) so consumers don’t depend on array ordering.
| subnets: [ | |
| { | |
| name: subnetName | |
| properties: { | |
| addressPrefix: subnetAddressPrefix | |
| networkSecurityGroup: { | |
| id: nsgId | |
| } | |
| } | |
| } | |
| ] | |
| } | |
| } | |
| output vnetId string = virtualNetwork.id | |
| output vnetName string = virtualNetwork.name | |
| output subnetId string = virtualNetwork.properties.subnets[0].id | |
| output subnetName string = virtualNetwork.properties.subnets[0].name | |
| } | |
| // Subnet child resource within the virtual network | |
| resource subnet 'subnets' = { | |
| name: subnetName | |
| properties: { | |
| addressPrefix: subnetAddressPrefix | |
| networkSecurityGroup: { | |
| id: nsgId | |
| } | |
| } | |
| } | |
| } | |
| output vnetId string = virtualNetwork.id | |
| output vnetName string = virtualNetwork.name | |
| output subnetId string = subnet.id | |
| output subnetName string = subnet.name |
| osProfile: { | ||
| computerName: vmName | ||
| adminUsername: adminUsername | ||
| adminPassword: adminPassword |
There was a problem hiding this comment.
For Windows VMs, osProfile.computerName is limited to 15 characters (NetBIOS). Using vmName here will likely exceed that (e.g., with the uniqueString suffix) and cause deployment to fail. Use a separate, constrained computer name (e.g., parameter with @maxlength(15) or a shortened derived name) instead of reusing vmName.
Implements subscription-scoped Bicep deployment for Windows Server 2022 VM with complete networking stack in Sweden Central region.
Architecture
Key Implementation Details
rdpSourceAddressPrefixparameter enables CIDR-based source IP restriction for NSG rule;@secure()decorated password parameter never persisted in parameter filesuniqueString()suffix prevents collisions across deployments.gitignorepatterninfra/bicep/**/*.jsonexcludes ARM templates from version controlUsage
Deployment cost: ~$45/mo for demo configuration (Standard_B2s VM, Standard LRS disk).
Documentation includes prerequisites, deployment steps, security hardening recommendations, and architecture diagram.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.