Azure Labs: Built Twice, Portal Then Bicep
Hands-on Azure labs, each built twice: by hand in the portal to see what the service really does, then from scratch as Bicep and compared. Lab 01 tested four Azure Policy rules and a delete lock until they broke, then rebuilt them as code in ten seconds. Lab 02, storage access tiers, is running.
Follow four requests through the rules.
- Request
- P1✓Modify
- P2✓Deny
- ARMActivity log
- t+0.0sRequestCreate a virtual network with no tags.
- t+0.4sP1Adds costCenter, inherited from the resource group.
- t+0.8sP2Checks for costCenter, and finds the one P1 just added.
- t+1.2sARMRecords the order on the request: Modify, then the deny check.
Created. Modify runs before Deny, so the rule that adds wins.
- P1Do not enforce
- Request
- P2✕Deny
- t+0.0sP1Switched off, nothing else changed.
- t+0.4sRequestThe same untagged network.
- t+0.8sP2No costCenter. RequestDisallowedByPolicy.
Refused. Proven both ways, so the first result was the order of evaluation, not luck.
- RemediationP1
- Update
- P3✕Deny
- t+0.0sRemediationA task sets out to add costCenter to a network that predates the rules.
- t+0.4sUpdateIt does that by sending an ordinary update.
- t+0.8sP3The network has no owner tag, so the update is refused. PolicyRemediationFailure.
A remediation is just another update, and every Deny rule still checks it.
- Ownerfull rights
- Lock✕CanNotDelete
- t+0.0sOwnerDeletes a network in the locked resource group.
- t+0.4sLockRefused, whatever the role.
A lock beats permissions. It stops deletion and nothing else.
Why build everything twice
Clicking through the portal is the fastest way to learn what a setting does. It is also impossible to repeat exactly, review, or keep in Git. Code is the opposite: repeatable and reviewable, but easy to write without understanding what it creates.
So every lab here is built twice. First by hand, with predictions written down before anything is created and something broken on purpose. Then from scratch as Bicep, deployed to a clean resource group, and compared with what the portal built. The differences between the two are usually where the lesson is.
Each lab follows the same five steps: build it in the portal, break something deliberately, export what Azure created, write the Bicep and deploy it, then write up the findings and tear everything down. Every resource carries environment, purpose, lab and autoDelete tags, and a script checks for anything left running.
Lab 01: governance and tagging
Three questions, each with a prediction written before the build:
| Question | Prediction | What happened |
|---|---|---|
| Do resource group tags flow down to the resources inside? | No | Correct. A network created in a group with six tags carried none. Inheritance takes a policy, one per tag name. |
| What happens to resources that existed before a policy? | Left alone | Half right. Not changed, but reported non-compliant within minutes, and fixed only by a remediation task. |
| If one policy adds a tag and another denies resources missing it, which wins? | The one that adds | Correct, and proven both ways. Modify runs before Deny; with the Modify rule switched off, the Deny rule refused the same request. |
Four built-in policies made the test: P1 inherits costCenter from the group (Modify), P2 requires costCenter (Deny), P3 requires owner (Deny), and P4 allows East US only, assigned to the whole subscription. A CanNotDelete lock sat on the group.
Broken on purpose
| Attempt | Result | Refused by |
|---|---|---|
| A network with no tags, P1 and P2 on | Created, costCenter added | nothing |
| The same, P1 switched to Do not enforce | Refused | P2 |
A network with no owner tag | Refused | P3 |
| A network in West US | Refused | P4 |
| A remediation task fixing an old network | Failed | P3, because a remediation is just another update |
| Adding a tag to a network with no owner | Refused | P3, not the lock |
| Deleting a network under the lock | Refused | the lock |
The remediation failure was the finding worth the lab. A remediation task applies a Modify rule by sending an ordinary update, and every Deny rule still checks that update. Adding the missing owner tag by hand let P1 add costCenter in the same request.
Rebuilt as code
The portal export was the first lesson: it held only the networks. Policy assignments and locks are extension resources, attached to the group rather than in it, so an exported template is not a backup of a group’s governance.
The Bicep rebuild deploys at subscription scope: the group and P4 from main.bicep, everything inside the group from a module. Built-in policy and role IDs are looked up, never remembered, and looking them up caught a wrong one. what-if listed eight creates and no changes; the deployment took ten seconds. Three things only the code build showed:
- Rules and resources in one deployment race. A network created seconds after its rules came out untagged and unrefused, because neither rule had taken effect yet. Rules belong in their own deployment, before the resources they govern.
- Tag Contributor is enough. The portal quietly gave P1’s managed identity Contributor. In code the role assignment is its own resource, and the narrow Tag Contributor role did the job, proven by the Activity log.
- Redeploying re-runs Modify. A second deployment sent the network’s tags as owner only, and P1 put
costCenterback inside that same request.
Lab 02, running: storage access tiers
The second lab asks what hot, cool, cold and archive storage really cost to keep and to read back, what it takes to bring an archived file back online, what moving a file to a colder tier too early costs, and, added mid-lab after noticing the portal quietly uses the account key, who can still read the files once the account’s keys are switched off. Predictions are written; the build is under way.
What is next
Twenty labs in three groups. Foundations: storage tiers, a virtual machine deep dive, a custom RBAC role, Entra ID users and groups at scale, Azure Files. Composition: a three-tier network, hub-and-spoke with Bastion, load balancer failover, monitoring and alerts, backup and restore, Key Vault with managed identity, VNet-to-VNet VPN, App Service slots, private endpoints. Applied: a Bicep CI/CD pipeline, a Defender for Cloud baseline, a cost guardrail, Conditional Access with PIM, and a full environment built and destroyed from one template.
What it looks like when it is actually configured.
Screenshots from the build, named and captioned so each one can be traced to the configuration it proves.








