Govern a Secure Azure Mini-Company

Build a governed Azure web environment with CLI, alerts, backup, and scaling.

Introduction

30 Second Summary

A new company often starts with a blank online account. Without clear boundaries, its growing setup becomes hard to trust.

In this project, you will build Cloud Interview Lab, a miniature company environment in Microsoft Azure. You will use Azure Cloud Shell to turn an empty subscription into a governed web environment.

What You'll Build

At the lab's public IP address, you'll see the NGINX welcome page served from a machine protected by your network rule.

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

  • A tagged lab boundary you can show in Azure. A Microsoft Entra ID group has Reader access at that boundary. Azure Policy audits the virtual machine.
  • A private blob demo in Azure Storage. You can upload interview-note.txt. You can list it through Microsoft Entra authentication.
  • A web operations demo that moves from a blocked browser request to the NGINX welcome page. You can show an Azure Monitor CPU alert. You can also show Azure Backup protection.
  • Secret Mission: Deploy two VM instances together with a Flexible Virtual Machine Scale Set. Explain why its shared model suits the existing network.

Are there any prerequisites?

You need a Microsoft account, a phone number, and a non-prepaid credit or debit card for Azure signup. The final cleanup section helps you remove Azure resources that can incur pay-as-you-go charges.

Before We Start

This opening checkpoint turns the Cloud Interview Lab into an interview story before you configure anything in Microsoft Azure. That story connects an Azure subscription boundary to a resource group lifecycle boundary, identity rules through Azure RBAC, audits through Azure Policy, private storage, network controls, monitoring, and recovery.

Open Your Azure Workshop

Your Cloud Interview Lab needs an Azure subscription before Microsoft Azure can place resources inside a billing boundary. Without an active subscription, the portal has no scope for the environment.

You'll create that boundary in the Azure portal. You'll use Azure Cloud Shell as an authenticated browser workstation without installing Azure tools on your Mac.

In this step, get ready to:
  • Activate an eligible Azure free-account or Pay-As-You-Go subscription.
  • Register the Microsoft.CloudShell resource provider.
  • Verify the intended subscription from an ephemeral Bash session.
Create the subscription

A subscription connects resource usage to an account. It also provides the outer governance boundary for everything you build later.

Subscription setup touches billing, so pause at the offer screen. Eligible new customers can receive USD 200 credit for 30 days with spending protection.

  • Open the official Azure account page.
  • Sign in with your existing Microsoft account.
  • Choose Azure free account if Microsoft confirms that you are eligible.
  • Choose Pay as you go if the free account is unavailable.
  • Enter your phone number when the account page requests it.
  • Enter a non-prepaid credit or debit card when the account page requests it.
  • Complete the identity checks shown on the account page.
  • Keep the Azure free account spending limit enabled if you selected the free offer.

Once signup completes, your subscription becomes the billing boundary for the lab. That is the first major piece of your Azure workshop in place.

  • Continue to the Azure portal from the signup confirmation.
  • Select Subscriptions from the portal navigation.

You should see the new subscription with an enabled status. This confirms Azure can accept authenticated management requests for the lab.

Register Cloud Shell

A resource provider connects a subscription to a specific Azure service. Registering the Cloud Shell provider allows this subscription to start the browser workstation.

  • In the Subscriptions page from the previous check, select the new subscription.
  • Under Settings, select Resource providers.
  • Search for cloudshell.
  • Select Microsoft.CloudShell from the results.
  • Select Register.

You should see the provider status change to Registered. Your subscription can now host a Cloud Shell session.

  • Select Cloud Shell from the portal navigation.

The setup panel now asks how the browser workstation should run. This project uses an ephemeral Bash session because no workshop files need to persist between sessions.

  • Choose Bash as the shell.
  • Select No storage account required for an ephemeral session.

The subscription picker now determines which Azure boundary receives commands from this session.

  • Select the new subscription.
  • Select Apply.

You should see a Bash prompt inside the Cloud Shell panel. Your authenticated browser workstation is now connected to Azure.

What Does Ephemeral Mean?

Files stored inside an ephemeral Cloud Shell session are deleted when the session ends. Azure resources created through the session remain until you delete those resources.

This gives you a temporary workshop bench without making the company environment temporary.

Cloud Shell Does Not Start?

  • Return to Resource providers under the subscription settings.
  • Confirm that Microsoft.CloudShell shows a Registered status.
  • Return to Cloud Shell from the portal navigation.
  • Select the intended subscription in the setup panel.

If the session still does not start, help me troubleshoot my Azure Cloud Shell setup.

Confirm the active subscription

The Azure CLI is preinstalled in Cloud Shell. Its account commands reveal which subscription receives your next resource commands.

Before you run the checks, which subscription do you expect Cloud Shell to identify as active?

  • Inspect the available subscriptions, active subscription, and Azure CLI version by running these commands:
az account list --output table
az account show --output table
az version

What Do These Checks Prove?

  • The az account list command shows the subscriptions your signed-in identity can access.
  • The az account show command identifies the subscription currently receiving Azure CLI requests.
  • The az version command reports the Azure CLI version provided by Cloud Shell.

The first table should include your enabled subscription. The second table should identify it as active.

The final output should include azure-cli. Use the tabs below to match the version result you see.

✔️ I see version 2.90.0 or higher

Your managed Cloud Shell image includes Azure CLI 2.90.0 or higher. The command-line workstation is ready for the lab.

ⓧ I see an older version

Microsoft manages the Azure CLI image inside Cloud Shell. A fresh session gives you the current service image.

  • Close the current Cloud Shell panel.
  • Return to Cloud Shell from the portal navigation.
  • Choose Bash.
  • Select No storage account required for an ephemeral session.
  • Select the intended subscription.
  • Select Apply.
  • Run the verification commands above again.

Still Seeing an Older Version?

Compare your result with the official Cloud Shell release notes. The service release notes identify the Azure CLI version available in Cloud Shell.

If your fresh session remains behind, help me check my Cloud Shell Azure CLI version.

ⓧ Azure CLI is unavailable

An unavailable Azure CLI indicates that the expected Cloud Shell environment did not finish loading.

  • Close the current Cloud Shell panel.
  • Return to the subscription's Resource providers page.
  • Confirm that Microsoft.CloudShell shows a Registered status.
  • Return to Cloud Shell from the portal navigation.
  • Start another ephemeral Bash session with the intended subscription.
  • Run the verification commands above again.

Azure CLI Still Unavailable?

If the Bash prompt loads without Azure CLI, help me diagnose the Cloud Shell environment.

The subscription tables may list more than one subscription. Match your output to the active-subscription tabs below.

✔️ The intended subscription is active

Your Cloud Shell session is already targeting the correct subscription. Every Azure CLI command in the next step uses this scope.

ⓧ A different subscription is active

  • Locate the exact intended subscription name in the first table.
  • Replace my-subscription-name in the command below with that exact name:
az account set --subscription 'my-subscription-name'

What Does This Command Do?

This changes the active Azure CLI subscription for the current Cloud Shell session. Future resource commands use the selected subscription by default.

  • Confirm the corrected active subscription by running:
az account show --output table

What Should I See?

The table should now show the intended subscription as the active scope. This prevents later resources from landing in a different subscription.

Workshop Checkpoint

Azure Cloud Shell: Authenticated ephemeral Bash session configured with no storage account required and connected to the active subscription.

Azure Subscription: Enabled Azure free-account or Pay-As-You-Go subscription selected as the active Azure CLI subscription.

Microsoft.CloudShell resource provider: Registered in the active subscription.

Your browser-based Azure workshop is authenticated and ready. Next, you'll give the empty subscription its first governance rules.

Set Subscription Guardrails

Your authenticated Azure Cloud Shell session is ready from the previous step. The active subscription is empty, so it has no useful resource boundary or least-privilege story.

In this step, you create a labeled container for the lab. You also add a read-only audience plus a non-blocking audit rule before any workload arrives.

In this step, get ready to:
  • Create a tagged resource group with a budget checkpoint.
  • Grant read-only access at the resource-group scope.
  • Assign a policy that audits virtual machine disks.
Create the resource boundary

A resource group gives related Azure resources one lifecycle boundary. The session variables keep every resource name consistent throughout the lab.

  • Set the reusable lab names in Cloud Shell by running these commands:
export LOCATION=eastus
export RG=rg-cloud-interview
export VNET=vnet-cloud-interview
export SUBNET=subnet-web
export NSG=nsg-web
export VM=vm-web-01
export VMSS=vmss-web
export STORAGE="stinterview$(openssl rand -hex 3)"

What Do These Variables Store?

  • The variables store the region plus the names used by later resources.
  • The STORAGE value ends with six random hexadecimal characters to help make the storage account name globally unique.
  • The exports remain available in the current ephemeral Cloud Shell session.
  • Create the tagged resource group by running this command:
az group create \
  --name "$RG" \
  --location "$LOCATION" \
  --tags Environment=Interview Owner=Candidate

What Does This Command Create?

  • The command creates rg-cloud-interview in eastus.
  • The Environment=Interview tag records the lab's purpose.
  • The Owner=Candidate tag records responsibility for the resources.

Your first guardrail is in place. The command output confirms that the lab now has a labeled boundary for access plus cleanup.

Resource Group Creation Failed?

Confirm that Cloud Shell still uses the intended subscription. Check that RG plus LOCATION contain values.

Help me troubleshoot the resource group command.

A Cost Management budget is a spending alarm. It sends notifications when recorded usage reaches configured thresholds.

This action touches billing settings. Creating a budget does not create a charge.

  • Return to the Azure portal from earlier.
  • Enter Cost Management + Billing in the portal search bar.
  • Select the active subscription scope.
  • Open Budgets.
  • Check whether budget creation is available for the new subscription.

✔️ Budget creation is available

  • Create a small monthly notification budget in your billing currency.
  • Save the budget.

The saved budget can notify you when recorded usage crosses the thresholds you configured. It does not stop running resources.

ⓧ Budgets are not available yet

Cost Management features can take up to 48 hours to become available for a new subscription. This initialization delay does not block the lab.

  • Record the new-subscription availability delay.
  • Switch back to Cloud Shell from earlier.
  • Check the current budget state by running this command:
az consumption budget list

What Does This Command Check?

The command lists budgets available at the active subscription scope. An empty result supports the budget checkpoint while Cost Management initializes.

Add read-only access at resource-group scope

Microsoft Entra ID identifies the person or group requesting access. Azure RBAC controls what that identity can do at a specific scope.

The readers group creates a clear least-privilege example. Your subscription Owner permission remains inherited from the broader subscription scope.

  • Capture the signed-in user ID plus the resource-group ID by running these commands:
export USER_ID=$(az ad signed-in-user show --query id --output tsv)
export RG_ID=$(az group show --resource-group "$RG" --query id --output tsv)

What Do These IDs Represent?

  • USER_ID identifies the account signed in to Cloud Shell.
  • RG_ID stores the full Azure scope for rg-cloud-interview.
  • Create the readers group by running this command:
export GROUP_ID=$(az ad group create \
  --display-name "Cloud Interview Readers" \
  --mail-nickname cloud-interview-readers \
  --query id \
  --output tsv)

What Does the Group Command Do?

The command creates the Cloud Interview Readers group. It stores the resulting identifier in GROUP_ID for the access commands.

  • Choose the outcome that matches the group creation result.

✔️ The group was created

  • Add the signed-in user to the readers group by running this command:
az ad group member add --group "$GROUP_ID" --member-id "$USER_ID"

What Does Membership Change?

The command places your signed-in account inside the readers group. Group membership lets one role assignment apply to every member.

  • Assign Reader to the group by running this command:
az role assignment create \
  --assignee "$GROUP_ID" \
  --role Reader \
  --scope "$RG_ID"

How Does the Assignment Work?

The assignment grants Reader to the group at the resource-group scope. This role provides a read-only view through this assignment.

ⓧ Group creation is blocked

Some tenants restrict group creation. The documented fallback assigns the same role directly to your signed-in identity at the same scope.

  • Assign Reader to the signed-in user by running this command:
az role assignment create --assignee-object-id "$USER_ID" --assignee-principal-type User --role Reader --scope "$RG_ID"

What Does the Fallback Preserve?

The fallback keeps the access boundary at rg-cloud-interview. USER_ID becomes the applicable identity for the Reader assignment.

The tenant restriction changes the assignee. The least-privilege scope stays the same.

  • Inspect the resulting Reader assignment by running this command:
az role assignment list --scope "$RG_ID" --role Reader --output table

What Should the Table Prove?

The table identifies the group or fallback user holding Reader at the resource-group scope. This is your least-privilege evidence.

Reader Assignment Missing?

Check that USER_ID plus RG_ID contain values. On the group path, confirm that GROUP_ID contains the created group identifier.

Help me troubleshoot the Reader assignment.

Assign the audit policy

Azure Policy evaluates resources against defined rules. This audit assignment reports virtual machines that do not use managed disks without blocking deployment.

Provider registration can take a little time. A short pause in Cloud Shell is expected while Azure enables the policy capability.

  • Register the policy provider by running this command:
az provider register --namespace Microsoft.PolicyInsights

Why Register the Provider?

Microsoft.PolicyInsights supplies the policy evaluation capabilities used by this assignment. Registration enables those capabilities in the active subscription.

  • Find the managed-disk audit definition plus create the assignment by running these commands:
definition=$(az policy definition list \
  --query "[?displayName=='Audit VMs that do not use managed disks']".name \
  --output tsv)

az policy assignment create \
  --name 'audit-vm-managed-disks' \
  --display-name 'Audit VM managed disks' \
  --scope "$RG_ID" \
  --policy "$definition" \
  --description 'Azure CLI policy assignment to resource group'

How Does This Policy Work?

  • The first command finds the built-in definition named Audit VMs that do not use managed disks.
  • The second command creates audit-vm-managed-disks at the resource-group scope.
  • Audit mode records compliance information without rejecting a resource deployment.

The assignment is available for inspection immediately. Its compliance state can take a few minutes to become active.

Before you inspect the guardrails, which three results do you expect the tables to confirm?

  • Inspect the resource group plus both assignments by running these commands:
az group show --resource-group "$RG" --output table
az role assignment list --scope "$RG_ID" --role Reader --output table
az policy assignment show --name audit-vm-managed-disks --scope "$RG_ID" --output table

What Do the Inspection Tables Prove?

  • The first table confirms that rg-cloud-interview exists in eastus with the lab tags.
  • The second table confirms a Reader assignment at the resource-group scope.
  • The third table confirms the Audit VM managed disks policy assignment.

That is the subscription foundation in place. Your lab now has a lifecycle boundary plus read-only access plus a non-blocking audit rule.

Missing One of the Guardrails?

  • Check that RG_ID came from rg-cloud-interview in the active subscription.
  • Confirm that the Reader assignment used the group identifier or the documented user fallback.
  • Confirm that the policy assignment name is audit-vm-managed-disks.

Help me find the missing guardrail.

✔️ Awesome, I've got everything!

Everything is in place. Keep this Cloud Shell session active so the exported variables remain available.

ⓧ I'd like to double check the full code

export LOCATION=eastus
export RG=rg-cloud-interview
export VNET=vnet-cloud-interview
export SUBNET=subnet-web
export NSG=nsg-web
export VM=vm-web-01
export VMSS=vmss-web
export STORAGE="stinterview$(openssl rand -hex 3)"

az group create \
  --name "$RG" \
  --location "$LOCATION" \
  --tags Environment=Interview Owner=Candidate

export USER_ID=$(az ad signed-in-user show --query id --output tsv)
export RG_ID=$(az group show --resource-group "$RG" --query id --output tsv)
export GROUP_ID=$(az ad group create \
  --display-name "Cloud Interview Readers" \
  --mail-nickname cloud-interview-readers \
  --query id \
  --output tsv)

az ad group member add --group "$GROUP_ID" --member-id "$USER_ID"

az role assignment create \
  --assignee "$GROUP_ID" \
  --role Reader \
  --scope "$RG_ID"

az provider register --namespace Microsoft.PolicyInsights

definition=$(az policy definition list \
  --query "[?displayName=='Audit VMs that do not use managed disks']".name \
  --output tsv)

az policy assignment create \
  --name 'audit-vm-managed-disks' \
  --display-name 'Audit VM managed disks' \
  --scope "$RG_ID" \
  --policy "$definition" \
  --description 'Azure CLI policy assignment to resource group'

az group show --resource-group "$RG" --output table
az role assignment list --scope "$RG_ID" --role Reader --output table
az policy assignment show --name audit-vm-managed-disks --scope "$RG_ID" --output table

How to Compare This Reference

This reference shows the primary group path in execution order. If tenant policy blocked group creation, use the documented user assignment from the fallback tab instead of the group membership plus group role commands.

Your subscription now has the rules a company environment needs before workloads arrive. Next, you build private storage plus a fenced network inside this governed boundary.

Create Private Storage and Network

The governance guardrails from the previous step now define who can inspect your lab. Before a server comes online, its data needs a private home.

In this step, you will use Azure Storage to hold a private interview note through Microsoft Entra ID authentication.

You will also use an Azure Virtual Network with a network security group to fence the future web subnet.

In this step, get ready to:
  • Create a private storage account with secure connection settings.
  • Upload a private blob through Microsoft Entra authentication.
  • Build a protected network boundary for the future web server.
Secure the storage boundary

A storage account is the building that holds your data rooms. HTTPS protects requests in transit while the public-access setting keeps blob data private.

  • Provision the storage account with its required security settings by running:
az storage account create \
  --name "$STORAGE" \
  --resource-group "$RG" \
  --location "$LOCATION" \
  --sku Standard_LRS \
  --kind StorageV2 \
  --https-only true \
  --allow-blob-public-access false \
  --tags Environment=Interview

What does this command do?

  • The account uses the name stored in $STORAGE so it keeps the random suffix generated earlier.
  • The Standard_LRS SKU stores locally redundant copies of the data.
  • The StorageV2 kind creates a general-purpose storage account.
  • The security settings require HTTPS while preventing public blob access.
  • Review the command output for the new account settings.

You should see the account named by $STORAGE in eastus with successful provisioning details.

Storage account creation rejected?

  • Check that $STORAGE contains between 3 and 24 lowercase letters or numbers.
  • Confirm that the current Cloud Shell subscription contains rg-cloud-interview.

Help me troubleshoot the Azure storage account creation command.

Creating the account gives you control over the Azure resource through the management plane. Blob operations use the data plane, which needs its own role assignment.

  • Capture the storage account ID by running the following commands:
export STORAGE_ID=$(az storage account show \
  --name "$STORAGE" \
  --resource-group "$RG" \
  --query id \
  --output tsv)

az ad signed-in-user show --query id --output tsv | az role assignment create \
  --role "Storage Blob Data Contributor" \
  --assignee @- \
  --scope "$STORAGE_ID"

How does data access work?

  • The first command stores the account's resource ID in STORAGE_ID for reuse as the access scope.
  • The pipeline retrieves your signed-in identity without exposing a credential.
  • The Storage Blob Data Contributor role grants that identity permission to create containers and upload blobs.
  • The storage-account scope keeps the data permission limited to this account.
  • Review the role assignment output.

You should see your signed-in identity associated with Storage Blob Data Contributor at the storage account scope.

Role assignment not created?

  • Confirm that STORAGE_ID contains the full resource ID for the account.
  • Check that your active Azure identity can create role assignments at this scope.

Help me fix the storage data role assignment.

Put a private blob in storage

An Azure Blob Storage container is one locked room inside the account. The room stays private while your Microsoft Entra identity receives permission to place files inside it.

New role assignments can take a few minutes to reach Blob Storage. A brief wait here is normal.

  • Create the interview note and its private container by running:
printf 'Azure interview storage check\n' > interview-note.txt

az storage container create \
  --account-name "$STORAGE" \
  --name interview \
  --auth-mode login \
  --public-access off

What gets created?

  • The first command writes one line to interview-note.txt inside the ephemeral Cloud Shell session.
  • The container command uses your Microsoft Entra login for authorization.
  • The interview container keeps public access turned off.
  • Review the container creation output.

You should see a result confirming that the private interview container exists.

Container access denied?

  • Wait a few minutes for the new data role assignment to propagate.
  • Rerun the container command after the wait.

Help me access the private blob container.

  • Upload the interview note through Microsoft Entra authentication by running:
az storage blob upload \
  --account-name "$STORAGE" \
  --container-name interview \
  --name interview-note.txt \
  --file interview-note.txt \
  --auth-mode login

How is the blob uploaded?

  • The local interview-note.txt file becomes a blob with the same name.
  • The upload targets the private interview container.
  • Microsoft Entra authentication avoids using a storage account key.
  • Review the upload result.

You should see a successful result for interview-note.txt.

Blob upload failed?

  • Confirm that interview-note.txt exists in the current Cloud Shell session.
  • Check that the container name is exactly interview.
  • Retry after the role assignment has had time to propagate.

Help me troubleshoot the private blob upload.

Fence the future web subnet

The virtual network defines the private address space for your lab. Its web subnet gives the future VM a specific network segment.

  • Create the VNet and web subnet by running:
az network vnet create \
  --resource-group "$RG" \
  --name "$VNET" \
  --address-prefix 10.10.0.0/16 \
  --subnet-name "$SUBNET" \
  --subnet-prefix 10.10.1.0/24

How is the network divided?

  • The VNet receives the address space 10.10.0.0/16.
  • The web subnet receives the smaller prefix 10.10.1.0/24 inside that address space.
  • The existing VNET and SUBNET variables keep the resource names consistent.
  • Review the network creation output.

You should see vnet-cloud-interview with its subnet-web subnet.

VNet creation failed?

  • Confirm that the current Cloud Shell session still contains RG, VNET, and SUBNET.
  • Check that the VNet address space contains the subnet prefix.

Help me troubleshoot the VNet and subnet creation.

The network still needs a traffic guard. An NSG supplies default rules that deny unsolicited inbound Internet traffic.

  • Create the subnet's NSG by running:
az network nsg create \
  --resource-group "$RG" \
  --name "$NSG"

What protects the subnet?

The NSG starts with Azure's default security rules. Its inbound defaults block Internet traffic that has no explicit allow rule.

  • Review the NSG creation output.

You should see the network security group named nsg-web inside rg-cloud-interview.

NSG creation failed?

  • Confirm that NSG still resolves to nsg-web.
  • Check that the active subscription contains rg-cloud-interview.

Help me create the network security group.

  • Attach the NSG to the web subnet by running:
az network vnet subnet update \
  --resource-group "$RG" \
  --vnet-name "$VNET" \
  --name "$SUBNET" \
  --network-security-group "$NSG"

Why attach the NSG here?

Associating the NSG with subnet-web applies its traffic rules to resources connected to that subnet. The subnet becomes the network boundary for the web workload.

  • Review the subnet update output.

You should see nsg-web referenced by the subnet's network security group setting.

NSG not attached?

  • Confirm that the VNet and NSG both exist in rg-cloud-interview.
  • Check that the subnet name is exactly subnet-web.

Help me attach the NSG to the subnet.

Before you run these checks, what do you expect the blob table to contain? What kind of inbound protection do you expect from an NSG with no custom HTTP rule?

  • Inspect the private blob and network rules by running:
az storage blob list \
  --account-name "$STORAGE" \
  --container-name interview \
  --output table \
  --auth-mode login

az network nsg rule list \
  --resource-group "$RG" \
  --nsg-name "$NSG" \
  --include-default \
  --output table

What do these checks prove?

The first table contains interview-note.txt. This proves your Microsoft Entra identity can list data in the private container.

The second table includes a default inbound deny rule. It contains no custom HTTP allow rule, so the future web subnet remains closed to inbound web traffic.

Verification output missing?

  • Rerun the upload command if the blob table does not contain interview-note.txt.
  • Confirm that nsg-web is associated with subnet-web if the NSG check fails.

Help me verify my private storage and default-deny network.

You have built the lab's locked data room and network fence. Both boundaries now show concrete evidence that they are working.

✔️ Awesome, I've got everything!

Great. Keep this Cloud Shell session active so the storage and network variables remain available for the web VM.

ⓧ I'd like to double check the full code

az storage account create \
  --name "$STORAGE" \
  --resource-group "$RG" \
  --location "$LOCATION" \
  --sku Standard_LRS \
  --kind StorageV2 \
  --https-only true \
  --allow-blob-public-access false \
  --tags Environment=Interview

export STORAGE_ID=$(az storage account show \
  --name "$STORAGE" \
  --resource-group "$RG" \
  --query id \
  --output tsv)

az ad signed-in-user show --query id --output tsv | az role assignment create \
  --role "Storage Blob Data Contributor" \
  --assignee @- \
  --scope "$STORAGE_ID"

printf 'Azure interview storage check\n' > interview-note.txt

az storage container create \
  --account-name "$STORAGE" \
  --name interview \
  --auth-mode login \
  --public-access off

az storage blob upload \
  --account-name "$STORAGE" \
  --container-name interview \
  --name interview-note.txt \
  --file interview-note.txt \
  --auth-mode login

az network vnet create \
  --resource-group "$RG" \
  --name "$VNET" \
  --address-prefix 10.10.0.0/16 \
  --subnet-name "$SUBNET" \
  --subnet-prefix 10.10.1.0/24

az network nsg create \
  --resource-group "$RG" \
  --name "$NSG"

az network vnet subnet update \
  --resource-group "$RG" \
  --vnet-name "$VNET" \
  --name "$SUBNET" \
  --network-security-group "$NSG"

az storage blob list \
  --account-name "$STORAGE" \
  --container-name interview \
  --output table \
  --auth-mode login

az network nsg rule list \
  --resource-group "$RG" \
  --nsg-name "$NSG" \
  --include-default \
  --output table

Your private storage and network boundary are ready. Next, you will place a web VM inside this protected subnet and test what the network guard allows.

Deploy a Blocked Web VM

Your private blob is locked down. Your subnet already has a security boundary.

Installing a web server solves the compute side of the workload. A network security group independently decides whether browser traffic reaches it.

In this step, you will use Azure Virtual Machines to deploy the server. You will use Azure Cloud Shell to install NGINX through the Azure management plane.

In this step, get ready to:
  • Create an Ubuntu VM on the protected subnet.
  • Install NGINX through Azure Run Command.
  • Compare VM health with browser reachability.
Deploy the protected VM

The VM joins the existing subnet-web subnet. Its traffic remains governed by the subnet-level nsg-web security group.

This VM Can Incur Charges

Creating this VM starts billable compute usage. The cleanup section removes the lab resources when you finish.

The managed OS disk can continue to incur storage charges after compute is deallocated. The public IP can also incur charges.

  • Deploy vm-web-01 to the protected subnet by running this command:
az vm create \
  --resource-group "$RG" \
  --name "$VM" \
  --image Ubuntu2204 \
  --size Standard_D2s_v5 \
  --admin-username azureuser \
  --generate-ssh-keys \
  --vnet-name "$VNET" \
  --subnet "$SUBNET" \
  --nsg "" \
  --public-ip-sku Standard \
  --tags Environment=Interview

What Does This Command Create?

  • The Ubuntu2204 image provides the operating system.
  • The Standard_D2s_v5 value sets the VM size.
  • The azureuser value names the administrative account.
  • The --generate-ssh-keys option creates or reuses SSH keys in ~/.ssh.
  • The existing virtual network variables place the VM on subnet-web.
  • The --nsg "" option prevents a second NSG from being created on the VM's network interface. The subnet-level nsg-web remains the only traffic guard.
  • The deployment assigns a Standard public IP. It also applies the Environment=Interview tag.
  • The VM receives a managed OS disk. This matches the policy assigned earlier.
  • Review the deployment result for vm-web-01.

Expect this deployment to take a few minutes while Azure provisions the VM. The result shows the new VM details when provisioning finishes.

Did the VM Deployment Fail?

  • Confirm that the Cloud Shell session still contains the RG variable.
  • Confirm that the session still contains the VM variable.
  • Check that subnet-web still exists inside vnet-cloud-interview.

Help me diagnose the VM deployment failure.

Install NGINX through the management plane

Azure Run Command delivers a script through the management plane. This path does not require an inbound remote-login rule.

  • Install NGINX on vm-web-01 by running this command:
az vm run-command invoke \
  --resource-group "$RG" \
  --name "$VM" \
  --command-id RunShellScript \
  --scripts "sudo apt-get update && sudo apt-get install -y nginx"

How Does Run Command Work?

  • Azure delivers the script through its management path.
  • The RunShellScript command ID executes the script on the Linux VM.
  • The first package operation refreshes the available package information.
  • The second package operation installs NGINX.
  • The subnet NSG continues to govern inbound application traffic.
  • Review the Run Command response for its completion status.

The response confirms that the script completed successfully. NGINX is now installed through the management plane.

Did Run Command Fail?

  • Confirm that the VM deployment finished before retrying Run Command.
  • Inspect the response for a package download failure.
  • Retry the same command after the VM reports a running state.

Help me troubleshoot the Run Command response.

Compare VM health with browser reachability

The final check compares Azure's view of the VM with the browser's view of the website. This separates compute health from network reachability.

Before you run this check, do you expect Azure to report the VM as running?

  • Store the VM's public address in VM_IP by running these commands:
export VM_IP=$(az vm show \
  --show-details \
  --resource-group "$RG" \
  --name "$VM" \
  --query publicIps \
  --output tsv)

echo "Try this address in your browser: http://$VM_IP"
az vm list --resource-group "$RG" --show-details --output table

What Does This Check Prove?

  • The first command reads the public address assigned to vm-web-01.
  • The VM_IP variable stores that address for later steps.
  • The second command prints the complete browser address.
  • The final command displays the VM's current Azure status.

Cloud Shell prints the browser address. The VM table shows vm-web-01 as running.

Before you test the printed address, do you expect the NGINX page to load through the current security boundary?

  • Copy the complete address printed by Cloud Shell.
  • Open a new browser tab.
  • Paste the copied address into the address bar.
  • Press Enter.

The Timeout Is the Intended Result

Your browser request times out. This planned failure is the result you need.

Run Command proved that NGINX installed successfully. The VM table proved that Azure sees vm-web-01 as running.

The remaining boundary is the subnet NSG. It still has no custom inbound HTTP rule for TCP port 80.

Did the Page Load Instead?

  • Confirm that you used the address printed by Cloud Shell.
  • Check nsg-web for a custom inbound rule on TCP port 80.
  • Remove any unexpected HTTP allow rule before repeating the browser test.

Help me investigate an unexpected browser result.

✔️ Awesome, I've got everything!

Your VM is running with NGINX installed. The browser timeout proves that the subnet NSG still controls inbound web traffic.

ⓧ I'd like to double check the full code

az vm create \
  --resource-group "$RG" \
  --name "$VM" \
  --image Ubuntu2204 \
  --size Standard_D2s_v5 \
  --admin-username azureuser \
  --generate-ssh-keys \
  --vnet-name "$VNET" \
  --subnet "$SUBNET" \
  --nsg "" \
  --public-ip-sku Standard \
  --tags Environment=Interview

az vm run-command invoke \
  --resource-group "$RG" \
  --name "$VM" \
  --command-id RunShellScript \
  --scripts "sudo apt-get update && sudo apt-get install -y nginx"

export VM_IP=$(az vm show \
  --show-details \
  --resource-group "$RG" \
  --name "$VM" \
  --query publicIps \
  --output tsv)

echo "Try this address in your browser: http://$VM_IP"
az vm list --resource-group "$RG" --show-details --output table

You have isolated the failure to the network boundary. Next, you will add the smallest web opening before monitoring and protecting the VM.

Open, Monitor, and Protect the VM

Your NGINX server is healthy. The subnet-level NSG is still blocking browser traffic.

The server now needs the smallest possible network opening. It also needs an alarm for abnormal CPU usage.

Azure Backup adds recovery protection if the VM is damaged or deleted. These controls turn the server into an operated workload.

In this step, get ready to:
  • Permit inbound HTTP traffic through nsg-web.
  • Create an email alert for high CPU usage.
  • Protect vm-web-01 with the Standard backup policy.
Allow only HTTP traffic

The default inbound deny rules remain your baseline protection. The allow-http rule creates one exception for web traffic on TCP port 80.

  • Switch back to the Azure Cloud Shell session from earlier.
  • Create the narrow HTTP exception by running this command:
az network nsg rule create \
  --resource-group "$RG" \
  --nsg-name "$NSG" \
  --name allow-http \
  --priority 200 \
  --access Allow \
  --protocol Tcp \
  --direction Inbound \
  --source-address-prefixes Internet \
  --source-port-ranges "*" \
  --destination-address-prefixes "*" \
  --destination-port-ranges 80

What Does This Rule Do?

  • The Internet source permits requests from public browsers.
  • The Tcp protocol matches HTTP traffic.
  • Destination port 80 limits the opening to the website.
  • Priority 200 evaluates this rule before the default inbound deny rule.

Before you refresh the timed-out page, do you expect the same request to reach NGINX now?

  • Return to the browser tab that timed out earlier.
  • Refresh http://$VM_IP.

You should see the NGINX welcome page. Good work. You resolved the planned network failure with one precise rule.

Still Seeing a Timeout?

Confirm that the browser address uses the public IP stored in VM_IP.

Check that allow-http uses port 80. Check that its direction is Inbound.

Help me diagnose the remaining timeout.

Create a CPU alert

Azure Monitor evaluates measurements from the VM. A metric alert compares those measurements with a threshold.

An action group defines where Azure sends the notification. Your action group uses an email receiver named Candidate.

  • Switch back to Azure Cloud Shell.
  • Replace replace-with-an-email-you-can-check with your reachable email address.
  • Set the email destination and capture the VM scope by running these commands:
export ALERT_EMAIL="replace-with-an-email-you-can-check"
export VM_ID=$(az vm show \
  --resource-group "$RG" \
  --name "$VM" \
  --query id \
  --output tsv)

What Do These Variables Hold?

  • The ALERT_EMAIL variable stores the notification destination.
  • The VM_ID variable stores the full Azure resource ID for vm-web-01.
  • The resource ID gives the alert an exact monitoring scope.

Cloud Shell returns to the prompt after capturing both values.

  • Create ag-cloud-interview and capture its resource ID by running:
export ACTION_ID=$(az monitor action-group create \
  --name ag-cloud-interview \
  --resource-group "$RG" \
  --action email Candidate "$ALERT_EMAIL" \
  --query id \
  --output tsv)

How Does the Action Group Work?

The action group stores your email destination under the receiver name Candidate. Its resource ID is stored in ACTION_ID.

The alert uses that resource ID to find its notification path.

The action group is now ready to receive alerts from vm-web-01.

  • Create the high-CPU metric alert by running:
az monitor metrics alert create \
  --name alert-vm-high-cpu \
  --resource-group "$RG" \
  --scopes "$VM_ID" \
  --condition "avg Percentage CPU > 90" \
  --window-size 5m \
  --evaluation-frequency 1m \
  --action "$ACTION_ID" \
  --description "Interview VM high CPU"

How Does the Alert Decide to Fire?

  • The scope limits the rule to vm-web-01.
  • The condition checks whether average CPU usage exceeds 90 percent.
  • The 5m window calculates the average over five minutes.
  • The 1m frequency evaluates the condition every minute.
  • The action connects the rule to ag-cloud-interview.

Azure returns the created alert configuration. Your VM now has a notification path for sustained high CPU usage.

Alert Creation Failed?

Confirm that you replaced the email placeholder before exporting ALERT_EMAIL.

Check that VM_ID identifies vm-web-01. Check that ACTION_ID identifies the action group.

Help me troubleshoot the monitoring setup.

Before you inspect the saved rule, which alert details do you expect Azure to report?

  • Inspect the finished metric alert by running:
az monitor metrics alert show \
  --name alert-vm-high-cpu \
  --resource-group "$RG" \
  --output table

What Does This Check Prove?

This command reads the saved alert from Azure Monitor. The table confirms that alert-vm-high-cpu exists in rg-cloud-interview.

You should see an enabled rule. The monitoring layer is now active.

Protect the VM with Azure Backup

A Recovery Services vault holds recovery points for protected resources. The vault must use the same region as the VM.

Backup Can Incur Costs

Azure Backup bills protected instances separately from backup storage. The cleanup section shows how to remove the protection data after this lab.

  • Switch back to the Azure portal tab from earlier.
  • Enter Resiliency in the portal search bar.
  • Open the Resiliency dashboard.
  • Select + Vault.
  • Choose Recovery Services vault.
  • Select Continue.

Why Must the Region Match?

Azure Backup requires the vault to use the same region as the protected data source. Selecting eastus makes vm-web-01 eligible for this vault.

  • Choose your active subscription.
  • Choose rg-cloud-interview as the resource group.
  • Enter rsv-cloud-interview as the vault name.
  • Choose eastus as the region.
  • Create the vault.

Expect the vault deployment to take a few minutes. Azure is provisioning the recovery service during this wait.

The portal confirms that rsv-cloud-interview is ready in the same resource group as the VM.

The protection workflow contains several similar choices. Each selection below narrows the workflow to Azure virtual machine backups.

  • Return to Resiliency.
  • Select + Configure protection.
  • Set Resource managed by to Azure.
  • Set Datasource type to Azure Virtual machines.
  • Set Solution to Azure Backup.
  • Select Continue.

What Do These Choices Mean?

The workflow now targets Azure virtual machines. Azure Backup stores their recovery points in your Recovery Services vault.

  • Choose the Standard policy.
  • Select Add.
  • Choose vm-web-01.
  • Select Enable backup.

Azure registers the VM with the vault. The Standard policy now controls its backup schedule.

Before you inspect the protection list, where do you expect vm-web-01 to appear?

  • Return to Resiliency.
  • Open Protected items.

You should see vm-web-01 registered under rsv-cloud-interview. That completes the operational layer for your web VM.

VM Missing From Protected Items?

Confirm that rsv-cloud-interview is in eastus.

Confirm that you selected the Standard policy before enabling backup. Allow the protection request time to finish processing.

Help me troubleshoot the missing protected VM.

Secret mission

Deploy Two VMs at Once

You already manage one protected web VM. This mission uses a Flexible Virtual Machine Scale Set to deploy two more VMs on your existing network from one Azure CLI command. You will inspect the shared model before deallocating both instances together.

Clean Up Your Resources

Clean Up Your Resources

Your Microsoft Azure lab can keep incurring charges while its resources remain. Choose whether to keep the lab available, pause its compute, or delete it entirely.

Cost warning

The running vm-web-01 VM continues to incur compute charges. The deallocated scale-set instances have stopped using billable compute.

Managed disks can still incur charges after deallocation. Public IP addresses and monitoring resources can also remain billable.

Azure Backup bills protected instances and backup storage separately. Budget notifications report thresholds without stopping consumption.

Resources you used:

  • Optional monthly notification budget at subscription scope.
  • The Cloud Interview Readers group in Microsoft Entra ID.
  • Tagged resource group rg-cloud-interview.
  • Reader role assignment scoped to rg-cloud-interview.
  • Policy assignment audit-vm-managed-disks.
  • Private storage account $STORAGE with its container and blob.
  • Network resources in vnet-cloud-interview.
  • Web VM vm-web-01 with its managed disk and public IP address.
  • Azure Monitor action group ag-cloud-interview with metric alert alert-vm-high-cpu.
  • Recovery Services vault rsv-cloud-interview with backup protection for vm-web-01.
  • Flexible Virtual Machine Scale Set vmss-web with two deallocated VM instances.

Keep everything running

Leave the lab in its current state if your interview or demonstration is soon. The web VM stays available while the scale set remains deallocated.

  • Monitor the subscription in Cost Management for continuing charges.
  • Leave vmss-web deallocated until you need its two instances.
  • Delete the lab immediately after your final demonstration.

Pause - I'll come back to this later

Free up the remaining VM compute while preserving the lab for later. Storage and protection resources stay in place.

  • Keep vmss-web in its existing deallocated state.
  • Deallocate vm-web-01 by running this command:
az vm deallocate --resource-group "$RG" --name "$VM"

What Does Deallocation Change?

Deallocation stops compute billing for vm-web-01. Its managed disk remains available for a later restart.

The public IP address and backup data can still incur charges. Pausing compute reduces costs without making the lab free.

  • Confirm that vm-web-01 shows Stopped (Deallocated) in the Azure portal.
  • Continue monitoring the subscription in Cost Management while the lab is paused.

Delete - I don't want to use this again

Deleting the lab is intentionally final. Your subscription stays active after the project resources are removed.

  • Return to Resiliency in the Azure portal.
  • Open Protection Inventory.
  • Open Protected items.
  • Select vm-web-01.
  • Stop backup protection for vm-web-01.
  • Delete the backup data for vm-web-01.

You should see no protected items remaining for vm-web-01. The vault can now be removed without leaving its recovery data behind.

  • Return to the rsv-cloud-interview vault in Resiliency.
  • Delete rsv-cloud-interview after confirming that no protected items remain.
  • Remove the optional subscription budget by running these commands if you created one:
budgetName='[[BUDGET_NAME="your budget name"]]'
az consumption budget delete --budget-name $budgetName

What Does This Command Remove?

The command removes the optional notification budget from the subscription. Resource cleanup still happens separately.

  • Return to Budgets in Cost Management + Billing.
  • Confirm that the deleted budget no longer appears.
  • Return to Microsoft Entra ID in the Azure portal.
  • Find the Cloud Interview Readers group.
  • Remove the Cloud Interview Readers group from Microsoft Entra ID.
  • Delete rg-cloud-interview and its contained resources by running this command in Cloud Shell:
az group delete --name "$RG" --no-wait --yes

What Does This Command Remove?

The resource group acts as the cleanup boundary for the lab. Deleting it removes the storage account and network resources.

The command also removes the VM and scale set. Their managed disks and contained monitoring resources are removed with the group.

  • Return to Resource groups in the Azure portal.
  • Refresh the list until rg-cloud-interview no longer appears.
  • Return to Microsoft Entra ID to confirm that Cloud Interview Readers no longer appears.
  • Check Cost Management after usage data updates to confirm that the lab no longer adds charges.

Nice Work!

Nice Work!

You did it! You built Cloud Interview Lab in Microsoft Azure from subscription signup through secure operations.

You've learned how to:

  • Govern the tagged resource group rg-cloud-interview with group-based Microsoft Entra ID access. You enforced scope through Azure RBAC. You audited resource configuration through Azure Policy.
  • Protect a private blob through Azure Storage access authenticated by Microsoft Entra ID. You proved that NGINX could run while a network security group blocked web traffic. You restored access with one HTTP rule.
  • Monitor vm-web-01 with an Azure Monitor CPU alert. You protected the VM with Azure Backup.
  • Secret Mission: Deployed two VM instances through the Flexible Virtual Machine Scale Set vmss-web. You also practiced deallocation as a shared lifecycle action.

Ready to quiz yourself?