Deploy a Governed Azure Environment
Build a governed Azure environment covering all five AZ-104 exam domains.
Introduction
30 Second Summary
Every company running in the cloud needs someone who can set up secure access, enforce rules, build networks, and connect services together. These are the exact skills that Azure administrators use every day.
In this project, you will deploy a fully governed Azure environment from scratch. You will configure identity and governance controls, build a secured network, launch a virtual machine, and connect it to cloud storage using a managed identity.
What You'll Build
A production-style Azure environment that mirrors real-world administration tasks tested on the AZ-104 exam, covering all five exam domains: identity, governance, networking, compute, and storage.
By the end of this project, you'll have:
- A resource group with enforced tagging governance via Azure Policy.
- A Virtual Network with subnets and a Network Security Group, plus a Linux Virtual Machine deployed inside it.
- A Storage Account with a blob container accessible from the VM using a managed identity.
- Secret Mission: Configure Azure Monitor to track VM metrics and set up an alert rule that fires when CPU exceeds 80%.
Are there any prerequisites?
You need a Windows PC and an Azure free account (you can create one today). The Azure free account provides $200 credit for 30 days, which is more than enough to complete this project at no cost.
Set Up Azure CLI and Sign In
Every resource you create in this project is managed through the Azure CLI. It is your command-line interface to Azure, letting you create, configure, and tear down cloud resources without clicking through the portal.
In this step, you will install Azure CLI on your Windows machine, authenticate with your Azure account, and set a default region for all your resources.
In this step, get ready to:
- Install Azure CLI using WinGet.
- Sign in to your Azure account.
- Set a default location for all resources.
Install Azure CLI on Windows
Azure CLI is a cross-platform tool that lets you manage Azure resources from your terminal. You will install it using WinGet, the built-in Windows package manager.
- Press the Windows key to open your search bar.
- Type PowerShell and look for Windows PowerShell in the results.
- Right-click Windows PowerShell and select Run as administrator.
- If prompted with a User Account Control dialog, click Yes to allow it.
Why run as administrator?
WinGet needs administrator privileges to install system-wide software like Azure CLI. After installation, you can use a regular PowerShell window for the remaining commands.
- Install Azure CLI by running this command:
winget install --exact --id Microsoft.AzureCLI
What does this command do?
The --exact flag ensures WinGet matches the package ID precisely. The --id flag targets the package by its unique identifier, Microsoft.AzureCLI.
- Wait for the installation to complete. You should see a message confirming the install was successful.
- Close your PowerShell window completely.
- Open a new PowerShell window. Press the Windows key, type PowerShell, and press Enter. You do not need administrator mode this time.
Why close and reopen?
The installer adds Azure CLI to your system PATH, but your current terminal session does not pick up PATH changes. Opening a fresh window loads the updated PATH so the az command is recognized.
- Verify the installation by running this command:
az version
Check the version number in the output. You need version 2.87.0 or later.
✔️ I see version 2.87.0 or later
Azure CLI is installed and ready. Move on to the next substep.
ⓧ I see an older version
You have an older version of Azure CLI installed. Upgrade it by running this command in your PowerShell (as administrator):
winget upgrade --exact --id Microsoft.AzureCLI
- Close and reopen PowerShell after the upgrade completes.
- Run az version again to confirm it now shows 2.87.0 or later.
ⓧ Command not found
PowerShell does not recognize the az command. Try these fixes:
- Make sure you closed and reopened PowerShell after the installation.
- Confirm the install succeeded by running winget list --id Microsoft.AzureCLI. If it appears in the list, the install worked but your PATH is not updated. Try restarting your computer.
- If it does not appear, re-run the install command from an Administrator PowerShell window.
Azure CLI installs to C:\Program Files\Microsoft SDKs\Azure\CLI2 on 64-bit Windows. If the folder exists but the command is not found, your PATH needs a restart to refresh.
Sign in to your Azure account
Now that Azure CLI is installed, you need to authenticate so it can manage resources in your subscription.
- Sign in to Azure by running this command:
az login
What happens when you run this?
A browser window opens asking you to sign in with your Microsoft account. After you authenticate, the browser confirms success and you can return to PowerShell.
If you have multiple subscriptions, Azure CLI prompts you to select one. Press Enter to accept the default, or type the number of your free-account subscription.
- After signing in, confirm your subscription is active by running:
az account show
You should see JSON output containing your subscription name and ID. Note down the id value. You will need this subscription ID in later steps.
Browser did not open?
If no browser window appeared, check that your default browser is set in Windows Settings. You can also copy the URL shown in the terminal and paste it into a browser manually.
If you see an authentication error, make sure you are signing in with the same Microsoft account that has the Azure free subscription.
Help me troubleshoot my az login issue.
Set a default location for all resources
Most Azure commands require a --location flag to specify the datacenter region. Setting a default saves you from repeating it on every command.
- Set eastus as your default location by running:
az configure --defaults location=eastus
Why eastus?
East US is one of the largest Azure regions with broad service availability. It is a common choice for lab environments and keeps all your resources geographically close for low latency.
You will not see any output from this command. That is expected. The default is now saved.
✔️ Awesome, I've got everything!
Great. Double check that you have az version returning 2.87.0+ and az account show displaying your subscription.
ⓧ I'd like to double check my setup
Run these two commands in order to confirm everything is working:
az version
az account show
The first command should show 2.87.0 or higher. The second should display your subscription name, ID, and state as Enabled.
Your environment is ready. Next up, you will create your first resource group, set up identity governance with a policy, and assign an RBAC role.
Configure Identity, Governance, and Policy
Your Azure CLI is installed and authenticated. Now it is time to build the organizational foundation for your environment.
Every well-governed Azure deployment starts with identity and policy controls. In this step, you will create a resource group, set up a user with scoped permissions, and assign a policy that enforces tagging on all future resource groups.
In this step, get ready to:
- Create a resource group with enforced tags.
- Set up a Microsoft Entra ID user with a scoped RBAC role.
- Assign an Azure Policy that denies untagged resource groups.
Create a resource group and Entra ID user
Before running the commands in this substep, you need two pieces of information unique to your account: your subscription ID and your tenant domain name.
- Retrieve your subscription ID by running this command:
az account show --query id --output tsv
- Copy the output and save it somewhere you can reference easily. This is your subscription ID.
How do I find my tenant domain?
Your tenant domain is the .onmicrosoft.com name you chose (or was auto-generated) when you created your Azure account. You can find it in the Azure Portal by navigating to Microsoft Entra ID and looking at the Primary domain on the Overview page.
For example, if your primary domain is contoso.onmicrosoft.com, then your tenant name is contoso.
With your subscription ID and tenant domain noted, you are ready to create resources.
- Create a resource group with tags by running this command:
az group create --name az104-lab-rg --tags Environment=Lab Project=AZ104
What does this command do?
- Creates a resource group named az104-lab-rg in your default location (eastus).
- Applies two tags: Environment=Lab and Project=AZ104. Tags help you organize and track resources.
- Confirm the resource group was created by running:
az group show --name az104-lab-rg --output table
- You should see a table showing az104-lab-rg with location eastus and a Succeeded provisioning state.
Not seeing Succeeded?
Make sure you ran az configure --defaults location=eastus in Step 1. Without a default location, the command requires an explicit --location flag.
If you see a permissions error, confirm you signed in with az login and that your subscription is active with az account show.
Help me troubleshoot my resource group creation.
Next, you will create a Microsoft Entra ID user to simulate an operator in your organization.
- Create the Entra ID user by running this command:
az ad user create --display-name "Lab Operator" --user-principal-name laboperator@[[YOURDOMAIN="your-tenant-name"]].onmicrosoft.com --password "P@ssw0rd1234!" --force-change-password-next-sign-in true
What is Microsoft Entra ID?
Microsoft Entra ID (formerly Azure Active Directory) is the identity service that manages users, groups, and authentication for Azure resources. Creating a user here simulates adding a team member to your organization.
The --force-change-password-next-sign-in true flag ensures the user must set a new password on their first login. This is a security best practice for any temporary or initial password.
- Verify the user was created by running:
az ad user show --id laboperator@[[YOURDOMAIN="your-tenant-name"]].onmicrosoft.com --query "{Name:displayName, UPN:userPrincipalName}" --output table
- You should see a table with Lab Operator as the Name and your full UPN in the UPN column.
Getting a permissions error?
Your account needs the User Administrator role in Microsoft Entra ID to create users. On a free Azure account you created today, you are automatically the Global Administrator, so this should work. If it does not, check that you are signed into the correct subscription with az account show.
Help me fix Entra ID user creation permissions.
Now assign this user a limited role so they can view resources in the resource group without making changes. This is RBAC (Role-Based Access Control) in action.
- Assign the Reader role to the Lab Operator user by running this command:
az role assignment create --assignee laboperator@[[YOURDOMAIN="your-tenant-name"]].onmicrosoft.com --role "Reader" --scope /subscriptions/[[SUBID="your-subscription-id"]]/resourceGroups/az104-lab-rg
What is RBAC?
Role-Based Access Control lets you assign specific permissions to users at a defined scope. The Reader role grants view-only access. By scoping it to az104-lab-rg, the Lab Operator can see resources inside this group but cannot create, modify, or delete them.
This principle of least privilege is a core AZ-104 concept in the Identity and Governance domain.
- Verify the role assignment exists by running:
az role assignment list --resource-group az104-lab-rg --output table
- You should see a row showing the Reader role assigned to your Lab Operator user.
Assign an Azure Policy to enforce tagging
Governance goes beyond identity. Azure Policy lets you enforce organizational rules at scale. You will assign a built-in policy that denies the creation of any resource group missing an Environment tag.
- Assign the built-in policy to your subscription by running this command:
az policy assignment create --name "require-tag-on-rg" --display-name "Require Environment tag on resource groups" --policy "/providers/Microsoft.Authorization/policyDefinitions/96670d01-0a4d-4649-9c89-2d3abc0a5025" --params "{ \"tagName\": { \"value\": \"Environment\" } }"
What does this policy do?
The built-in policy "Require a tag on resource groups" uses a deny effect. Any attempt to create a resource group without the specified tag is blocked before the resource is provisioned.
The --params flag passes the tag name (Environment) to the policy definition. The policy ID 96670d01-0a4d-4649-9c89-2d3abc0a5025 is a Microsoft-managed built-in definition that you do not need to create yourself.
- Verify the policy assignment is active by running:
az policy assignment list --output table
- You should see a row with the name require-tag-on-rg and enforcement mode Default.
Policy assignment not appearing?
Double-check that you used the correct policy definition ID. A typo in the long ID string will cause the command to fail silently or return an error. Copy the full ID directly from the command above.
Help me troubleshoot my Azure Policy assignment.
Test the policy enforcement
A policy only proves its value when it blocks a non-compliant action. You will now attempt to create a resource group without the required tag to confirm the deny effect works.
Policy propagation timing
A new policy assignment takes about five minutes to become fully effective. If you assigned the policy less than five minutes ago, wait a moment before testing. In some cases it can take longer.
- Attempt to create a resource group without the required Environment tag by running:
az group create --name test-no-tag-rg
✔️ I see a RequestDisallowedByPolicy error
The policy successfully denied the creation. This is the expected behavior. The error message references the policy assignment ID and confirms that governance is actively enforced.
- No cleanup needed. The resource group was never created.
ⓧ The resource group was created anyway
The policy has not fully propagated yet. This can happen if you ran the test within five minutes of assigning the policy.
- Delete the test resource group by running:
az group delete --name test-no-tag-rg --yes
- Wait 5 minutes, then re-run the test command above.
- After waiting, the policy should deny the creation and return an error.
Why is this important for AZ-104?
The AZ-104 exam tests your ability to implement and evaluate Azure Policy. Understanding how deny effects block non-compliant requests at creation time is a key exam scenario in the Identity and Governance domain (20-25% of the exam).
Your identity, RBAC, and policy controls are live. Next up, you will build the network infrastructure that your virtual machine will live inside.
Build the Virtual Network and Network Security
Your Azure environment now has identity controls and governance policies in place. In this step, you'll build the network infrastructure that your Virtual Machine will live inside.
Every Azure resource that communicates over a network needs to sit inside a Virtual Network. Without one, your VM would have no private address space and no way to isolate traffic from the public internet.
In this step, get ready to:
- Create a Virtual Network with two subnets for workload separation.
- Create a Network Security Group with an SSH rule and attach it to your workload subnet.
- Verify the full network configuration.
Create the Virtual Network and subnets
A Virtual Network (VNet) gives your resources a private IP address space. Subnets divide that space into smaller segments so you can group resources by purpose and apply different security rules to each group.
- Create the Virtual Network with its first subnet by running this command:
az network vnet create --resource-group az104-lab-rg --name lab-vnet --address-prefixes 10.0.0.0/16 --subnet-name workload-subnet --subnet-prefixes 10.0.1.0/24
What does this command do?
- Creates a VNet named lab-vnet with the address space 10.0.0.0/16 (65,536 available addresses).
- Simultaneously creates a subnet named workload-subnet with the prefix 10.0.1.0/24 (256 addresses) inside that VNet.
- The VNet inherits the default location eastus you configured earlier.
You should see JSON output with "provisioningState": "Succeeded" confirming the VNet was created.
- Add a second subnet for storage-related resources by running this command:
az network vnet subnet create --resource-group az104-lab-rg --vnet-name lab-vnet --name storage-subnet --address-prefixes 10.0.2.0/24
Why two subnets?
Separating workloads into distinct subnets lets you apply different security policies to each group. Your VM will live in workload-subnet and your storage service endpoint will connect through storage-subnet.
You should see JSON output showing the new subnet with "provisioningState": "Succeeded".
Seeing a resource not found error?
- Make sure the --vnet-name value is exactly lab-vnet and the --resource-group is az104-lab-rg.
- Confirm the VNet was created by running az network vnet list --resource-group az104-lab-rg --output table. If lab-vnet is not listed, re-run the VNet create command above.
Help me troubleshoot my VNet subnet creation error.
Create and attach a Network Security Group
A Network Security Group (NSG) acts as a virtual firewall for your subnet. It contains rules that allow or deny traffic based on direction, port, and protocol.
- Create the NSG by running this command:
az network nsg create --resource-group az104-lab-rg --name workload-nsg
You should see JSON output confirming the NSG named workload-nsg was created with a "provisioningState": "Succeeded" status.
- Add an inbound rule to allow SSH traffic on port 22 by running this command:
az network nsg rule create --resource-group az104-lab-rg --nsg-name workload-nsg --name AllowSSH --priority 100 --direction Inbound --access Allow --protocol Tcp --destination-port-ranges 22 --source-address-prefixes "*"
What does this rule do?
- Creates an inbound rule named AllowSSH with priority 100 (lower numbers are evaluated first).
- Allows TCP traffic on port 22 (the standard SSH port) from any source address.
- You will use this rule to SSH into your VM in a later step.
- Associate the NSG with your workload subnet by running this command:
az network vnet subnet update --resource-group az104-lab-rg --vnet-name lab-vnet --name workload-subnet --network-security-group workload-nsg
You should see JSON output for the updated subnet, now showing a networkSecurityGroup property that references workload-nsg.
NSG not associating with the subnet?
- Double-check that the NSG name in the --network-security-group flag matches exactly: workload-nsg.
- Confirm the NSG exists by running az network nsg list --resource-group az104-lab-rg --output table.
- Ensure the subnet name is workload-subnet (not storage-subnet).
Help me troubleshoot my NSG subnet association.
Verify the network configuration
- Confirm the VNet and its subnets by running this command:
az network vnet show --resource-group az104-lab-rg --name lab-vnet --output table
You should see a table showing lab-vnet in the eastus location with the address prefix 10.0.0.0/16.
- Confirm the NSG and its rules by running this command:
az network nsg show --resource-group az104-lab-rg --name workload-nsg --output table
You should see a table displaying workload-nsg in resource group az104-lab-rg with a Succeeded provisioning state.
What should I look for?
The VNet table confirms your address space and region. The NSG table confirms it was provisioned successfully. To see your custom SSH rule specifically, you can run az network nsg rule list --resource-group az104-lab-rg --nsg-name workload-nsg --output table and look for the AllowSSH entry with priority 100.
Your network foundation is in place. Next up, you'll deploy a Linux Virtual Machine into this secured subnet and connect to it via SSH.
Deploy a Virtual Machine with Managed Identity
Your network is locked down with subnets and security rules. Now it is time to deploy compute into that secured environment.
In this step, you will create a Linux Virtual Machine inside your workload subnet. The VM gets a system-assigned managed identity so it can authenticate to other Azure services without storing credentials.
In this step, get ready to:
- Create a Linux VM with a managed identity in the workload subnet.
- Verify the VM is running and note its public IP and identity details.
- SSH into the VM to confirm connectivity.
Create the Linux VM in your workload subnet
Azure VMs are the core compute resource you manage as an administrator. By attaching a system-assigned managed identity at creation time, the VM can later prove who it is to other Azure services without you ever storing a password or key.
- Create the VM by running this command in your PowerShell terminal:
az vm create --resource-group az104-lab-rg --name lab-vm --image Ubuntu2204 --size Standard_B1s --admin-username azureuser --generate-ssh-keys --subnet workload-subnet --vnet-name lab-vnet --assign-identity --tags Environment=Lab Project=AZ104
What does this command do?
- `--image Ubuntu2204` deploys Ubuntu 22.04 LTS, a long-term support Linux distribution.
- `--size Standard_B1s` picks a small, burstable VM size that keeps costs low for a lab.
- `--generate-ssh-keys` creates an SSH key pair on your machine if one does not already exist.
- `--subnet workload-subnet --vnet-name lab-vnet` places the VM inside the secured subnet you built in the previous step.
- `--assign-identity` enables a system-assigned managed identity on the VM.
- `--tags Environment=Lab Project=AZ104` applies the same governance tags you used on the resource group.
This command takes 1-2 minutes to complete. When it finishes, the CLI outputs a JSON block containing the VM's public IP address and other details.
Seeing a quota error?
If you see a message about exceeding quota for `Standard_B1s` in your region, your subscription may not have vCPU quota in `eastus`. Try a different region by adding `--location westus2` to the command.
If you already have SSH keys and the command warns about an existing key, that is fine. Azure CLI reuses your existing `id_rsa` key automatically.
help me troubleshoot my az vm create error
Cost warning
The Standard_B1s VM incurs a small hourly charge while running. This is covered by your Azure free account's $200 credit. Clean up promptly after the project to avoid consuming credit unnecessarily.
Verify the VM is running
- Check the VM status and note its public IP by running:
az vm show --resource-group az104-lab-rg --name lab-vm --show-details --output table
What should I see?
The output table shows the VM's power state as `VM running`, along with the public IP address and private IP address. Look at the `PublicIps` column and note that value. You will need it in the next substep.
The `--show-details` flag adds networking info that `az vm show` alone does not include.
- Find the managed identity principal ID by running:
az vm show --resource-group az104-lab-rg --name lab-vm --query identity.principalId --output tsv
Why do I need the principal ID?
The principal ID is the VM's identity within Microsoft Entra ID. In the next step, you will use it to grant the VM permissions on a storage account without sharing any secrets.
- Copy and save the principal ID output somewhere accessible. You will use it in Step 5.
VM showing a different power state?
If the power state shows `VM starting` or `VM deallocated`, wait a minute and re-run the `az vm show` command. The VM may still be booting.
my VM is not showing as running in az vm show
SSH into the VM to confirm connectivity
The NSG rule you created in the previous step allows SSH traffic on port 22. Now you can verify end-to-end connectivity by connecting to the VM from your local machine.
- Connect to the VM by running:
ssh azureuser@[[PUBLIC_IP="your VM's public IP address from the table above"]]
What happens when I connect?
The first time you SSH to a new host, your terminal asks you to confirm the server's fingerprint. Type `yes` and press **Enter** to continue.
Once connected, your terminal prompt changes to the VM's hostname (something like `azureuser@lab-vm`). This confirms you are now running commands on the remote VM, not your local machine.
- Confirm you are on the VM by running:
hostname
- You should see `lab-vm` in the output.
- Disconnect from the VM by running:
exit
You are now back in your local PowerShell session.
Connection timed out?
If SSH hangs or times out, confirm that the NSG rule `AllowSSH` on `workload-nsg` is set to allow inbound TCP port 22. Run `az network nsg rule list --resource-group az104-lab-rg --nsg-name workload-nsg --output table` to verify.
Also confirm you are using the correct public IP from the `az vm show` output.
help me troubleshoot my SSH connection to the Azure VM
Your VM is running inside a secured subnet and carries its own identity. Next up, you will create a storage account and let the VM access it using that managed identity.
Create Storage and Connect via Managed Identity
Your VM is running, secured behind an NSG, and authenticated with a system-assigned managed identity. The final piece of the puzzle is storage.
In this step, you will create a Storage Account with a Blob Storage container, then prove that the VM can access it using only its managed identity. No storage keys, no passwords, no secrets passed around.
In this step, get ready to:
- Create a Storage Account with a blob container.
- Assign the Storage Blob Data Contributor role to the VM's managed identity.
- Upload a blob from the VM using managed identity authentication.
Create a Storage Account and blob container
Azure Storage Accounts hold blobs, files, queues, and tables. You need a globally unique name for yours because the name becomes part of the public URL.
- Create a Storage Account by running this command:
az storage account create --resource-group az104-lab-rg --name [[STORAGE_ACCOUNT="az104labstore followed by a unique suffix e.g. az104labstore7291"]] --sku Standard_LRS --tags Environment=Lab Project=AZ104
What does this command do?
- --resource-group az104-lab-rg places the account in your existing resource group.
- --name must be globally unique across all of Azure, between 3 and 24 characters, lowercase letters and numbers only.
- --sku Standard_LRS uses locally redundant storage, the cheapest tier for a lab.
- --tags satisfy the tagging policy you set up earlier.
- You should see a JSON response containing "provisioningState": "Succeeded" confirming the account is ready.
Seeing a name conflict error?
Storage account names must be globally unique. If your name is taken, try appending more random digits or your initials plus today's date.
Also confirm the name is only lowercase letters and numbers, with no hyphens or uppercase characters.
Help me troubleshoot my storage account name
- Create a blob container inside the storage account by running this command:
az storage container create --account-name [[STORAGE_ACCOUNT="az104labstore followed by a unique suffix e.g. az104labstore7291"]] --name project-data --auth-mode login
What does --auth-mode login do?
The --auth-mode login flag tells Azure to authenticate using your Entra ID credentials instead of a storage key. This is the identity-based access pattern the AZ-104 exam emphasizes.
- You should see a JSON response with "created": true confirming the container exists.
Container creation denied?
Your signed-in account needs the Storage Blob Data Contributor or Owner role on the storage account. If you are the subscription owner, you already have this. If not, assign the role to yourself first.
Help me fix this authorization error
Assign the Storage Blob Data Contributor role to the VM's managed identity
For the VM to read and write blobs on its own, its managed identity needs an explicit RBAC role assignment on the storage account.
- Retrieve the VM's managed identity principal ID by running:
az vm show --resource-group az104-lab-rg --name lab-vm --query identity.principalId --output tsv
- Copy the GUID that is returned. This is the principal ID of the VM's managed identity.
- Assign the Storage Blob Data Contributor role to that principal by running this command:
az role assignment create --assignee-object-id [[PRINCIPAL_ID="your VM managed identity principal ID"]] --assignee-principal-type ServicePrincipal --role "Storage Blob Data Contributor" --scope /subscriptions/[[SUB_ID="your subscription ID"]]/resourceGroups/az104-lab-rg/providers/Microsoft.Storage/storageAccounts/[[STORAGE_ACCOUNT="az104labstore followed by a unique suffix e.g. az104labstore7291"]]
What is Storage Blob Data Contributor?
This built-in role grants read, write, and delete access to blob containers and data. Assigning it to the VM's managed identity means the VM can interact with blobs without ever holding a storage account key.
This pattern of identity-based access is a core AZ-104 concept. It replaces the older approach of distributing shared keys.
- You should see a JSON response containing the role assignment details, including "roleDefinitionName": "Storage Blob Data Contributor".
Role assignment failing?
Make sure you pasted the correct principal ID (a GUID with hyphens). If you see a "principal does not exist" error, double-check that you copied the output of the previous command exactly.
Also confirm your subscription ID is correct. You can retrieve it with az account show --query id --output tsv.
Help me troubleshoot this role assignment
SSH into the VM and upload a blob using the managed identity
Now for the payoff. You will SSH into the VM, install the Azure CLI on it, sign in with the managed identity, and upload a file to blob storage. No passwords or keys involved.
- SSH into the VM by running:
ssh azureuser@[[PUBLIC_IP="your VM public IP address"]]
- You are now inside the VM. Install the Azure CLI on the VM by running:
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
Why install Azure CLI on the VM?
The VM needs the Azure CLI to interact with Azure services from within. This install script is maintained by the Azure CLI team and handles all the repository and package setup for Ubuntu automatically.
The installation takes a minute or two. Wait until you see the terminal prompt return.
- Once the installation finishes, sign in to Azure using the VM's managed identity by running:
az login --identity
- You should see a JSON response showing the subscription details. No username or password was needed because the VM authenticated with its managed identity.
az login --identity not working?
Make sure the CLI installation completed successfully. Run az version to confirm the CLI is installed.
If you see a timeout error, the RBAC role assignment may still be propagating. Wait 1-2 minutes and try again.
Help me fix managed identity login
- Create a test file and upload it to your blob container by running:
echo "AZ-104 Lab Complete" > test.txt && az storage blob upload --account-name [[STORAGE_ACCOUNT="az104labstore followed by a unique suffix e.g. az104labstore7291"]] --container-name project-data --name test.txt --file test.txt --auth-mode login
What is happening here?
The first part creates a local file called test.txt on the VM. The second part uploads it to the project-data container using --auth-mode login.
Because the VM is signed in via its managed identity, Azure checks the RBAC role you assigned earlier. The VM has Storage Blob Data Contributor, so the upload succeeds.
- Verify the blob exists in the container by running:
az storage blob list --account-name [[STORAGE_ACCOUNT="az104labstore followed by a unique suffix e.g. az104labstore7291"]] --container-name project-data --auth-mode login --output table
- You should see a table listing test.txt as a blob in the container. This confirms the VM uploaded the file using only its managed identity.
- Type exit to disconnect from the VM and return to your local terminal.
Blob upload denied?
RBAC role assignments can take up to 5 minutes to propagate. If you just assigned the role, wait a few minutes and retry the upload command.
Confirm you are signed in with the managed identity by running az account show on the VM. The user type should show as servicePrincipal.
Help me troubleshoot this blob upload error
Secret mission
Set Up Azure Monitor Alerts
Your Azure environment is governed, secured, and connected. But how do you know when something goes wrong? Configure Azure Monitor to track VM metrics and set up an alert rule that fires when CPU exceeds 80%.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. The VM and public IP in this project consume your Azure free credit while running.
Cost warning
Your Standard_B1s VM costs approximately $0.0104/hour and the public IP costs approximately $0.005/hour. These charges draw from your $200 Azure free credit. If you are done with the project, choose Pause or Delete to stop accumulating charges.
Resources you used:
- Resource group az104-lab-rg (contains VM, storage account, VNet, NSG, NIC, public IP, OS disk, action group, metric alert rule).
- Policy assignment require-tag-on-rg (subscription-level).
- Microsoft Entra ID user laboperator (tenant-level).
Keep everything running
No action needed. Choose this if you are still actively building or want to continue practicing Azure administration with these resources.
- Your VM, storage account, and networking resources remain active and accessible.
- The policy assignment continues enforcing tagging governance on new resource groups.
- Charges will continue to draw from your $200 Azure free credit while the VM is running.
Pause - I'll come back to this later
Deallocate the VM to stop compute charges while keeping all your resources and configuration intact. You will still be charged for the managed disk and public IP.
- Deallocate the VM by running this command:
az vm deallocate --resource-group az104-lab-rg --name lab-vm
- To resume later, start the VM again with:
az vm start --resource-group az104-lab-rg --name lab-vm
What stays after deallocating?
Deallocating stops compute charges but your OS disk, public IP, storage account, and all other resources remain. The disk and static public IP still incur small charges.
Delete - I don't want to use this again
Remove all project resources completely. This deletes everything and cannot be undone.
- Delete the entire resource group (this removes the VM, storage account, VNet, NSG, NIC, public IP, OS disk, action group, and metric alert rule) by running:
az group delete --name az104-lab-rg --yes --no-wait
- Delete the policy assignment (subscription-level, not removed by the resource group deletion) by running:
az policy assignment delete --name "require-tag-on-rg"
- Delete the Microsoft Entra ID user (tenant-level, not removed by the resource group deletion) by running:
az ad user delete --id [[YOURDOMAIN="laboperator@your-domain.onmicrosoft.com"]]
- Verify the resource group is gone by running:
az group list --output table
You should no longer see az104-lab-rg in the output.
Nice Work!
Nice Work!
You just deployed a fully governed Azure environment that spans all five AZ-104 exam domains: identity and governance, networking, compute, storage, and monitoring.
You've learned how to:
- Enforce Azure Policy governance and assign scoped RBAC roles to a Microsoft Entra ID user.
- Build a secured Virtual Network with subnets and NSG rules, then deploy a Linux Virtual Machine with a system-assigned managed identity.
- Create a Storage Account and access Blob Storage from the VM using only its managed identity, with no storage keys required.
- Secret Mission: Configured Azure Monitor metric alerts to track VM CPU utilization and receive email notifications.
Ready to quiz yourself?