Microsoft Azure from Zero to Professional: 100 Examples with CLI, Bicep and Python

Microsoft Azure from Zero to Professional: 100 Examples with CLI, Bicep and Python

Microsoft Azure is a cloud platform for running websites, databases, containers, data pipelines and AI services without buying servers. This article takes you from your first sign-in to a production-minded setup in 100 examples. You will learn the Azure CLI, resource groups and tags, infrastructure as code with Bicep, identity with managed identities and RBAC, virtual machines, App Service, containers and Kubernetes, storage with SAS and lifecycle rules, SQL and Cosmos DB, serverless Functions, virtual networks and private endpoints, Service Bus and Event Hubs, monitoring with Log Analytics and Application Insights, Key Vault, and cost control. Honesty note: the cloud commands and the SDK examples were not run, because they need an Azure subscription. The examples marked as run were executed on a computer and their real output is shown after the code.

What you need

An Azure account with a subscription (a free trial is enough for most examples), the Azure CLI, Python 3.10 or newer, and a text editor such as Visual Studio Code with the Bicep extension. Use a sandbox subscription for learning and never paste real secrets into commands or code. Costs apply to some resources, so delete the lab resource group when you finish.

How to use an example

Bash examples use the Azure CLI, Bicep examples use the Azure CLI or VS Code, and Python examples that need no Azure account run with python and show their real output. Examples that call Azure need a subscription before they will work.

Topic 1: Getting Started with Azure

Azure is organised as subscriptions, resource groups and resources. Learn to sign in, check where your commands will run, and use names that are easy to find and charge.

The Azure CLI and sign-in

The Azure CLI is one tool for every Azure service. Install it, run az login, and check az account show. Commands act on the subscription you have selected, so confirm it before you create or delete anything.

Regions and naming

Each resource lives in a region, so choose the one closest to your users and check that your services are available there. Storage names are strict: 3 to 24 lowercase letters and digits and unique across Azure.

Naming conventions

A pattern such as rg-app-env-region-01 keeps names consistent, so people can find resources and understand their cost. Build names with a small function and reuse it in every script.

Illustrations

How a typical Azure application fits together: users reach a web app through a load balancer, the app reads secrets from Key Vault and data from a database, and logs and metrics flow to Log Analytics
How a typical Azure application fits together: users reach a web app through a load balancer, the app reads secrets from Key Vault and data from a database, and logs and metrics flow to Log Analytics

Example 1: Install the Azure CLI and sign in

Set up the command-line tool and log in to your account.

winget install -e --id Microsoft.AzureCLI
az --version
az login

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 2: Check the active subscription

See which subscription your commands will change.

az account show --output table
az account list --query "[].{name:name, id:id, default:isDefault}" --output table

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 3: Create a resource group

A container for related resources in one region.

az group create --name rg-learn-dev --location southeastasia

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 4: List Azure regions

Find the regions you can deploy to.

az account list-locations --query "[?metadata.regionCategory=='Recommended'].name" --output tsv

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 5: Validate a storage account name

Storage names are 3 to 24 lowercase letters and digits.

import re

def valid_storage_name(name):
    return re.fullmatch(r"[a-z0-9]{3,24}", name) is not None

for candidate in ("stlearndev01", "Store-1", "ab", "a" * 25):
    print(repr(candidate), valid_storage_name(candidate))

Output:

'stlearndev01' True
'Store-1' False
'ab' False
'aaaaaaaaaaaaaaaaaaaaaaaaa' False

Example 6: Build a resource name from a convention

Consistent names make resources easy to find and charge.

def resource_name(kind, app, env, region, index=1):
    return f"{kind}-{app}-{env}-{region}-{index:02d}"

print(resource_name("rg", "shop", "dev", "sea"))
print(resource_name("app", "shop", "prod", "sea", 3))

Output:

rg-shop-dev-sea-01
app-shop-prod-sea-03

Topic 2: Resource Groups, Tags and CLI Output

A resource group holds the resources of one application or environment. Tags record who owns them and what they cost, and the CLI can filter and format its output for people or scripts.

Resource groups and deletion

Put related resources in one group so you can manage and delete them together. Deleting a group removes everything inside it, so use --no-wait only when you are sure, and never delete a group you did not create.

Tags for ownership and cost

Tags are key and value pairs such as owner and costcenter. Add them when you create resources and check for missing ones with a script. Cost reports can group spending by tag.

Output, queries and defaults

Use --output table for people and json for scripts. --query uses JMESPath to filter fields. Set default group and location with az configure so you do not repeat them.

Example 7: Tag a resource group

Tags record the owner, cost centre and environment.

az group update --name rg-learn-dev \
  --tags owner=team-a costcenter=1001 environment=dev

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 8: Check required tags

Find resources that are missing the tags your team needs.

REQUIRED = {"owner", "costcenter", "environment"}

def missing_tags(tags):
    return sorted(REQUIRED - set(tags))

print(missing_tags({"owner": "team-a", "environment": "dev"}))
print(missing_tags({"owner": "x", "costcenter": "1", "environment": "prod"}))

Output:

['costcenter']
[]

Example 9: Delete a resource group without waiting

Remove a whole group of resources in one command.

az group delete --name rg-learn-test --yes --no-wait

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 10: Pick the closest region by latency

Choose the region that answers fastest for your users.

latency_ms = {"southeastasia": 42, "japaneast": 71, "eastus": 210, "westeurope": 185}

best = min(latency_ms, key=latency_ms.get)
print("closest region:", best, latency_ms[best], "ms")

Output:

closest region: southeastasia 42 ms

Example 11: Filter output with a JMESPath query

Show only the fields and rows you need.

az vm list \
  --query "[?powerState=='VM running'].{name:name, rg:resourceGroup}" \
  --output table

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 12: Choose an output format

Table, JSON or YAML for people or for scripts.

az group list --output table
az group list --output json
az group list --output yaml

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 13: Loop over resources in a script

Run one command for every resource group.

for rg in $(az group list --query "[].name" -o tsv); do
  echo "resource group: $rg"
done

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 14: Set default group and location

Stop typing the same options in every command.

az configure --defaults group=rg-learn-dev location=southeastasia
az configure --list-defaults

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 3: Scripting and Automation

Repeated work belongs in a script. Loops, retries and parsed JSON turn one-off commands into reliable automation that a pipeline can run.

Loops over resources

A shell loop runs one command for every resource in a list. Keep the list small and check the result before you change anything.

Parsing JSON output

Ask the CLI for JSON and parse it instead of reading text. Group resources by type to produce a summary that is easy to read.

Retries and backoff

The Azure API throttles busy clients. Retry with an increasing wait, so the script recovers without flooding the service.

Example 15: Parse CLI JSON output

Turn a list of resources into a summary.

import json

sample = """[{"name": "vm-web-01", "type": "Microsoft.Compute/virtualMachines", "location": "southeastasia"},
           {"name": "stlearn01", "type": "Microsoft.Storage/storageAccounts", "location": "southeastasia"}]"""

resources = json.loads(sample)
by_type = {}
for r in resources:
    by_type.setdefault(r["type"].split("/")[-1], []).append(r["name"])
for kind, names in sorted(by_type.items()):
    print(kind, names)

Output:

storageAccounts ['stlearn01']
virtualMachines ['vm-web-01']

Example 16: Retry a flaky call with backoff

Wait longer after each throttled request.

import time

def call_with_retry(fn, attempts=4, base_delay=0.05):
    for n in range(attempts):
        try:
            return fn()
        except ConnectionError as e:
            if n == attempts - 1:
                raise
            delay = base_delay * (2 ** n)
            print(f"attempt {n + 1} failed ({e}); retrying in {delay:.2f}s")
            time.sleep(delay)

state = {"calls": 0}
def flaky_list():
    state["calls"] += 1
    if state["calls"] < 3:
        raise ConnectionError("throttled")
    return ["rg-learn-dev"]

print(call_with_retry(flaky_list))

Output:

attempt 1 failed (throttled); retrying in 0.05s
attempt 2 failed (throttled); retrying in 0.10s
['rg-learn-dev']

Example 17: Check the CLI version and extensions

Keep the tool and its extensions up to date.

az version
az extension list --output table
az extension add --name containerapp --upgrade

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 18: Create a service principal safely

An app identity for automation, with the smallest scope.

# Store the output securely: it contains a password. Never commit it to Git.
az ad sp create-for-rbac --name sp-learn-deploy --role Contributor \
  --scopes /subscriptions/<subscription-id>/resourceGroups/rg-learn-dev

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 4: Infrastructure as Code with Bicep

Bicep describes Azure resources as code. A template can be reviewed, versioned and deployed the same way every time, which is safer than clicking in the portal.

Resources, parameters and outputs

A resource declaration names the type and API version, and sets its properties. Parameters make a template reusable, and @secure keeps passwords out of logs. Outputs return values such as an ID to the caller.

Modules, conditions and loops

A module splits a large template into reusable files. A condition creates a resource only when it is true, such as in production. A loop creates several resources from one list.

Deploying safely

Use az deployment group what-if to preview changes, then create. Names built with uniqueString avoid clashes between environments in the same subscription.

Example 19: A minimal Bicep storage account

Describe a storage account as code.

param location string = resourceGroup().location
param storageName string

resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
  }
}

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 20: Parameters, outputs and secure values

Reuse templates and keep passwords out of the file.

@minLength(3)
@maxLength(24)
param storageName string

@allowed([
  'dev'
  'test'
  'prod'
])
param environment string = 'dev'

@secure()
param adminPassword string

output storageId string = storage.id
resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageName
  location: resourceGroup().location
  sku: { name: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS' }
  kind: 'StorageV2'
}

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 21: Deploy a module from main.bicep

Split a large template into reusable modules.

// main.bicep
targetScope = 'resourceGroup'
module network './modules/network.bicep' = {
  name: 'networkDeploy'
  params: {
    prefix: 'shop-dev'
  }
}
output vnetId string = network.outputs.vnetId

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 22: Conditional resources

Create a resource only in production.

param environment string

resource logs 'Microsoft.OperationalInsights/workspaces@2022-10-01' = if (environment == 'prod') {
  name: 'log-shop-prod'
  location: resourceGroup().location
  properties: {
    retentionInDays: 90
  }
}

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 23: Loop over containers in Bicep

Create several blob containers from one list.

param containerNames array = [
  'uploads'
  'exports'
  'backups'
]

resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' existing = {
  name: 'stlearndev01'
}

resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = [for name in containerNames: {
  name: '${storage.name}/default/${name}'
}]

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 24: Unique names with uniqueString

Generate a name that does not clash in the subscription.

param prefix string = 'shop'

var uniqueSuffix = uniqueString(resourceGroup().id)
var storageName = toLower('st${prefix}${uniqueSuffix}')

output generatedName string = storageName

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 25: Deploy a Bicep file

Create or update resources from the template.

az deployment group create \
  --resource-group rg-learn-dev \
  --template-file main.bicep \
  --parameters storageName=stlearndev01 environment=dev

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 26: Preview changes with what-if

See what a deployment will add, change or delete first.

az deployment group what-if \
  --resource-group rg-learn-dev \
  --template-file main.bicep \
  --parameters storageName=stlearndev01

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 5: Identity and Access Control

Security starts with who can do what. Managed identities give applications credentials without passwords, and role-based access control grants only the permissions each identity needs.

Managed identities

A managed identity is an account that Azure runs for your app, so the app gets tokens without storing a secret. Use user-assigned identities when several resources share one identity.

RBAC and least privilege

A role assignment connects an identity to a role at a scope, such as one storage account. Prefer built-in roles that match the job, and create a custom role only when none fits.

Service principals and tokens

A service principal is an identity for automation. Its password is a secret, so store it in the pipeline's vault. A token is a signed string with an expiry time, and you can read that time from its payload.

Example 27: A user-assigned managed identity

An identity your app uses without storing a password.

param location string = resourceGroup().location

resource appIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: 'id-shop-dev'
  location: location
}

output principalId string = appIdentity.properties.principalId

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 28: Grant read access with RBAC

Give an identity only the role it needs.

PRINCIPAL_ID=$(az identity show --name id-shop-dev --resource-group rg-learn-dev --query principalId -o tsv)
az role assignment create \
  --assignee-object-id "$PRINCIPAL_ID" \
  --assignee-principal-type ServicePrincipal \
  --role "Storage Blob Data Reader" \
  --scope /subscriptions/<subscription-id>/resourceGroups/rg-learn-dev/providers/Microsoft.Storage/storageAccounts/stlearndev01

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 29: Sign in a pipeline with a service principal

Automation logs in with secrets from the pipeline.

az login --service-principal \
  --username "$AZURE_CLIENT_ID" \
  --password "$AZURE_CLIENT_SECRET" \
  --tenant "$AZURE_TENANT_ID"

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 30: Check role assignments

Review who can do what on a resource.

assignments = [
    {"principal": "id-shop-dev", "role": "Storage Blob Data Reader", "scope": "stlearndev01"},
    {"principal": "sp-learn-deploy", "role": "Contributor", "scope": "rg-learn-dev"},
    {"principal": "id-shop-dev", "role": "Key Vault Secrets User", "scope": "kv-shop-dev"},
]

def roles_for(principal):
    return sorted(a["role"] for a in assignments if a["principal"] == principal)

print(roles_for("id-shop-dev"))
print(roles_for("nobody"))

Output:

['Key Vault Secrets User', 'Storage Blob Data Reader']
[]

Example 31: Sign in on a VM with its managed identity

A VM gets a token without any password.

az login --identity
az account show --query "user.assignedIdentityInfo" -o tsv

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 32: Read token expiry from a JWT

Tokens carry an expiry time in their payload.

import base64
import json

def jwt_payload(token):
    part = token.split(".")[1]
    part += "=" * (-len(part) % 4)
    return json.loads(base64.urlsafe_b64decode(part))

payload = {"aud": "https://management.azure.com/", "exp": 1767225600, "upn": "[email protected]"}
raw = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
token = f"header.{raw}.signature"

claims = jwt_payload(token)
print("audience:", claims["aud"])
print("expires at unix time:", claims["exp"])

Output:

audience: https://management.azure.com/
expires at unix time: 1767225600

Example 33: A least-privilege custom role

Allow only one action on one scope.

{
  "Name": "Blob Uploader",
  "IsCustom": true,
  "Description": "Can upload blobs to one storage account only.",
  "Actions": [],
  "DataActions": [
    "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write"
  ],
  "AssignableScopes": [
    "/subscriptions/<subscription-id>/resourceGroups/rg-learn-dev"
  ]
}

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 34: Create the custom role

Register the role definition in the subscription.

az role definition create --role-definition @blob-uploader.json

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 6: Compute: Virtual Machines and App Service

Azure runs your code on virtual machines or on managed platforms. App Service removes most server work, while a VM gives you full control of the operating system.

Virtual machines and scale sets

A VM has a size, an image, disks and a network interface. A scale set runs identical VMs and adds or removes them with demand. Turn dev machines off at night to save money.

App Service plans and web apps

A plan defines the machines and their price, and the web app runs on it. Deploy code as a zip file, set app settings for configuration, and use a deployment slot to test before users see a change.

Autoscaling decisions

Scaling rules compare a metric, such as average CPU, with thresholds, and add or remove an instance. A cooldown stops the system from flapping between sizes.

Example 35: Create a Linux VM

Start a virtual machine with a chosen size and image.

az vm create \
  --resource-group rg-learn-dev \
  --name vm-web-01 \
  --image Ubuntu2204 \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 36: List VM sizes and disks

Check capacity and storage before you resize.

az vm list-sizes --location southeastasia --output table | head -n 10
az vm disk list --resource-group rg-learn-dev --output table

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 37: A virtual machine scale set

Run identical VMs that grow and shrink together.

resource vmss 'Microsoft.Compute/virtualMachineScaleSets@2023-09-01' = {
  name: 'vmss-web'
  location: resourceGroup().location
  sku: {
    name: 'Standard_B2s'
    capacity: 2
  }
  properties: {
    upgradePolicy: { mode: 'Rolling' }
    // osProfile, networkProfile and storageProfile go here
  }
}

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 38: Auto-shutdown for a dev VM

Switch off a test machine every night to save money.

az vm auto-shutdown --resource-group rg-learn-dev --name vm-web-01 --time 2100

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 39: Create an App Service plan and web app

Host a web app on managed Linux servers.

az appservice plan create --name plan-shop-dev --resource-group rg-learn-dev --sku B1 --is-linux
az webapp create --name app-shop-dev-01 --resource-group rg-learn-dev \
  --plan plan-shop-dev --runtime "PYTHON:3.12"

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 40: Deploy code from a zip file

Upload the application package to the web app.

zip -r app.zip . -x "*.git*"
az webapp deploy --resource-group rg-learn-dev --name app-shop-dev-01 \
  --src-path app.zip --type zip

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 41: App settings and a staging slot

Test a new version before it reaches users.

az webapp config appsettings set --resource-group rg-learn-dev --name app-shop-dev-01 \
  --settings ENVIRONMENT=dev LOG_LEVEL=info
az webapp deployment slot create --resource-group rg-learn-dev --name app-shop-dev-01 --slot staging

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 42: Scale decisions from CPU

Add instances when busy and remove them when quiet.

def scale_decision(cpu_samples, instances, minimum=1, maximum=5):
    average = sum(cpu_samples) / len(cpu_samples)
    if average > 75 and instances < maximum:
        return instances + 1, f"scale out (avg CPU {average:.0f}%)"
    if average < 25 and instances > minimum:
        return instances - 1, f"scale in (avg CPU {average:.0f}%)"
    return instances, f"hold (avg CPU {average:.0f}%)"

print(scale_decision([82, 90, 78], 2))
print(scale_decision([10, 12, 15], 2))
print(scale_decision([40, 45, 50], 2))

Output:

(3, 'scale out (avg CPU 83%)')
(1, 'scale in (avg CPU 12%)')
(2, 'hold (avg CPU 45%)')

Topic 7: Containers: Registry, Container Apps and Kubernetes

Containers package an application with everything it needs. Azure stores the images in a registry and runs them in Container Apps or in Kubernetes, depending on how much control you need.

Azure Container Registry

A registry is a private store for images. Build the image in Azure with az acr build, so the build does not depend on your computer, and push the result to the registry.

Container Apps and AKS

Container Apps runs containers without managing a cluster and scales to zero. AKS is managed Kubernetes for larger systems. AKS can pull images from your registry with its managed identity, so no password is needed.

Tags and cleanup

Tag each image with the version, date and commit so you can roll back. Schedule a purge task to delete old untagged images and keep storage costs under control.

Example 43: Create a container registry

A private place to store container images.

az acr create --resource-group rg-learn-dev --name acrshopdev01 --sku Basic
az acr login --name acrshopdev01

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 44: Build and push an image

Build the image in Azure and store it in the registry.

az acr build --registry acrshopdev01 --image shop-api:1.0.0 .
az acr repository show-tags --name acrshopdev01 --repository shop-api -o table

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 45: Run a container app

Run a container without managing servers.

az containerapp env create --name cae-shop-dev --resource-group rg-learn-dev --location southeastasia
az containerapp create --name ca-shop-api --resource-group rg-learn-dev \
  --environment cae-shop-dev \
  --image acrshopdev01.azurecr.io/shop-api:1.0.0 \
  --target-port 8080 --ingress external --min-replicas 0 --max-replicas 3

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 46: Create an AKS cluster

A managed Kubernetes cluster that pulls from your registry.

az aks create \
  --resource-group rg-learn-dev \
  --name aks-shop-dev \
  --node-count 2 \
  --enable-managed-identity \
  --attach-acr acrshopdev01 \
  --generate-ssh-keys
az aks get-credentials --resource-group rg-learn-dev --name aks-shop-dev

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 47: Deploy to Kubernetes

Apply a manifest and wait for the rollout.

kubectl apply -f deployment.yaml
kubectl rollout status deployment/shop-api
kubectl get pods -l app=shop-api

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 48: A Kubernetes deployment manifest

Describe the pods, replicas and resources.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: shop-api
  template:
    metadata:
      labels:
        app: shop-api
    spec:
      containers:
        - name: shop-api
          image: acrshopdev01.azurecr.io/shop-api:1.0.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 49: Generate an image tag

Tag each image with the version, date and commit.

from datetime import date

def image_tag(version, commit, when):
    short = commit[:7]
    return f"{version}-{when.strftime('%Y%m%d')}-{short}"

print(image_tag("1.4.2", "9f3c1ab7e54d0a12", date(2026, 1, 15)))

Output:

1.4.2-20260115-9f3c1ab

Example 50: Purge old images with a task

Delete untagged images on a schedule to save storage.

az acr task create --name purge-old-images --registry acrshopdev01 \
  --cmd "acr purge --filter 'shop-api:.*' --ago 30d --untagged" \
  --schedule "0 1 * * *" --context /dev/null

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 8: Storage: Blobs, Queues and Access Control

Storage accounts hold files, messages and tables. Secure access with Entra ID, use short-lived SAS links when you must share, and let lifecycle rules move old data to cheaper tiers.

Blobs and naming paths

Blob containers hold files. Use paths that include the device and date so you can list and delete a day's data quickly. Keep public access off unless the content must be public.

Shared access signatures

A SAS link grants limited access for a short time. Give only the permissions needed, such as read, and choose the shortest expiry that works.

Lifecycle rules and queues

Lifecycle rules move blobs to cool or archive tiers and delete them after a set number of days. Queues hold messages for background workers, so a slow job does not block a web request.

Example 51: Create a storage account

Secure defaults: TLS 1.2 and no public blob access.

az storage account create \
  --name stlearndev01 \
  --resource-group rg-learn-dev \
  --location southeastasia \
  --sku Standard_LRS \
  --kind StorageV2 \
  --min-tls-version TLS1_2 \
  --allow-blob-public-access false

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 52: Blob paths with date partitions

Organise uploads by device and date so queries stay fast.

from datetime import datetime

def blob_path(device, when, filename):
    return f"telemetry/{device}/{when:%Y/%m/%d}/{when:%H%M%S}-{filename}"

print(blob_path("sensor-7", datetime(2026, 1, 15, 8, 30, 5), "batch.json"))

Output:

telemetry/sensor-7/2026/01/15/083005-batch.json

Example 53: Upload a blob

Copy a file into a container with the CLI.

az storage container create --name uploads --account-name stlearndev01 --auth-mode login
az storage blob upload --account-name stlearndev01 --container-name uploads \
  --name reports/2026-01.csv --file ./report.csv --auth-mode login

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 54: Upload from Python with Entra ID

Use your Azure identity instead of an account key.

from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

client = BlobServiceClient(
    account_url="https://stlearndev01.blob.core.windows.net",
    credential=DefaultAzureCredential(),
)
blob = client.get_blob_client(container="uploads", blob="reports/2026-01.csv")
with open("report.csv", "rb") as data:
    blob.upload_blob(data, overwrite=True)

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 55: Create a short-lived SAS

A link that expires and grants only read access.

EXPIRY=$(date -u -d "+1 hour" '+%Y-%m-%dT%H:%MZ')
az storage blob generate-sas --account-name stlearndev01 --container-name uploads \
  --name reports/2026-01.csv --permissions r --expiry "$EXPIRY" --auth-mode login --as-user --output tsv

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 56: Describe SAS permissions

Explain what a shared access signature allows and for how long.

from datetime import datetime, timedelta, timezone

def describe_sas(permissions, expiry):
    names = {"r": "read", "w": "write", "d": "delete", "c": "create"}
    allowed = ", ".join(names[p] for p in permissions)
    left = expiry - datetime(2026, 1, 15, 8, 0, tzinfo=timezone.utc)
    return f"allows {allowed} for {int(left.total_seconds() // 60)} minutes"

print(describe_sas("rw", datetime(2026, 1, 15, 9, 0, tzinfo=timezone.utc)))

Output:

allows read, write for 60 minutes

Example 57: Lifecycle rules for blobs

Move old data to cheaper storage and then delete it.

{
  "rules": [
    {
      "enabled": true,
      "name": "archive-telemetry",
      "type": "Lifecycle",
      "definition": {
        "filters": { "blobTypes": ["blockBlob"], "prefixMatch": ["telemetry/"] },
        "actions": {
          "baseBlob": {
            "tierToCool": { "daysAfterModificationGreaterThan": 30 },
            "delete": { "daysAfterModificationGreaterThan": 365 }
          }
        }
      }
    }
  ]
}

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 58: Queue storage for background work

Put jobs on a queue and let workers process them later.

az storage queue create --name jobs --account-name stlearndev01 --auth-mode login
az storage message put --queue-name jobs --content "resize:photo-42" \
  --account-name stlearndev01 --auth-mode login

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 9: Databases: Azure SQL and Cosmos DB

Azure offers a managed relational database and a globally distributed NoSQL database. Connect with identity instead of passwords, and always use parameters in queries.

Azure SQL

Azure SQL is a managed SQL Server database. Turn on Microsoft Entra ID authentication and private endpoints, so the database has no stored password and no public address.

Parameterised queries

Pass user input as parameters, never by joining text into SQL. The database then treats the input as data, which prevents SQL injection.

Cosmos DB and partition keys

Cosmos DB stores JSON documents in containers. The partition key decides how data is spread across machines. A key with many distinct values, such as customer ID, keeps the load balanced.

Example 59: Azure SQL server and database

A managed relational database with private networking.

az sql server create --name sql-shop-dev-01 --resource-group rg-learn-dev \
  --location southeastasia --enable-public-network false --enable-ad-only-auth \
  --external-admin-principal-type User --external-admin-name [email protected]
az sql db create --resource-group rg-learn-dev --server sql-shop-dev-01 \
  --name shopdb --service-objective S0

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 60: Connect to Azure SQL without a password

Entra ID authentication instead of a stored password.

import pyodbc

conn = pyodbc.connect(
    "Driver={ODBC Driver 18 for SQL Server};"
    "Server=tcp:sql-shop-dev-01.database.windows.net,1433;"
    "Database=shopdb;Encrypt=yes;"
    "Authentication=ActiveDirectoryDefault;"
)
cursor = conn.cursor()
cursor.execute("SELECT TOP 5 name FROM sys.tables")
print([row.name for row in cursor.fetchall()])

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 61: Parameterised queries

Pass values as parameters so input cannot change the query.

import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE orders (id INTEGER, customer TEXT, total REAL)")
conn.executemany("INSERT INTO orders VALUES (?, ?, ?)",
                 [(1, "ana", 120.0), (2, "ben", 75.5)])

customer = "ana' OR '1'='1"          # hostile input stays a plain value
rows = conn.execute("SELECT id, total FROM orders WHERE customer = ?", (customer,)).fetchall()
print("matches:", rows)
conn.close()

Output:

matches: []

Example 62: Create a Cosmos DB container

A globally distributed database with a partition key.

az cosmosdb create --name cosmos-shop-dev-01 --resource-group rg-learn-dev \
  --locations regionName=southeastasia failoverPriority=0
az cosmosdb sql database create --account-name cosmos-shop-dev-01 --resource-group rg-learn-dev \
  --name shop
az cosmosdb sql container create --account-name cosmos-shop-dev-01 --resource-group rg-learn-dev \
  --database-name shop --name orders --partition-key-path "/customerId"

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 63: Write an item to Cosmos DB

Store a JSON document in a container.

from azure.cosmos import CosmosClient
from azure.identity import DefaultAzureCredential

client = CosmosClient("https://cosmos-shop-dev-01.documents.azure.com:443/", DefaultAzureCredential())
container = client.get_database_client("shop").get_container_client("orders")
container.upsert_item({"id": "o-1001", "customerId": "ana", "total": 120.0})

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 64: Spread items with a partition key hash

A good key spreads data evenly across partitions.

import hashlib

def partition_for(key, partitions=4):
    digest = hashlib.sha256(key.encode("utf-8")).hexdigest()
    return int(digest, 16) % partitions

counts = [0, 0, 0, 0]
for customer in ["ana", "ben", "chai", "dao", "eve", "fon", "gus", "hana"]:
    counts[partition_for(customer)] += 1
print("items per partition:", counts)

Output:

items per partition: [4, 1, 1, 2]

Topic 10: Serverless with Azure Functions

Azure Functions run small pieces of code when something happens: an HTTP request, a timer, or a message in a queue. You pay for executions, and you do not manage servers.

Triggers and bindings

A trigger starts the function, such as an HTTP request or a timer. Bindings connect the function to other services, such as writing to a queue, so less connection code is needed.

Timers and cron schedules

A timer trigger uses a six-field NCRONTAB expression, with seconds first. The expression 0 */5 * * * * runs every five minutes.

Idempotent processing

Queues can deliver a message more than once. Record the IDs you have processed, and skip duplicates, so running a job twice does not charge a customer twice.

Example 65: Create a function app

Serverless code on the Consumption plan.

az storage account create --name stfuncshopdev01 --resource-group rg-learn-dev --sku Standard_LRS
az functionapp create --name func-shop-dev-01 --resource-group rg-learn-dev \
  --storage-account stfuncshopdev01 --consumption-plan-location southeastasia \
  --runtime python --runtime-version 3.12 --functions-version 4 --os-type Linux

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 66: An HTTP-triggered function

Run code when a web request arrives.

import azure.functions as func

app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)

@app.route(route="hello", methods=["GET"])
def hello(req: func.HttpRequest) -> func.HttpResponse:
    name = req.params.get("name", "world")
    return func.HttpResponse(f"Hello, {name}!", status_code=200)

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 67: A timer function with a cron schedule

Run code every five minutes without a server.

import logging
import azure.functions as func

app = func.FunctionApp()

@app.timer_trigger(schedule="0 */5 * * * *", arg_name="timer", run_on_startup=False)
def every_five_minutes(timer: func.TimerRequest) -> None:
    logging.info("checking sensors")

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 68: Next run of a cron expression

Work out when a schedule fires next.

def next_minutes(expression, current_minute):
    field = expression.split()[1]           # minute field, for example */5
    step = int(field.removeprefix("*/"))
    return step - (current_minute % step) if current_minute % step else step

for minute in (0, 3, 12, 14):
    print(f"at minute {minute:2d}: next run in {next_minutes('0 */5 * * * *', minute)} min")

Output:

at minute  0: next run in 5 min
at minute  3: next run in 2 min
at minute 12: next run in 3 min
at minute 14: next run in 1 min

Example 69: Publish a function app

Deploy the Python code to Azure Functions.

func azure functionapp publish func-shop-dev-01 --python

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 70: A queue-triggered function

Process each message and write the result to another queue.

import azure.functions as func

app = func.FunctionApp()

@app.queue_trigger(arg_name="msg", queue_name="jobs", connection="AzureWebJobsStorage")
@app.queue_output(arg_name="out", queue_name="done", connection="AzureWebJobsStorage")
def process(msg: func.QueueMessage, out: func.Out[str]) -> None:
    out.set(f"processed:{msg.get_body().decode('utf-8')}")

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 71: Idempotent message handling

Process a message once even if it arrives twice.

processed_ids = set()

def handle(message):
    if message["id"] in processed_ids:
        return "skipped duplicate"
    processed_ids.add(message["id"])
    return f"processed {message['id']}"

events = [{"id": "e1"}, {"id": "e2"}, {"id": "e1"}]
for event in events:
    print(handle(event))

Output:

processed e1
processed e2
skipped duplicate

Example 72: Key Vault references in app settings

Load a secret into the app without copying it.

az functionapp config appsettings set --name func-shop-dev-01 --resource-group rg-learn-dev \
  --settings "[email protected](VaultName=kv-shop-dev;SecretName=db-password)"

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 11: Networking: Virtual Networks, NSGs and Private Endpoints

A virtual network is your private network inside Azure. Subnets separate tiers, security rules limit traffic, and private endpoints keep services off the public internet.

Virtual networks and subnets

Choose an address range such as 10.10.0.0/16, then split it into subnets for web and data. Azure reserves five addresses in every subnet, so a /24 gives 251 usable addresses.

Network security groups

A network security group is a list of allow and deny rules with priorities. Allow only the ports you need, such as 443 for HTTPS, and deny the rest by default.

Private endpoints and DNS

A private endpoint gives a service a private IP address inside your network. A private DNS zone makes its name resolve to that address, so applications need no code changes.

Example 73: Virtual network and subnets

A private network with separate subnets for web and data.

az network vnet create --name vnet-shop-dev --resource-group rg-learn-dev \
  --address-prefix 10.10.0.0/16 --subnet-name snet-web --subnet-prefix 10.10.1.0/24
az network vnet subnet create --vnet-name vnet-shop-dev --resource-group rg-learn-dev \
  --name snet-data --address-prefixes 10.10.2.0/24

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 74: Network security group rule

Allow only HTTPS from the internet.

az network nsg create --name nsg-web --resource-group rg-learn-dev
az network nsg rule create --nsg-name nsg-web --resource-group rg-learn-dev \
  --name allow-https --priority 100 --direction Inbound --access Allow \
  --protocol Tcp --destination-port-ranges 443 --source-address-prefixes Internet

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 75: Private endpoint for storage

Reach the storage account only from inside your network.

az network private-endpoint create --name pe-storage --resource-group rg-learn-dev \
  --vnet-name vnet-shop-dev --subnet snet-data \
  --private-connection-resource-id $(az storage account show -n stlearndev01 -g rg-learn-dev --query id -o tsv) \
  --group-id blob --connection-name conn-storage

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 76: Private DNS zone

Resolve private endpoint names inside the virtual network.

az network private-dns zone create --resource-group rg-learn-dev \
  --name privatelink.blob.core.windows.net
az network private-dns link vnet create --resource-group rg-learn-dev \
  --zone-name privatelink.blob.core.windows.net --name link-shop \
  --virtual-network vnet-shop-dev --registration-enabled false

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 77: Public load balancer

Spread incoming traffic across several virtual machines.

az network public-ip create --name pip-lb --resource-group rg-learn-dev --sku Standard
az network lb create --name lb-shop --resource-group rg-learn-dev --sku Standard \
  --public-ip-address pip-lb --backend-pool-name be-web
az network lb probe create --lb-name lb-shop --resource-group rg-learn-dev \
  --name probe-https --protocol Tcp --port 443

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 78: Subnet size calculator

Count the usable addresses in a CIDR block.

import ipaddress

def usable_hosts(cidr):
    net = ipaddress.ip_network(cidr)
    return net.num_addresses - 5          # Azure reserves 5 addresses in every subnet

for block in ("10.10.1.0/24", "10.10.2.0/27", "10.10.0.0/16"):
    print(block, usable_hosts(block))

Output:

10.10.1.0/24 251
10.10.2.0/27 27
10.10.0.0/16 65531

Topic 12: Messaging and Streaming: Service Bus and Event Hubs

Services talk to each other through messages. Service Bus is for reliable business messages, and Event Hubs is for high-volume streams such as telemetry.

Service Bus queues

A queue holds messages until a receiver completes them. Complete a message only after you have processed it, so a crash leads to a retry instead of a lost order. Messages that fail too often go to a dead-letter queue.

Event Hubs and partitions

An event hub splits its stream into partitions that many readers can consume in parallel. Send events in batches, and keep each batch under the size limit.

Choosing between them

Use Service Bus when each message matters and must be processed exactly as intended, and Event Hubs when you collect large volumes of events and can process them in order within a partition.

Example 79: Service Bus queue

Reliable messaging between applications, with dead-lettering.

az servicebus namespace create --name sb-shop-dev --resource-group rg-learn-dev --location southeastasia --sku Standard
az servicebus queue create --namespace-name sb-shop-dev --resource-group rg-learn-dev \
  --name orders --max-delivery-count 5 --dead-lettering-on-message-expiration true

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 80: Send a Service Bus message

Put an order on a queue from Python.

from azure.identity import DefaultAzureCredential
from azure.servicebus import ServiceBusClient, ServiceBusMessage

with ServiceBusClient("sb-shop-dev.servicebus.windows.net", DefaultAzureCredential()) as client:
    with client.get_queue_sender("orders") as sender:
        sender.send_messages(ServiceBusMessage('{"orderId": "o-1001"}'))

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 81: Receive and complete a message

Remove a message only after you have processed it.

from azure.identity import DefaultAzureCredential
from azure.servicebus import ServiceBusClient

with ServiceBusClient("sb-shop-dev.servicebus.windows.net", DefaultAzureCredential()) as client:
    with client.get_queue_receiver("orders", max_wait_time=5) as receiver:
        for msg in receiver:
            print(str(msg))
            receiver.complete_message(msg)     # removes it only after processing succeeded

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 82: Event Hubs for telemetry

A stream that takes millions of events per second.

az eventhubs namespace create --name evhns-shop-dev --resource-group rg-learn-dev --location southeastasia --sku Standard
az eventhubs eventhub create --name telemetry --namespace-name evhns-shop-dev --resource-group rg-learn-dev \
  --partition-count 4 --message-retention 1

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 83: Send batches to Event Hubs

Group events into batches for efficiency.

import json
from azure.eventhub import EventData, EventHubProducerClient
from azure.identity import DefaultAzureCredential

producer = EventHubProducerClient(
    fully_qualified_namespace="evhns-shop-dev.servicebus.windows.net",
    eventhub_name="telemetry",
    credential=DefaultAzureCredential(),
)
with producer:
    batch = producer.create_batch()
    batch.add(EventData(json.dumps({"device": "sensor-7", "temp_c": 21.5})))
    producer.send_batch(batch)

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 84: Split messages by size limit

Keep each batch under the maximum size.

def make_batches(messages, limit_bytes=60):
    batches, current, size = [], [], 0
    for msg in messages:
        n = len(msg.encode("utf-8"))
        if current and size + n > limit_bytes:
            batches.append(current)
            current, size = [], 0
        current.append(msg)
        size += n
    if current:
        batches.append(current)
    return batches

events = [f'{{"id": {i}, "temp": 21.{i}}}' for i in range(6)]
for number, batch in enumerate(make_batches(events), start=1):
    print("batch", number, len(batch), "messages")

Output:

batch 1 2 messages
batch 2 2 messages
batch 3 2 messages

Topic 13: Monitoring: Log Analytics, Application Insights and Alerts

You cannot fix what you cannot see. Azure Monitor collects logs and metrics, Log Analytics lets you query them with KQL, and alerts tell the team when something is wrong.

Log Analytics and KQL

A Log Analytics workspace stores logs from many resources. The Kusto Query Language filters, summarises and charts them, such as counting failures per hour.

Application Insights

Application Insights traces requests, dependencies and custom events from your code. Use it to find which call is slow, not just that the app is slow.

Alerts without false alarms

A metric alert fires when a measure crosses a threshold. Require the condition to hold for several samples in a row, so a single spike does not wake the on-call engineer.

Example 85: Log Analytics workspace

A central place to query logs from every resource.

az monitor log-analytics workspace create --resource-group rg-learn-dev \
  --workspace-name log-shop-dev --location southeastasia --retention-time 30

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 86: KQL query for failures

Count failed requests per hour with Kusto Query Language.

AppRequests
| where TimeGenerated > ago(24h)
| where Success == false
| summarize failures = count() by bin(TimeGenerated, 1h), ResultCode
| order by TimeGenerated desc

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 87: Diagnostic settings

Send platform logs and metrics to the workspace.

az monitor diagnostic-settings create \
  --name diag-storage \
  --resource /subscriptions/<subscription-id>/resourceGroups/rg-learn-dev/providers/Microsoft.Storage/storageAccounts/stlearndev01 \
  --workspace log-shop-dev \
  --logs '[{"category":"StorageRead","enabled":true}]' \
  --metrics '[{"category":"Transaction","enabled":true}]'

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 88: Application Insights telemetry

Trace requests and custom events from Python code.

from opentelemetry import trace
from azure.monitor.opentelemetry import configure_azure_monitor

configure_azure_monitor(connection_string="InstrumentationKey=<key>;IngestionEndpoint=https://southeastasia-1.in.applicationinsights.azure.com/")
tracer = trace.get_tracer("shop")
with tracer.start_as_current_span("checkout") as span:
    span.set_attribute("order.total", 120.0)

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 89: Metric alert on response time

Notify the on-call team when the API is slow.

az monitor metrics alert create --name alert-slow-api --resource-group rg-learn-dev \
  --scopes /subscriptions/<subscription-id>/resourceGroups/rg-learn-dev/providers/Microsoft.Web/sites/app-shop-dev-01 \
  --condition "avg HttpResponseTime > 2" --window-size 5m --evaluation-frequency 1m \
  --action /subscriptions/<subscription-id>/resourceGroups/rg-learn-dev/providers/microsoft.insights/actionGroups/ag-oncall

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 90: Alert after several bad samples

Avoid false alarms by requiring a streak of bad readings.

def should_alert(samples, threshold=2.0, required=3):
    streak = 0
    for value in samples:
        streak = streak + 1 if value > threshold else 0
        if streak >= required:
            return True
    return False

print(should_alert([1.0, 2.5, 2.7, 2.9, 1.1]))
print(should_alert([2.5, 1.0, 2.5, 2.5, 1.0]))

Output:

True
False

Topic 14: Security: Key Vault, Secrets and Threat Protection

Secrets belong in a vault, not in code or logs. Key Vault stores them with access control, and Defender for Cloud watches your resources for threats.

Key Vault and access

Key Vault stores secrets, keys and certificates. Use role-based access, give each app read access to the secrets it needs only, and log every access.

Reading secrets safely

DefaultAzureCredential uses the managed identity in Azure and your own login on your computer, so the same code works in both places. Never print a secret, and never write it into a log.

Defender for Cloud

Defender for Cloud scores your security posture and turns on threat protection for VMs, storage and key vaults. Review its recommendations regularly.

Example 91: Key Vault and a secret

Store a password in a vault with access control.

az keyvault create --name kv-shop-dev --resource-group rg-learn-dev --location southeastasia \
  --enable-rbac-authorization true
az keyvault secret set --vault-name kv-shop-dev --name db-password --value "$DB_PASSWORD"

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 92: Read a secret from Python

Use DefaultAzureCredential to fetch a secret.

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

client = SecretClient(vault_url="https://kv-shop-dev.vault.azure.net/", credential=DefaultAzureCredential())
password = client.get_secret("db-password").value
# use the value, never print it

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 93: Redact secrets from logs

Hide passwords and keys before anything is written.

import re

def redact(text):
    text = re.sub(r"(password|secret|token)=\S+", r"\1=***", text, flags=re.IGNORECASE)
    text = re.sub(r"(AccountKey=)[^;\s]+", r"\1***", text)
    return text

print(redact("connecting with password=hunter2 to host"))
print(redact("AccountKey=abc123==;EndpointSuffix=core.windows.net"))

Output:

connecting with password=*** to host
AccountKey=***;EndpointSuffix=core.windows.net

Example 94: Defender for Cloud plans

Turn on threat protection for VMs, storage and key vaults.

az security pricing create --name VirtualMachines --tier Standard
az security pricing create --name StorageAccounts --tier Standard
az security pricing create --name KeyVaults --tier Standard

*This example was not run here because it needs an Azure subscription or Azure tools.*

Topic 15: DevOps, Cost and Governance

Production systems need a repeatable pipeline, a spending limit and rules that every new resource must follow. These practices keep Azure reliable and affordable as the team grows.

Pipelines and rollback

A pipeline tests every change and deploys from the main branch. Watch the error rate and latency after a release, and roll back when they cross your limits.

Budgets and cost estimates

A budget sets a monthly limit and sends alerts before you reach it. Estimate costs from hours and storage, then compare with the real bill and export daily reports.

Azure Policy

A policy checks resources against a rule, such as requiring an owner tag. A deny effect stops non-compliant resources from being created, which enforces the standards you agreed.

Example 95: Azure DevOps pipeline

Test on every push and deploy the web app from main.

trigger:
  branches:
    include: [main]

pool:
  vmImage: ubuntu-latest

steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: '3.12'
  - script: pip install -r requirements.txt && pytest
    displayName: Test
  - task: AzureWebApp@1
    inputs:
      azureSubscription: sc-shop-dev
      appName: app-shop-dev-01
      package: '$(System.DefaultWorkingDirectory)'

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 96: Monthly budget

Set a spending limit and alert before it is reached.

az consumption budget create --budget-name budget-dev --amount 200 \
  --category Cost --time-grain Monthly \
  --start-date 2026-01-01 --end-date 2026-12-31 \
  --resource-group rg-learn-dev

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 97: Estimate a monthly bill

Turn usage hours and storage into a rough cost.

def monthly_cost(hours_per_day, hourly_rate, storage_gb, storage_rate, days=30):
    compute = hours_per_day * days * hourly_rate
    storage = storage_gb * storage_rate
    return round(compute + storage, 2)

print(monthly_cost(10, 0.05, 120, 0.02))
print(monthly_cost(24, 0.05, 120, 0.02))

Output:

17.4
38.4

Example 98: Export cost data

Send daily cost reports to a storage account.

az costmanagement export create --name daily-costs \
  --scope "/subscriptions/<subscription-id>/resourceGroups/rg-learn-dev" \
  --storage-account-id /subscriptions/<subscription-id>/resourceGroups/rg-learn-dev/providers/Microsoft.Storage/storageAccounts/stlearndev01 \
  --storage-container exports --timeframe MonthToDate --recurrence Daily \
  --recurrence-period from=2026-01-01T00:00:00Z to=2026-12-31T00:00:00Z

*This example was not run here because it needs an Azure subscription or Azure tools.*

Example 99: Decide a rollback

Use error rate and latency to decide whether to roll back.

def should_roll_back(error_rate, previous_error_rate, latency_ms, latency_limit=800):
    if error_rate > 0.05:
        return True, "error rate above 5 percent"
    if error_rate > previous_error_rate * 2 and error_rate > 0.01:
        return True, "error rate doubled since the last release"
    if latency_ms > latency_limit:
        return True, "latency above limit"
    return False, "healthy"

print(should_roll_back(0.02, 0.01, 300))
print(should_roll_back(0.07, 0.01, 300))

Output:

(False, 'healthy')
(True, 'error rate above 5 percent')

Example 100: Require tags with Azure Policy

Deny new resources that have no owner tag.

az policy definition create --name require-owner-tag --display-name "Require owner tag" \
  --rules '{"if":{"field":"tags[owner]","exists":"false"},"then":{"effect":"deny"}}' --mode Indexed
az policy assignment create --name require-owner --policy require-owner-tag \
  --scope /subscriptions/<subscription-id>/resourceGroups/rg-learn-dev

*This example was not run here because it needs an Azure subscription or Azure tools.*

Practice Project: A Secure Web App with Storage and Monitoring

Build one small, secure setup that uses ideas from several sections. Write the Bicep and scripts yourself first, then compare with the examples. Delete the resource group when you finish to avoid charges.

1. Create a resource group with owner and environment tags, and check for missing tags with a script.

2. Write a Bicep template for a storage account with TLS 1.2 and no public blob access, and preview it with what-if before you deploy.

3. Create a user-assigned managed identity and give it the Storage Blob Data Reader role on the storage account only.

4. Deploy a web app that reads blobs with its managed identity, and add an alert when the average response time stays above two seconds.

5. Create a budget at 80 percent of your monthly limit, then delete the resource group and confirm the deletion in the portal.

Next steps

Next, study Azure architecture guidance, take a role-based certification such as AZ-900 or AZ-104, and practise with infrastructure pipelines in GitHub Actions or Azure DevOps. Run every command in a sandbox subscription first, and read the service documentation for the version you deploy.

Summary

Azure becomes a professional platform when you describe it as code, secure it with identities instead of passwords, monitor it, and control its cost. Through 100 examples, this article covered the Azure CLI, resource groups and tags, scripting, Bicep, identity and RBAC, virtual machines and App Service, containers and Kubernetes, storage, databases, serverless functions, networking, messaging, monitoring, Key Vault and security, and DevOps with cost governance. Build the secure web app in the lab, and change one setting at a time to learn how each part behaves.