Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · Azure

Azure Setup: Azure CLI, Login and a First Deployment

Install the Azure CLI on Ubuntu, sign in, deploy a locked-down VM in a resource group, and set up identities for automation without stored passwords.

3 min read

  • Azure CLI 2.55
  • Ubuntu 22.04 LTS
  • Microsoft Entra ID
  • Azure RBAC

The Azure CLI (az) is the quickest way to learn how Azure is organised and to script it. This post installs it, deploys a small virtual machine the way you would for a real workload (not wide open), and sets up identities for automation.

Versions in use on this date. Azure CLI 2.55 (released 5 December 2023) is the current release; 2.56 follows on 9 January. Azure Active Directory was renamed Microsoft Entra ID in July 2023, although the CLI command group is still az ad.

How Azure is organised

flowchart TB
  T[Entra ID tenant<br/>users groups apps] --> MG[Management group<br/>optional]
  MG --> SUB[Subscription<br/>billing and quota boundary]
  SUB --> RG[Resource group rg-demo-ci<br/>lifecycle container]
  RG --> VNET[Virtual network and subnet]
  RG --> NSG[Network security group]
  RG --> VM[Virtual machine<br/>managed disk and NIC]
  VNET --> VM
  NSG --> VM
  RBAC[Azure RBAC role assignment<br/>scope subscription or group] -.-> RG

A subscription is the billing and quota boundary. A resource group holds resources that share a lifecycle, so deleting the group deletes everything inside. Access is granted with Azure RBAC: a role (Reader, Contributor, or a custom role) assigned to a user, group or identity at a scope (management group, subscription, resource group or single resource). Resource groups also live in one region, but the resources inside can be elsewhere.

Install and sign in

curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
az version
az login                                   # opens a browser; use --use-device-code on servers
az account list -o table
az account set --subscription "<name or id>"
az account show --query "{name:name, id:id, tenant:tenantId}" -o table

The installer uses Microsoft’s package repository, so later updates come through apt upgrade (or run az upgrade). Extensions add features (az extension add --name resource-graph).

Create a resource group and a VM safely

az group create --name rg-demo-ci --location centralindia --tags owner=raviteja env=demo

az vm create --resource-group rg-demo-ci --name vm-demo \
  --image Ubuntu2204 --size Standard_B2s \
  --admin-username azureuser --generate-ssh-keys \
  --public-ip-sku Standard --nsg-rule NONE

az network nsg rule create --resource-group rg-demo-ci --nsg-name vm-demoNSG \
  --name allow-ssh-mine --priority 1000 --protocol Tcp --destination-port-ranges 22 \
  --source-address-prefixes "$(curl -s https://ifconfig.me)/32" --access Allow
az vm list -d -o table

By default az vm create opens SSH (port 22) to the whole internet. --nsg-rule NONE skips that, and the second command allows SSH only from your own address. For anything beyond a lab, use Azure Bastion or just-in-time access (Microsoft Defender for Cloud) so no management port is exposed at all. Use JMESPath to read output without extra tools:

az vm show -g rg-demo-ci -n vm-demo -d --query "{ip:publicIps, state:powerState}" -o table

Put a budget on the subscription (Cost Management) before experimenting, and use tags for owner, environment and cost centre so spend can be traced.

Clean up in one command

az group delete --name rg-demo-ci --yes --no-wait

Deleting the group removes the VM, disk, NIC, public IP and NSG together, so nothing is left billing.

Identities for automation

Automation should not use your own login.

Option Use when
Managed identity Code runs on Azure (VM, App Service, AKS): az login --identity, no secret exists
Workload identity federation CI outside Azure (GitHub Actions, GitLab): exchanges a short-lived token, no secret stored
Service principal with a secret Last resort, when neither above works

A service principal scoped to one resource group:

az ad sp create-for-rbac --name sp-ci --role Contributor \
  --scopes /subscriptions/<id>/resourceGroups/rg-demo-ci

The output contains a secret; treat it like a password, set a short expiry, and scope the role to a resource group, never the whole subscription. For GitHub Actions, prefer federation: create the app registration and add a federated credential that trusts one repository and branch:

az ad app create --display-name app-github-ci
az ad app federated-credential create --id <app-object-id> --parameters '{
  "name": "github-main",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:acme/shop-api:ref:refs/heads/main",
  "audiences": ["api://AzureADTokenExchange"] }'
az role assignment create --assignee <app-client-id> --role Contributor \
  --scope /subscriptions/<id>/resourceGroups/rg-demo-ci

The workflow then signs in with the azure/login action using the client, tenant and subscription IDs, with no stored secret.