Build a Governed Azure Sandbox

Use Bicep to govern an Azure resource group with tags, drift repair, and a lock.

Introduction

30 Second Summary

Clicking through settings can solve today's problem in seconds. Months later, that same change is hard to reproduce or prove to someone else.

In this project, you will use Bicep to build a governed Azure resource group from reusable code. Your finished GitHub portfolio entry will preserve proof of configuration drift repair plus deletion protection.

What You'll Build

You will demo a governed sandbox through three visible proofs: a repaired tag, a blocked deletion, and a GitHub evidence pack.

By the end of this project, you'll have:

  • A repeatable deployment that creates rg-az104-governance with visible Owner, Environment, and Workload tags.
  • A drift detection and repair workflow with screenshots showing a manual Production change, the What-if correction preview, and the restored Learning state.
  • Tested deletion protection that turns a blocked removal attempt into evidence that the CanNotDelete lock works.
  • Secret Mission: Assign a temporary Azure Policy that rejects an untagged resource group.

Are there any prerequisites?

You need an active Azure subscription with permission to create resource groups plus deployments at subscription scope. You also need a GitHub account plus Visual Studio Code on Windows.

Creating the management lock requires an Owner or User Access Administrator assignment.

Before We Start

Before any hands-on work, commit to the governance story this project must prove. You will build a repeatable subscription-scope Bicep baseline for rg-az104-governance, trigger configuration drift through its Environment tag, then publish the source plus What-If and delete-lock evidence in GitHub.

Set Up the Bicep Workspace

Your governance baseline needs an editor that understands Bicep before you can trust its syntax.

In this step, you will add Bicep support to Visual Studio Code. You will also connect the deployment pane to your Microsoft Azure subscription.

In this step, get ready to:
  • Install the Microsoft Bicep extension in Visual Studio Code.
  • Create the local Bicep workspace.
  • Connect the Deployment Pane to your Azure subscription.
Install the Microsoft Bicep extension

The Microsoft extension teaches Visual Studio Code how to recognize .bicep files. This gives you language support for the governance source you build later.

  • Press the Windows key to open Windows search.
  • Type Visual Studio Code into the search bar.
  • Press Enter to open Visual Studio Code.
  • Press Ctrl+Shift+X to open the Extensions view.
  • Enter Bicep in the Extensions search box.
  • Select Bicep published by Microsoft.
  • Select Install.

The Bicep extension now appears in your installed extensions. Your editor has the language tooling needed for the workspace.

Why use Bicep?

Bicep gives your Azure resources a reusable source of truth. The same file can be reviewed before each deployment.

This repeatable approach makes governance decisions visible. It also gives you an artifact that can be published later.

Can't install the extension?

Confirm that the search result is published by Microsoft. Check that Visual Studio Code can reach the Visual Studio Marketplace.

Help me troubleshoot the Microsoft Bicep extension installation. Share any environment-specific details with the NextWork community.

Create the local workspace

A workspace keeps the governance source beside its modules and evidence. You will place it inside az104-administrator-portfolio/01-governance on your Desktop.

  • Select File in the Visual Studio Code menu.
  • Select Open Folder....
  • Choose the Desktop folder.
  • Select New Folder.
  • Enter az104-administrator-portfolio as the folder name.
  • Press Enter to create the folder.

You will see the empty az104-administrator-portfolio folder on your Desktop.

  • Select the az104-administrator-portfolio folder.
  • Select New Folder.
  • Enter 01-governance as the folder name.
  • Press Enter to create the folder.
  • Select the 01-governance folder.
  • Select Select Folder to open the workspace.
  • Select Yes, I trust the authors if the Workspace Trust dialog appears.

The Explorer now shows 01-governance as your workspace. New project files will stay inside the portfolio structure.

  • Right-click 01-governance in the Explorer.
  • Select New Folder.
  • Enter evidence as the folder name.
  • Press Enter to create the folder.
  • Right-click 01-governance again.
  • Select New Folder.
  • Enter modules as the folder name.
  • Press Enter to create the folder.

You will see evidence and modules under 01-governance in the Explorer.

  • Right-click 01-governance in the Explorer.
  • Select New File.
  • Enter main.bicep as the file name.
  • Press Enter to create the file.
  • Set the deployment scope by entering the code below in main.bicep:
targetScope = 'subscription'

What does this declaration do?

  • The targetScope declaration defines where this Bicep file deploys resources.
  • The 'subscription' value prepares the file for resources managed at subscription scope.
  • Press Ctrl+S to save main.bicep.
  • Check the language mode in the lower-right corner of Visual Studio Code.

You will see Bicep in the lower-right corner. This confirms that the extension recognizes main.bicep.

File not recognized as Bicep?

Confirm that the file is named main.bicep. Check that the Microsoft Bicep extension still appears in your installed extensions.

Help me fix the Bicep language mode.

✔️ Awesome, I've got everything!

  • Keep main.bicep saved with the Bicep language mode visible.

ⓧ I'd like to double check the full code

  • Compare your complete main.bicep file with this reference:
targetScope = 'subscription'

Why this is the full file

This first version contains one deployment-scope declaration. The resource group definition arrives in the next step.

Connect the Deployment Pane to Azure

The Deployment Pane gives the Bicep file an authenticated Azure target. Selecting the subscription now prepares later validation without creating any resources.

Signing in connects the pane to your account. It does not deploy or modify the governance baseline.

  • Select the main.bicep editor tab.
  • Press Ctrl+Shift+P to open the Command Palette.
  • Choose Show Deployment Pane.
  • Complete the Microsoft Azure sign-in flow when prompted.
  • Select Pick Scope in the Deployment Pane.
  • Choose your target Azure subscription.

Why use the Deployment Pane?

The pane keeps Validate, What-if, and Deploy beside your Bicep source. You can preview later changes without adding an unverified command-line dependency.

Before you check the workspace, which editor signal and pane signal do you expect to confirm readiness?

  • Confirm that main.bicep remains open in the editor.
  • Check the lower-right language mode.
  • Check the selected target in the Deployment Pane.

You will see Bicep as the language mode. The Deployment Pane will show your selected Azure subscription.

Can't authenticate or select a subscription?

Confirm that you signed in with the account linked to your active Azure subscription. Check that the subscription appears in the Azure account you selected.

Help me troubleshoot Deployment Pane authentication. Share any account-specific details without posting subscription identifiers in the NextWork community.

That is the groundwork finished: your Bicep workspace is authenticated to its target subscription. Next up, you will turn main.bicep into the tagged governance baseline for rg-az104-governance.

Deploy the Tagged Governance Baseline

Your Visual Studio Code workspace now recognizes Bicep. The deployment pane is connected to your selected Azure subscription.

A visible resource group proves that the deployment path works. One manual change to its tags then exposes how quickly portal administration can move Azure away from the file.

In this step, get ready to:
  • Define the tagged resource group in main.bicep.
  • Validate the deployment before applying it.
  • Deploy the baseline before testing a manual tag change.
Declare the tagged resource group

Declarative infrastructure describes the state you want Azure to maintain. Azure Resource Manager reads that declaration to create the matching resource.

  • In main.bicep from earlier, place the cursor on the blank line below targetScope = 'subscription'.
  • Add the resource group parameters by pasting this block:
@description('Name of the governed resource group.')
param resourceGroupName string = 'rg-az104-governance'

@description('Portfolio owner recorded as a resource tag.')
param owner string

What do these parameters control?

  • The resourceGroupName parameter gives every deployment the repeatable default name rg-az104-governance.
  • The owner parameter requires a portfolio owner for the resource tag.
  • Each @description annotation explains the parameter inside the deployment pane.
  • Save main.bicep.
  • Check the editor for red underlines beneath either parameter.

You should see both parameter declarations without red underlines.

Are the parameters underlined?

  • Check that each description uses matching single quotes.
  • Confirm that param owner string has no default value.

Help me fix the Bicep parameter declarations in main.bicep.

  • Place the cursor on the blank line beneath param owner string.
  • Add the resource declaration plus its outputs by pasting this block:
resource governedResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = {
  name: resourceGroupName
  location: deployment().location
  tags: {
    Environment: 'Learning'
    Workload: 'AZ104Portfolio'
    Owner: owner
  }
}

output resourceGroupId string = governedResourceGroup.id
output deployedResourceGroupName string = governedResourceGroup.name

What does this resource declaration do?

  • The governedResourceGroup resource uses Microsoft.Resources/resourceGroups@2025-04-01 to define a subscription-level resource group.
  • The deployment().location expression uses the subscription deployment location.
  • The Environment tag classifies the group as Learning.
  • The Workload tag associates the group with AZ104Portfolio.
  • The Owner tag records the value supplied through the owner parameter.
  • The two outputs expose the deployed resource group's ID plus its name.
  • Save main.bicep.
  • Inspect the complete resource declaration for red underlines.

You should see a clean Bicep file with no syntax problems highlighted.

Is the resource block underlined?

  • Compare each opening brace with its closing brace.
  • Confirm that the resource type is Microsoft.Resources/resourceGroups@2025-04-01.
  • Check that the lower-right language mode still shows Bicep.

Help me diagnose the underlined Bicep resource declaration.

✔️ Awesome, I've got everything!

Good progress. Your main.bicep file now describes a tagged resource group plus its deployment outputs.

ⓧ I'd like to double check the full code

targetScope = 'subscription'

@description('Name of the governed resource group.')
param resourceGroupName string = 'rg-az104-governance'

@description('Portfolio owner recorded as a resource tag.')
param owner string

resource governedResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = {
  name: resourceGroupName
  location: deployment().location
  tags: {
    Environment: 'Learning'
    Workload: 'AZ104Portfolio'
    Owner: owner
  }
}

output resourceGroupId string = governedResourceGroup.id
output deployedResourceGroupName string = governedResourceGroup.name
Deploy through the Bicep pane

Validation checks whether Azure can accept the planned subscription deployment. What-If predicts the resource changes without applying them.

  • Switch back to the Bicep Deployment Pane from earlier.
  • Enter your name in the owner parameter field.
  • Select Validate.

You should see a successful validation result with no resources changed.

Did validation report an authorization problem?

Your account needs write access to the target resources. It also needs access to every operation under Microsoft.Resources/deployments/*.

Help me understand the Azure permission required for this validation.

Before you preview the deployment, which change do you expect Azure to propose from this file?

  • Preview the planned Azure change by selecting What-if.

You should see a proposed creation for rg-az104-governance. The preview leaves the subscription unchanged.

  • Capture the preview area with your Windows screenshot method.
  • Save the screenshot as evidence/01-what-if-create.png.
  • Apply the previewed baseline by selecting Deploy.

The deployment pane should report a successful deployment for rg-az104-governance.

You have crossed the first live milestone. Azure now has a resource group created from your Bicep source.

Did the deployment fail?

  • Confirm that the deployment pane still points to the selected subscription.
  • Check that the owner parameter contains your name.
  • Ask your subscription administrator to confirm your subscription-scope deployment access.

Help me troubleshoot the failed subscription deployment.

Create intentional configuration drift

The Azure portal gives you an independent view of the live resource group. A mismatch there becomes visible evidence that manual changes can move Azure away from its declared source.

  • Open the Azure portal in your browser.
  • Sign in with the Azure account used in the deployment pane if prompted.
  • Search for rg-az104-governance using the portal search bar.
  • Select rg-az104-governance from the search results.
  • Select Tags from the resource group menu.

What should I see?

  • The Environment tag shows Learning.
  • The Workload tag shows AZ104Portfolio.
  • The Owner tag displays your name.

Before you apply the edit, which value do you think the portal will show immediately after the save?

  • Select the Learning value beside Environment.
  • Replace the current value with Production.
  • Apply the update using the page's save control.

You should now see Environment = Production on the Tags page. main.bicep still declares Environment: 'Learning'.

This mismatch is the intended shortfall. The live resource group has drifted from its source.

Your baseline is live with an intentional mismatch in its tags. Next up, you will make that drift visible before restoring the declared state.

Detect and Repair Configuration Drift

Your resource group is live. Its Environment tag now says Production in the portal while main.bicep still declares Learning.

This mismatch is configuration drift. You will use Azure Resource Manager What-If to expose it before any change reaches Azure.

You will then redeploy the unchanged Bicep file. Azure will return to the declared state.

In this step, get ready to:
  • Preview the changed Environment tag with What-If.
  • Save the drift preview as portfolio evidence.
  • Redeploy the unchanged baseline to restore Learning.
Preview the drift with What-If

A What-If operation compares your declared Bicep state with the live Azure state. It predicts modifications without applying them.

  • Switch back to Visual Studio Code.
  • Select the main.bicep editor tab.
  • Locate the tags block.
  • Confirm the block still declares Environment as Learning.
  • Return to the Bicep Deployment Pane from earlier.

Before you run the preview, do you expect Azure to keep Production or propose Learning?

  • Select What-if in the Bicep Deployment Pane.

You will see a predicted modification to the Environment tag. The preview shows Azure returning the live value from Production to Learning.

No Tag Modification in the Preview?

  • Switch back to the Azure portal.
  • Confirm the live Environment tag still shows Production.
  • Return to main.bicep.
  • Confirm the source still declares Environment as Learning.

Help me troubleshoot why Bicep What-If does not show my tag drift.

Repair the drift from source

The What-If result is temporary evidence of the mismatch. Your screenshot preserves that proof after deployment restores the tag.

  • Capture a screenshot of the What-If preview showing the predicted Environment tag modification.
  • Save the screenshot inside the evidence folder as evidence/02-drift-detected.png.
  • Double-check main.bicep against the reference before redeploying:

✔️ Awesome, I've got everything!

Your unchanged main.bicep is ready to restore the declared baseline.

ⓧ I'd like to double check the full code

targetScope = 'subscription'

@description('Name of the governed resource group.')
param resourceGroupName string = 'rg-az104-governance'

@description('Portfolio owner recorded as a resource tag.')
param owner string

resource governedResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = {
  name: resourceGroupName
  location: deployment().location
  tags: {
    Environment: 'Learning'
    Workload: 'AZ104Portfolio'
    Owner: owner
  }
}

output resourceGroupId string = governedResourceGroup.id
output deployedResourceGroupName string = governedResourceGroup.name

Why Does the File Stay Unchanged?

The file already declares Learning as the desired Environment value. What-If detects the difference between that declaration and the live Production tag.

Deploy applies the declaration again. The same source repairs the drift without a code change.

  • Select Deploy in the Bicep Deployment Pane.
  • Reuse your owner value if the pane requests it.
  • Wait for the deployment to finish.

The deployment completes successfully. Azure has reapplied the state declared in main.bicep.

Deployment Rejected?

  • Confirm the Deployment Pane still targets the subscription from the previous step.
  • Ask your Azure administrator to confirm your account has subscription deployment permissions.
  • Reuse the same owner value that created the original resource group.

Help me troubleshoot my subscription-scope Bicep redeployment.

Confirm the restored tag

An idempotent deployment reapplies the same declaration until the live resource matches it. The resource group's Tags page provides the final proof.

Before you refresh the Tags page, which Environment value do you expect Azure to show?

  • Switch back to the rg-az104-governance resource group page in the Azure portal.
  • Select Tags.
  • Refresh the page.

You will see Environment set to Learning again. The live resource now matches the Bicep declaration.

You just closed the drift loop. Your deployment can now expose a manual change before restoring the governed baseline.

Your declared baseline can now detect manual drift and restore the intended state. Next up, you will protect the sandbox from accidental deletion with a management lock.

Protect the Sandbox with a Delete Lock

You restored Environment to Learning by redeploying its Bicep source. The resource group still faces a second risk: an authorized administrator can delete the whole sandbox.

An Azure management lock adds a guardrail to the control plane at the resource group scope. The CanNotDelete level allows authorized changes while blocking deletion.

In this step, get ready to:
  • Define the management lock in a reusable Bicep module.
  • Deploy the lock module after reviewing its What-If preview.
  • Test the protection by capturing a blocked deletion attempt.
Create the delete lock module

A Bicep module keeps the lock definition separate from the subscription-level resource group declaration. The parent file can deploy that control directly at the group scope.

  • In Visual Studio Code's Explorer sidebar, create modules/resource-group-controls.bicep inside the modules folder using the New File control.
  • Define the deletion protection by pasting this code into modules/resource-group-controls.bicep:
resource deleteLock 'Microsoft.Authorization/locks@2020-05-01' = {
  name: 'protect-from-accidental-deletion'
  properties: {
    level: 'CanNotDelete'
    notes: 'Protects the AZ-104 portfolio resource group from accidental deletion.'
  }
}

What does this module do?

  • The deleteLock resource declares a management lock that Azure can deploy at resource group scope.
  • The CanNotDelete level permits authorized updates while preventing deletion.
  • The lock name provides a recognizable control for testing now and cleanup later.
  • The notes record why the protection exists.
  • Save modules/resource-group-controls.bicep.
  • Confirm that resource-group-controls.bicep appears inside the modules folder in the Explorer sidebar.

That is the reusable control ready: modules/resource-group-controls.bicep now holds the delete protection.

Is the module showing a problem?

  • Make sure the file sits inside the existing modules folder.
  • Check that the resource begins with the exact identifier deleteLock.
  • Confirm that every opening brace has a matching closing brace.

Help me debug my Bicep lock module.

Your module file should now match one of these outcomes.

✔️ Awesome, I've got everything!

Your lock module is saved in the correct folder.

ⓧ I'd like to double check the full code

resource deleteLock 'Microsoft.Authorization/locks@2020-05-01' = {
  name: 'protect-from-accidental-deletion'
  properties: {
    level: 'CanNotDelete'
    notes: 'Protects the AZ-104 portfolio resource group from accidental deletion.'
  }
}

What should match?

This reference contains the complete module file. Its resource name, lock level, notes, braces, and indentation should match your saved file.

Wire the lock into the subscription deployment

The subscription-scoped parent file must call the module at the governed resource group's scope. This connection makes the lock part of the same repeatable deployment as the tags.

  • In main.bicep, find output resourceGroupId string = governedResourceGroup.id.
  • Add the module block directly above that output by pasting this code:
module resourceGroupControls './modules/resource-group-controls.bicep' = {
  name: 'resourceGroupControls'
  scope: resourceGroup(governedResourceGroup.name)
}

What does this module block do?

  • The module path points the parent deployment to modules/resource-group-controls.bicep.
  • The resourceGroupControls name identifies the nested deployment in Azure.
  • The resourceGroup(governedResourceGroup.name) expression targets the resource group created by the parent file.
  • Save main.bicep.

Before you select What-if, picture the operation you expect Azure to show for the new control.

  • In the Bicep Deployment Pane from earlier, select What-if.

The preview should show planned creation of the protect-from-accidental-deletion lock under rg-az104-governance.

  • Select Deploy in the Deployment Pane.
  • Wait for the Deployment Pane to report that the deployment completed successfully.

That guardrail is now live: rg-az104-governance has Bicep-managed deletion protection.

Is the lock deployment failing?

Confirm that the Deployment Pane still targets the subscription used for rg-az104-governance.

Lock creation requires Microsoft.Authorization/locks/* access. An Owner or User Access Administrator assignment includes that access.

Help me troubleshoot my lock deployment.

Your parent deployment should now match one of these outcomes.

✔️ Awesome, I've got everything!

Your subscription deployment now calls the resource group controls module.

ⓧ I'd like to double check the full code

targetScope = 'subscription'

@description('Name of the governed resource group.')
param resourceGroupName string = 'rg-az104-governance'

@description('Portfolio owner recorded as a resource tag.')
param owner string

resource governedResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = {
  name: resourceGroupName
  location: deployment().location
  tags: {
    Environment: 'Learning'
    Workload: 'AZ104Portfolio'
    Owner: owner
  }
}

module resourceGroupControls './modules/resource-group-controls.bicep' = {
  name: 'resourceGroupControls'
  scope: resourceGroup(governedResourceGroup.name)
}

output resourceGroupId string = governedResourceGroup.id
output deployedResourceGroupName string = governedResourceGroup.name

What should match?

This reference contains the complete main.bicep file for this step. The module block should sit between the resource group declaration and the two outputs.

Test deletion protection

Management locks govern control-plane operations such as resource group deletion. A blocked deletion attempt provides visible proof that the new guardrail works.

This deletion test can feel risky. The deployed lock is the control under test, so follow the confirmation flow only once.

Before you submit the deletion request, picture whether Azure will accept it or reject it.

  • Return to the Azure portal tab that shows rg-az104-governance.
  • Select Delete resource group.
  • Enter rg-az104-governance when Azure asks for the resource group name.
  • Submit the deletion confirmation.

Azure blocks the deletion because protect-from-accidental-deletion has the CanNotDelete level. Your sandbox has now proved that its control-plane guardrail works.

  • Capture the blocked result using the same screenshot method as your earlier evidence.
  • Save the screenshot as evidence/03-lock-blocked-delete.png.
  • Refresh the portal page for rg-az104-governance.

You should still see rg-az104-governance with Environment = Learning, Workload = AZ104Portfolio, and your original Owner value.

Your delete protection has passed a real control-plane test. Next, you'll publish the Bicep source and evidence as a GitHub governance portfolio entry.

Publish the Governance Evidence Pack

Your Bicep deployment now manages a delete lock on rg-az104-governance. The failed deletion proves the guardrail works.

Azure resources are temporary. A structured GitHub repository preserves that proof after cleanup.

In this step, get ready to:
  • Document the governance workflow in README.md.
  • Create the az104-administrator-portfolio GitHub repository.
  • Upload the complete 01-governance folder as a permanent evidence pack.
Write the portfolio README

A portfolio README turns source files into an administrator story. It connects each control to the problem it solves.

  • In Visual Studio Code, use the new-file icon in the file sidebar for 01-governance.
  • Name the file README.md.

You will see README.md beside main.bicep in the local folder.

  • Build the scenario and governance sections by pasting this content into README.md:
# AZ-104 Project 1: Governed Azure Sandbox

## Scenario

A cloud administrator needs a repeatable landing area for training resources. The resource group must carry ownership and environment metadata, recover from manual configuration drift, and resist accidental deletion.

## What this demonstrates

- Subscription-scope Azure Resource Manager deployment with Bicep
- Consistent resource-group tagging
- Validate and What-If before deployment
- Drift detection and repair
- Resource-group protection with a delete lock
- Evidence-driven operational documentation

## Architecture

Visual Studio Code + Bicep -> Azure Resource Manager subscription deployment -> tagged resource group -> resource-group module -> delete lock

## Governance controls

| Control | Implementation | Demonstrated outcome |
| --- | --- | --- |
| Ownership | `Owner` tag | The responsible portfolio owner is visible in Azure |
| Environment classification | `Environment = Learning` | The sandbox is separated from production resources |
| Workload classification | `Workload = AZ104Portfolio` | Related projects can be filtered consistently |
| Drift management | Bicep What-If and redeployment | A manual tag change is detected and restored |
| Deletion protection | `CanNotDelete` lock | A resource-group deletion attempt is blocked |

What does this section do?

  • The scenario frames the sandbox as a repeatable administration task.
  • The architecture line maps the deployment path from the editor to the delete lock.
  • The governance table links every control to an observable outcome.
  • Save README.md.
  • Press Ctrl+Shift+V to render the Markdown preview.

You will see a rendered scenario followed by an architecture flow. The governance controls will appear as a five-row table.

Preview missing sections?

Check that README.md sits directly inside 01-governance. Make sure each heading starts with the same number of hash characters shown above.

Help me fix my Markdown preview

The second half connects your screenshots to the controls they prove. It also records the cost boundary before someone reuses the deployment.

  • Return to the README.md editor.
  • Complete the evidence pack by pasting this content beneath the governance controls table:
## Evidence

| Evidence | What it proves |
| --- | --- |
| ![Initial What-If result](evidence/01-what-if-create.png) | Azure previewed the resource-group creation before deployment |
| ![Drift detected](evidence/02-drift-detected.png) | What-If detected a portal change to the declared tag state |
| ![Delete blocked by lock](evidence/03-lock-blocked-delete.png) | The management lock prevented accidental deletion |

## AZ-104 skills mapped

- Manage resource groups and subscriptions
- Apply and manage tags
- Configure resource locks
- Implement and manage Azure Policy in the Secret Mission
- Interpret, modify, and deploy a Bicep file

## Cost boundary

This project deploys governance metadata and control-plane resources only. It does not deploy compute, storage, networking, or paid monitoring resources.

## Cleanup

Remove the `protect-from-accidental-deletion` lock before deleting `rg-az104-governance`. Keep this repository as the permanent portfolio artifact.

## Reflection

The portal is useful for inspection and controlled tests, while Bicep remains the source of truth. What-If makes drift visible before deployment, and the delete lock protects the environment from destructive control-plane operations.

How does the evidence pack work?

  • The relative image paths point to screenshots inside the evidence folder.
  • The cost boundary separates governance controls from metered workloads.
  • The cleanup section records the lock removal required before resource group deletion.
  • Save README.md.
  • Press Ctrl+Shift+V to refresh the rendered preview.

You will see three evidence rows followed by the skills mapping. The cost boundary and cleanup instructions will appear near the bottom.

Evidence images not rendering?

Check that all three image files remain inside evidence. Compare each filename with the matching path in README.md.

Help me repair my evidence links

✔️ Awesome, I've got everything!

Your README.md now documents the complete governance workflow.

ⓧ I'd like to double check the full code

# AZ-104 Project 1: Governed Azure Sandbox

## Scenario

A cloud administrator needs a repeatable landing area for training resources. The resource group must carry ownership and environment metadata, recover from manual configuration drift, and resist accidental deletion.

## What this demonstrates

- Subscription-scope Azure Resource Manager deployment with Bicep
- Consistent resource-group tagging
- Validate and What-If before deployment
- Drift detection and repair
- Resource-group protection with a delete lock
- Evidence-driven operational documentation

## Architecture

Visual Studio Code + Bicep -> Azure Resource Manager subscription deployment -> tagged resource group -> resource-group module -> delete lock

## Governance controls

| Control | Implementation | Demonstrated outcome |
| --- | --- | --- |
| Ownership | `Owner` tag | The responsible portfolio owner is visible in Azure |
| Environment classification | `Environment = Learning` | The sandbox is separated from production resources |
| Workload classification | `Workload = AZ104Portfolio` | Related projects can be filtered consistently |
| Drift management | Bicep What-If and redeployment | A manual tag change is detected and restored |
| Deletion protection | `CanNotDelete` lock | A resource-group deletion attempt is blocked |

## Evidence

| Evidence | What it proves |
| --- | --- |
| ![Initial What-If result](evidence/01-what-if-create.png) | Azure previewed the resource-group creation before deployment |
| ![Drift detected](evidence/02-drift-detected.png) | What-If detected a portal change to the declared tag state |
| ![Delete blocked by lock](evidence/03-lock-blocked-delete.png) | The management lock prevented accidental deletion |

## AZ-104 skills mapped

- Manage resource groups and subscriptions
- Apply and manage tags
- Configure resource locks
- Implement and manage Azure Policy in the Secret Mission
- Interpret, modify, and deploy a Bicep file

## Cost boundary

This project deploys governance metadata and control-plane resources only. It does not deploy compute, storage, networking, or paid monitoring resources.

## Cleanup

Remove the `protect-from-accidental-deletion` lock before deleting `rg-az104-governance`. Keep this repository as the permanent portfolio artifact.

## Reflection

The portal is useful for inspection and controlled tests, while Bicep remains the source of truth. What-If makes drift visible before deployment, and the delete lock protects the environment from destructive control-plane operations.
Create the portfolio repository

The root README gives your portfolio repository a landing page. The governance project will sit inside it as the first documented entry.

  • In your browser, go to GitHub.
  • From the repository menu, select New repository.

GitHub will display the repository creation form.

  • Enter az104-administrator-portfolio as the repository name.
  • Choose your preferred repository visibility.

The form will show the repository name under your GitHub account.

  • Turn on Add README.
  • Select Create repository.

You have the permanent evidence home in place. GitHub now shows the root README beneath the repository file list.

Repository name already in use?

Return to an existing az104-administrator-portfolio repository only when it is the portfolio you intend to update. Check that its current files are safe to keep before uploading this project.

Help me resolve my repository setup

Upload and verify the evidence pack

The browser upload keeps the project accessible without a local Git installation. Preserving the folder structure also keeps every relative evidence link intact.

  • From the repository page, select Add file.
  • Select Upload files.

GitHub will display an upload area for the project folder.

  • Use File Explorer to locate the local 01-governance folder from earlier.
  • Drag the 01-governance folder into the GitHub upload area.

GitHub will list the files while preserving the project folder and its nested directories.

  • Confirm that README.md appears in the upload list.
  • Confirm that main.bicep appears in the upload list.
  • Confirm that modules/resource-group-controls.bicep appears in the upload list.
  • Confirm that all three images from evidence appear in the upload list.
  • Enter Add governance evidence pack as the commit message.
  • Select Propose changes.

GitHub will return to the repository after it records the uploaded folder.

Pause for one prediction: will all three relative image links resolve from the uploaded folder.

  • Select the 01-governance folder in the repository.
  • Select README.md to view its rendered content.

GitHub will render the project narrative beneath the file name.

  • Inspect the evidence table for all three screenshots.
  • Scan the rendered document for subscription identifiers.

What should I see?

  • The Architecture section shows the path from Visual Studio Code to the delete lock.
  • The Governance controls table shows five tested controls.
  • The AZ-104 skills mapped section connects the project to administrator tasks.
  • The Evidence table loads all three screenshots.
  • The Cost boundary section confirms that the project contains no metered workload.
  • The Cleanup section explains how to remove the protected resource group safely.

You should find no subscription identifiers anywhere in the document.

Uploaded README not rendering correctly?

Check that README.md is inside 01-governance. Check that the screenshots remain inside the nested evidence folder.

Help me debug my uploaded evidence pack

That completes your first governance portfolio entry. Your reusable source and tested evidence now remain available after the Azure sandbox is removed.

Secret mission

Enforce Tags with Azure Policy

Extend your governance sandbox with a temporary subscription-wide rule. You will use Azure Policy to deny an untagged test resource group. You will prove that the required tag allows creation before removing every temporary resource.

Clean Up Your Resources

Clean Up Your Resources

Azure governance services are free for Azure subscribers. Choose whether to keep the sandbox, leave it idle for later, or remove the Azure resources.

Resources you used:

  • The rg-az104-governance Azure resource group with its declared tags.
  • The protect-from-accidental-deletion CanNotDelete management lock managed by Bicep.

The temporary policy assignment is already removed. The rg-az104-policy-test resource group is also removed.

Keep everything running

No action is needed. Choose this if you want the governed sandbox available for future practice.

  • Leave rg-az104-governance deployed with its declared tags.
  • Keep the protect-from-accidental-deletion lock attached.
  • Retain the published GitHub repository as your permanent evidence pack.

Pause - I'll come back to this later

There is no running workload to pause. The temporary Secret Mission policy assignment is already removed.

  • Leave rg-az104-governance idle in your Azure subscription.
  • Keep the protect-from-accidental-deletion lock attached for deletion protection.
  • Retain the az104-administrator-portfolio repository for your next portfolio project.

Delete - I don't want to use this again

Deleting the Azure sandbox is permanent. Your source files and screenshots remain in GitHub as proof of your work.

  • Return to the rg-az104-governance page in the Azure portal.
  • Select Locks in the left menu.
  • Remove protect-from-accidental-deletion with the deletion control in its row.
  • Select Delete resource group on the resource group page.
  • Enter rg-az104-governance in the confirmation field.
  • Confirm the deletion.
  • Return to the resource-group list to check whether rg-az104-governance remains.
  • Confirm that rg-az104-governance no longer appears in the list.

That closes the Azure sandbox cleanly. Your published evidence remains available in az104-administrator-portfolio.

Nice Work!

Nice Work!

Excellent work! You built a governed Azure sandbox from reusable Bicep source.

You've learned how to:

  • Created a subscription-scoped deployment that produces rg-az104-governance with declared governance tags.
  • Used What-If to expose configuration drift after a portal tag edit. Restored the Environment tag through idempotent redeployment.
  • Tested a CanNotDelete management lock through an intentional deletion attempt. Published the blocked result inside your GitHub evidence pack.
  • Secret Mission: Enforced the Environment tag with a temporary Azure Policy assignment. Confirmed that an untagged rg-az104-policy-test was denied. Confirmed that Environment = Learning allowed the test group to be created. Removed the temporary assignment after testing.

Ready to quiz yourself?