The Azure CLI (az) is Microsoft's cross-platform command-line tool for creating and managing Azure resources. In this tutorial you will install it on an Ubuntu 24.04 server from Microsoft's official APT repository, sign in interactively and with a service principal for automation, select a subscription, and use it to create a resource group, a storage account and a blob upload that you can verify and clean up.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • A Microsoft Azure account with an active subscription, and the Owner or Contributor role on it (Owner, or User Access Administrator, is needed to create the service principal role assignment in Step 7).
  • A computer with a web browser to complete the device code sign-in.

Step 1 - Adding the Microsoft APT repository

Microsoft ships the Azure CLI as the azure-cli package in its own repository. Installing it this way means regular apt upgrade runs keep it current.

Install the packages needed to fetch and verify the repository key:

sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release

Download Microsoft's signing key, convert it to the binary keyring format and make it readable by APT:

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/keyrings/microsoft.gpg > /dev/null
sudo chmod go+r /etc/apt/keyrings/microsoft.gpg

Create the repository definition in deb822 format. lsb_release -cs returns noble on Ubuntu 24.04 and dpkg --print-architecture returns amd64 or arm64:

sudo tee /etc/apt/sources.list.d/azure-cli.sources > /dev/null <<EOF
Types: deb
URIs: https://packages.microsoft.com/repos/azure-cli/
Suites: $(lsb_release -cs)
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/microsoft.gpg
EOF

Step 2 - Installing the Azure CLI

Refresh the package index and install the package:

sudo apt update
sudo apt install -y azure-cli

Confirm that az works:

az version
{
  "azure-cli": "2.xx.0",
  "azure-cli-core": "2.xx.0",
  "azure-cli-telemetry": "1.1.0",
  "extensions": {}
}

The CLI sends anonymous usage data to Microsoft by default. If you prefer to turn it off:

az config set core.collect_telemetry=false

Step 3 - Signing in with a device code

On a server without a browser, use the device code flow. The CLI prints a short code and a URL that you open on any other device:

az login --use-device-code
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code ABCD1234 to authenticate.

Open the page, enter the code and sign in with your Azure account. When it finishes, recent CLI versions show the subscriptions you have access to and let you pick the default one by number. Your login token is cached under ~/.azure/, so you do not need to repeat this on every command.

Step 4 - Selecting the subscription

If your account has several subscriptions, make sure commands run against the right one. List them:

az account list --output table
Name                 CloudName    SubscriptionId                        TenantId                              State    IsDefault
-------------------  -----------  ------------------------------------  ------------------------------------  -------  -----------
Production           AzureCloud   00000000-0000-0000-0000-000000000000  11111111-1111-1111-1111-111111111111  Enabled  True
Development          AzureCloud   22222222-2222-2222-2222-222222222222  11111111-1111-1111-1111-111111111111  Enabled  False

Set the default by name or ID, then check it:

az account set --subscription "Development"
az account show --query "{name:name, id:id}" --output table
Name         Id
-----------  ------------------------------------
Development  22222222-2222-2222-2222-222222222222

Step 5 - Setting defaults and output format

Resource commands need a resource group and a location. Storing defaults saves typing and avoids creating resources in the wrong region by mistake. List available regions:

az account list-locations --query "[].name" --output tsv

Set a default location and a friendlier output format for interactive use:

az config set defaults.location=westeurope core.output=table

These settings are saved in ~/.azure/config. You can still override them per command with --location and --output.

Step 6 - Creating a resource group and a storage account

Every Azure resource lives in a resource group, and deleting the group deletes everything inside it, which makes cleanup easy. Create one for this tutorial and set it as the default group:

az group create --name tutorial-rg
az config set defaults.group=tutorial-rg
Location    Name
----------  -----------
westeurope  tutorial-rg

Storage account names must be globally unique, 3 to 24 characters, lowercase letters and digits only. Replace yourstorageacct with your own name:

az storage account create \
  --name yourstorageacct \
  --sku Standard_LRS \
  --kind StorageV2 \
  --min-tls-version TLS1_2 \
  --allow-blob-public-access false

Create a private blob container. With --auth-mode key, the CLI retrieves the account key through your signed-in session, so you do not have to copy it by hand:

az storage container create \
  --account-name yourstorageacct \
  --name backups \
  --auth-mode key

Create a small test file, upload it and list the container to verify it arrived:

echo "Uploaded from $(hostname) on $(date -I)" > /tmp/hello.txt
az storage blob upload \
  --account-name yourstorageacct \
  --container-name backups \
  --name hello.txt \
  --file /tmp/hello.txt \
  --auth-mode key
az storage blob list \
  --account-name yourstorageacct \
  --container-name backups \
  --auth-mode key \
  --query "[].{name:name, size:properties.contentLength}"
Name       Size
---------  ------
hello.txt  38

Step 7 - Creating a service principal for automation

Scripts, CI pipelines and cron jobs should not use your personal account. A service principal is an application identity with its own credentials and role assignments. Create one with the Contributor role limited to the tutorial resource group, not the whole subscription:

RG_ID=$(az group show --name tutorial-rg --query id --output tsv)
az ad sp create-for-rbac --name tutorial-automation --role Contributor --scopes "$RG_ID" --output json
{
  "appId": "33333333-3333-3333-3333-333333333333",
  "displayName": "tutorial-automation",
  "password": "generated_client_secret",
  "tenant": "11111111-1111-1111-1111-111111111111"
}

Store the password value securely. It is only shown once, and it expires (one year by default).

To sign in as the service principal, read the secret into a variable so it does not end up in your shell history:

read -rsp "Client secret: " AZ_SP_SECRET; echo
az login --service-principal \
  --username 33333333-3333-3333-3333-333333333333 \
  --password "$AZ_SP_SECRET" \
  --tenant 11111111-1111-1111-1111-111111111111
unset AZ_SP_SECRET

Verify which identity is active:

az account show --query "user" --output json
{
  "name": "33333333-3333-3333-3333-333333333333",
  "type": "servicePrincipal"
}

Because the role is scoped to tutorial-rg, this identity can manage resources inside that group but cannot see or change anything else in the subscription.

Step 8 - Filtering output for scripts

The global --query flag takes a JMESPath expression and --output tsv prints plain values, which is the combination to use in shell scripts. For example, get the resource IDs of every VM in a group:

az vm list --resource-group tutorial-rg --query "[].id" --output tsv

Most commands accept --ids, so you can act on all of them at once. This deallocates every VM in the group (and stops compute billing for them):

az vm deallocate --ids $(az vm list --resource-group tutorial-rg --query "[].id" --output tsv)

The $(...) is intentionally unquoted here so each ID becomes a separate argument; resource IDs never contain spaces.

Step 9 - Cleaning up

Sign back in with your own account if you switched to the service principal, then delete the resource group. This removes the storage account and everything else created in this tutorial:

az login --use-device-code
az group delete --name tutorial-rg --yes

Remove the service principal's app registration as well:

az ad sp list --display-name tutorial-automation --query "[].appId" --output tsv
az ad app delete --id 33333333-3333-3333-3333-333333333333

Keeping the Azure CLI updated

Because you installed from the Microsoft repository, updates arrive with the rest of your packages:

sudo apt update
sudo apt install --only-upgrade azure-cli

Troubleshooting

Please run 'az login' to setup account: there is no cached token or it expired. Sign in again with az login --use-device-code. If a stale session keeps failing, run az account clear first.

(AuthorizationFailed) The client ... does not have authorization to perform action: the active identity lacks a role on that scope. Check which identity you are using with az account show and its roles with az role assignment list --assignee <id> --all --output table.

(SubscriptionNotFound) or resources that seem to be missing: the wrong subscription is active. Run az account show and switch with az account set --subscription.

The storage account named ... is already taken: storage names are global across all Azure customers. Choose a more specific name.

Conclusion

You installed the Azure CLI from Microsoft's repository on Ubuntu 24.04, signed in with a device code, selected a subscription and defaults, created and verified storage resources, and set up a scoped service principal for automation. From here you could schedule a backup upload with a systemd timer, provision VMs with az vm create, or describe your infrastructure declaratively with Bicep using az deployment group create.