Azure foundations
Azure makes sense once you notice that it splits apart what AWS bundles together. An AWS account is identity, billing, quota, and isolation in one object. Azure spreads those jobs across a hierarchy: the Entra tenant holds identity, the subscription holds the bill and the quotas, and the resource group holds the things you deploy and delete as one unit. This page builds that map, shows how every tool funnels through Azure Resource Manager, and ends with a lab that creates and destroys a real environment in a handful of commands.
1 · The hierarchy, top to bottom
Everything in Azure lives at a known address inside a five-level tree, and the tree is worth memorising before any individual service, because almost every confusing error message is really a question about which level you are standing on. The levels, from the top: Entra tenant, management group, subscription, resource group, resource.
The Entra tenant (the service was called Azure Active Directory until 2023)
is the identity boundary. It is a directory: users, groups, applications, and the policies
that govern how they sign in. A company typically has exactly one. The tenant is not a
billing object and it does not contain resources directly; what it does is vouch for
identities. Every subscription trusts exactly one tenant, which means every API call against
anything in that subscription is authenticated by that tenant's directory. The tenant is
identified by a GUID and by domain names you attach to it, starting with the default
something.onmicrosoft.com. How identity itself works — users, service
principals, managed identities, RBAC — is the subject of the next page,
Entra ID. For now: tenant = who you are.
Management groups sit between the tenant and subscriptions, and exist for one
reason: applying governance to many subscriptions at once. You can nest them up to six levels
deep under an automatically created root management group, arrange subscriptions into a tree
(platform, workloads, sandbox), and assign Azure Policy and RBAC roles at any node. The
assignment inherits down. A policy assigned at the root ("every resource must have an
owner tag", "no public IPs in this subtree") applies to every subscription below
it without being repeated. Small estates can ignore management groups entirely; large ones
are governed almost entirely through them.
The subscription is the billing and quota boundary. Every resource belongs to exactly one subscription; the subscription accumulates the charges and produces the invoice line items. Quotas are also scoped here: the number of vCPUs of a given VM family you can run is a per-subscription, per-region number, and a new subscription often starts with a default as low as ten. A subscription is also an RBAC scope (grant someone Reader on the subscription and they can see everything in it) and a policy scope. What it is not is a hard network or identity boundary: two subscriptions in the same tenant share the directory, and their networks can be connected with ordinary peering.
The resource group is the level Azure adds that the other clouds make you improvise. It is a lifecycle unit: a container for resources that you deploy together, update together, and — this is the point — delete together. Deleting a resource group deletes everything inside it, in dependency order, as one operation. A resource group cannot be nested or renamed, every resource lives in exactly one, and the group itself has a location. That location is only where the group's metadata is stored; the resources inside can be in any region, although mixing regions in one group is usually a smell. The mental test for what goes in a group: "if I deleted all of this at once, would that be a sensible operation?" An application environment, yes. "All our databases", no.
At the bottom, the resource: a VM, a storage account, a virtual network, a database. Every resource has a full ID that spells out its position in the tree — subscription, resource group, provider namespace, type, name — and that ID is what RBAC role assignments, locks, and diagnostic settings attach to.
2 · How this maps to AWS and GCP
If you arrive from AWS, the instinct is to ask "what is the Azure equivalent of an account?", and the honest answer is: nothing, because an AWS account does four jobs that Azure assigns to three different levels. The account is the identity store (IAM lives inside it), the bill, the quota scope, and the hard isolation boundary. Azure's split: identity goes to the tenant, billing and quota go to the subscription, lifecycle goes to the resource group. Isolation is the subtle one — a subscription is a softer boundary than an AWS account, because identities are tenant-wide. There is no equivalent of cross-account role assumption ceremonies inside one tenant; you grant a user or service principal a role at a scope, and that is the whole story.
GCP sits in between. A GCP project is roughly a subscription and a resource group fused into one object: it is a quota scope and a billing attachment point like a subscription, but it is also the container you create and delete around a workload, like a resource group. GCP folders map well to management groups, and the GCP organization maps to the tenant. The practical consequence: GCP nudges you toward many small projects, AWS toward many accounts, and Azure toward fewer subscriptions with many resource groups inside them.
One more difference worth internalising early: AWS "resource groups" exist, but they are saved tag queries — views, not containers. An Azure resource group is a real container with a real delete operation. That single feature changes day-to-day habits. On Azure you stop thinking "I'll remember to clean these six things up" and start thinking "everything for this experiment goes in one group, and the teardown is one command". The lab at the end of this page is built around exactly that.
| Job | Azure | AWS | GCP |
|---|---|---|---|
| Identity boundary | Entra tenant | Account (IAM) | Organization / Cloud Identity |
| Policy inheritance tree | Management groups | Organizations + OUs | Folders |
| Billing + quota | Subscription | Account | Project (+ billing account) |
| Deploy/delete-together unit | Resource group | — (tags, CloudFormation stacks at best) | Project, loosely |
| Hard isolation | Tenant (strong), subscription (soft) | Account | Project (soft), org (strong) |
3 · ARM: the single front door
Every management operation in Azure — portal click, CLI command, Bicep deployment, Terraform
apply, SDK call — goes through one API: Azure Resource Manager, at
management.azure.com. There is no side door. The portal is a web client of ARM,
the az CLI is a command-line client of ARM, Bicep and ARM templates are
declarative documents that ARM executes, and Terraform's azurerm provider is a
wrapper over the same REST endpoints. This is one of Azure's better design decisions, and it
has consequences you feel daily.
First consequence: anything you can do in the portal you can do in the CLI or a template, because they are the same calls. The portal even shows you the template it is about to submit ("Download a template for automation" on the review screen), which is a decent way to learn the declarative shape of a resource. Second: there is one audit trail. The activity log records every ARM call regardless of which client made it, so "who deleted that VM" has an answer. Third: governance is enforced in one place. RBAC, Azure Policy, and resource locks are evaluated by ARM on every request, so a deny means deny from every tool, not just the one you remembered to configure.
ARM is also a deployment engine, not just a request router. You can hand it a template — ARM
JSON or, far more pleasantly, Bicep, which compiles to ARM JSON — describing the desired set
of resources, and it works out ordering and parallelism and converges the target scope to
that state. Deployments are idempotent PUTs: submit the same template twice and the second
run is a no-op. A what-if mode shows the diff before you commit. Deployments can
target any scope in the tree: a resource group (the common case), a subscription, a
management group, or the tenant.
yourname.blob.core.windows.net) — the data
plane — with its own authentication options and its own availability story. ARM being slow or
briefly unavailable does not stop your blobs from being served. The same split exists in AWS;
Azure just makes the control plane unusually uniform.4 · Resource providers and registration
ARM itself does not know how to create a VM. The work is done by resource
providers: per-service backends with namespaces like Microsoft.Compute,
Microsoft.Storage, Microsoft.Network, and a couple of hundred
others. Every resource type is named namespace/type —
Microsoft.Storage/storageAccounts, Microsoft.Compute/virtualMachines
— and every API call against a type carries an explicit api-version, which is
how Azure evolves service APIs without breaking old templates.
The part that bites newcomers: providers must be registered per subscription
before you can use them. A fresh subscription has only a baseline set registered, and the
first attempt to create a resource from an unregistered namespace fails with
MissingSubscriptionRegistration. The portal registers providers silently as you
use it, which is why the error tends to appear only when you switch to the CLI or CI. The fix
is one command, az provider register --namespace Microsoft.Storage, and
registration takes a minute or two. Terraform's azurerm provider registers a
large set automatically by default, which papers over the issue until someone turns that
behaviour off for least-privilege reasons and the pipeline starts failing in a fresh
subscription. Worth knowing before it happens to you.
5 · Regions, region pairs, and availability zones
Azure has more regions than any other cloud — sixty-plus and growing — which sounds like an advantage until you learn the catch: regions vary widely in what they offer. Not every region has every service, not every region has availability zones, and capacity for popular VM families can be tight in smaller ones. Region choice on Azure is a real decision, not a default. A region is a set of data centres in one metro area; within zone-enabled regions, those data centres are grouped into three or more availability zones with independent power, cooling, and networking, connected by low-latency links (round trips within a region's zones are well under two milliseconds).
Zones show up in two flavours when you deploy. A zonal resource is pinned to one zone — a VM in zone 2 — and you build redundancy by placing replicas across zones yourself. A zone-redundant resource is spread across zones by the platform: zone-redundant storage, a zone-redundant load balancer or SQL database. One subtlety inherited from AWS's playbook: zone numbers are logical and shuffled per subscription. Your zone 1 and a colleague's zone 1 in a different subscription are not necessarily the same physical facility, so cross-subscription zone alignment needs the physical-zone mapping API rather than the labels.
The distinctly Azure idea is the region pair. Most regions are statically paired with a second region in the same geography: West Europe with North Europe, East US with West US, and so on. The pairing is not decoration; three real behaviours hang off it.
First, geo-redundant storage (GRS) replicates your data to the paired region asynchronously, and you do not get to pick the destination — the pair is the destination. Create a GRS storage account in West Europe and your secondary copy is in North Europe, full stop. Second, platform updates are sequenced across pairs: Microsoft avoids rolling the same maintenance to both halves of a pair simultaneously, so a bad update cannot take out your primary and your DR region in one wave. Third, in a catastrophic multi-region event, one region from each pair is prioritised for restoration. Pairs stay within a data residency geography with one or two historical exceptions (Brazil South pairs with South Central US, which matters if your compliance story assumes data never leaves the country).
One caveat for new deployments: several recent regions launched without a pair at all, and Microsoft's current guidance leans on availability zones within a region as the primary resilience tool, with paired-region DR as an extra layer where it exists. If your design depends on GRS or on paired-update sequencing, check that your chosen region actually has a pair before committing.
6 · Names and tags: decide once, early
Naming on Azure deserves ten deliberate minutes because most resources cannot be renamed.
You delete and recreate, which for a storage account or database is a migration, not a
rename. The uniqueness scope also varies by type: a VM name only has to be unique within its
resource group, but a storage account name must be unique across all of Azure, because the
name becomes a public DNS label (name.blob.core.windows.net). Storage accounts
are also the strictest format: three to twenty-four characters, lowercase letters and digits
only, no hyphens. Key Vault and a handful of other DNS-fronted services are globally unique
too.
The convention most teams adopt is the Cloud Adoption Framework pattern: a short type
abbreviation, the workload, the environment, the region, and an instance number, separated
by hyphens where the type allows it. rg-payments-prod-weu-001 for a resource
group, vnet-payments-prod-weu-001 for a network, kv-payments-prod-weu
for a vault, and the hyphen-free squeeze stpaymentsprodweu001 for storage. The
exact scheme matters less than having one before the first resource is created, because a
subscription with four competing conventions never recovers.
Tags are the other half of housekeeping: free-form key-value pairs (up to
fifty per resource) that drive cost reporting, ownership lookup, and automation. The
standard starter set is env, owner, costCenter, and
something like workload. Two facts to know before you rely on them. Tags are
not inherited: tagging a resource group does nothing to the resources inside it
unless you assign an Azure Policy with the "inherit tag from resource group" effect, which
well-run estates do. And tags are only as good as their enforcement — a policy that denies
resource creation without an owner tag is worth a hundred wiki pages asking
people nicely.
7 · The az CLI: enough to be dangerous
The CLI is az, and its grammar is consistent enough to guess:
az <service> <noun> <verb>, as in
az storage account create or az vm list. Three habits cover most of
daily life. az login opens a browser to authenticate against your tenant (use
az login --use-device-code on a remote box); the session is cached locally and
refreshed silently. az account show tells you which subscription you are
pointed at, and az account set --subscription <name-or-id> changes it —
run the first before anything that creates resources, because after login the CLI picks a
default subscription for you and it is not always the one you meant. And
--output table turns the default JSON wall into something readable, while
--query takes a JMESPath expression when you want a specific field for a
script.
One more convenience worth setting up:
az configure --defaults group=rg-lab-dev-weu-001 location=westeurope lets you
drop the --resource-group and --location flags for a working
session. Useful in a lab, slightly dangerous in real work, since a forgotten default is how
resources end up in the wrong group. The CLI runs identically in Cloud Shell — the terminal
built into the portal — which is the zero-install way to follow the lab below.
8 · Subscription patterns and landing zones
How many subscriptions should you have? More than one, fewer than AWS would push you to. The floor for anything serious is per-environment: production and non-production at minimum, because the three things a subscription scopes — billing, quota, and broad RBAC — are exactly the three things you want separated between prod and everything else. A load test in dev cannot eat production's vCPU quota if dev is a different subscription; the production bill is legible without tag archaeology; and "Contributor on the dev subscription" is a sane grant in a way that "Contributor on our only subscription" is not. Larger estates go further and give each major workload its own subscription per environment, treating subscriptions as scale units that get stamped out on demand.
Microsoft's reference architecture for organising all of this is the Azure landing zone. Stripped to its skeleton: a management group tree with a platform branch (separate subscriptions for identity, management/monitoring, and connectivity — the hub networks live in that last one) and a landing-zones branch where workload subscriptions are created, each landing pre-wired with policy guardrails, logging, and network plumbing inherited from the management groups above it. New workload teams get a fresh subscription vended into the tree and inherit the guardrails on arrival rather than having governance retrofitted later. You do not need to implement the full pattern to benefit from its two ideas: govern at the management-group level, and treat subscriptions as disposable, stampable units rather than precious pets. The networking side of the hub-and-spoke arrangement is covered in virtual networks.
9 · The decoder table
Most Azure services are familiar ideas wearing different names. The table below is the ten-second translation layer; the rest of this series and the provider-neutral pages in the cloud codex fill in where the behaviour genuinely differs rather than just the label.
| Azure | AWS | GCP | What it is |
|---|---|---|---|
| Virtual Machines | EC2 | Compute Engine | VMs |
| Blob Storage | S3 | Cloud Storage | Object storage |
| Virtual Network | VPC | VPC | Private networks |
| Entra ID | IAM + IAM Identity Center | Cloud Identity + IAM | Identity and access |
| Azure Functions | Lambda | Cloud Run functions | Serverless functions |
| AKS | EKS | GKE | Managed Kubernetes |
| Azure SQL Database | RDS / Aurora | Cloud SQL / AlloyDB | Managed relational DB |
| Cosmos DB | DynamoDB | Firestore / Bigtable | Managed NoSQL |
| Service Bus / Event Grid | SQS + SNS / EventBridge | Pub/Sub / Eventarc | Queues and events |
| Key Vault | KMS + Secrets Manager | Cloud KMS + Secret Manager | Keys and secrets |
| Azure Monitor | CloudWatch | Cloud Monitoring | Metrics, logs, alerts |
| Front Door / CDN | CloudFront | Cloud CDN + global LB | Edge delivery |
Two translation traps. Entra ID is broader than AWS IAM: it is the corporate directory (closer to IAM Identity Center plus a SAML/OIDC identity provider) and Azure RBAC handles the resource-permission half. And Cosmos DB is one multi-model service wearing several database APIs, so its row in any comparison table is an approximation.
10 · Lab: one resource group, cradle to grave
Fifteen minutes, pennies at most if you tear down at the end, and it exercises the whole point of this page: the resource group as the unit of creation and destruction. You need a subscription you can experiment in (the free tier is fine) and either the CLI installed locally or Cloud Shell in the portal.
- Sign in and check where you are. Never skip the second command; the CLI
picks a default subscription after login and it may not be the one you expect.
az login az account show --output table # wrong subscription? list them and switch: az account list --output table az account set --subscription "My Lab Subscription" - Create the resource group. One call, immediate, free. Tag it at birth.
az group create \ --name rg-lab-dev-weu-001 \ --location westeurope \ --tags env=dev owner=you purpose=lab - Deploy a storage account into it. The name must be globally unique,
lowercase, no hyphens — hence the random suffix. Standard_LRS is the cheapest SKU and an
empty account costs effectively nothing.
STORAGE_NAME="stlabdev$RANDOM$RANDOM" az storage account create \ --name "$STORAGE_NAME" \ --resource-group rg-lab-dev-weu-001 \ --location westeurope \ --sku Standard_LRS \ --kind StorageV2 - Look at what exists. The first command lists everything in the group; the
second pulls one field with a JMESPath query, the pattern you will use in every script.
az resource list --resource-group rg-lab-dev-weu-001 --output table az storage account show \ --name "$STORAGE_NAME" \ --resource-group rg-lab-dev-weu-001 \ --query "primaryEndpoints.blob" --output tsv - Read a resource ID. Notice how the ID spells out the whole hierarchy from
section 1 — subscription, resource group, provider namespace, type, name.
az storage account show \ --name "$STORAGE_NAME" \ --resource-group rg-lab-dev-weu-001 \ --query "id" --output tsv # /subscriptions/<sub-id>/resourceGroups/rg-lab-dev-weu-001/ # providers/Microsoft.Storage/storageAccounts/stlabdev12345 - Tear it all down with one command. This is the payoff. The group and
everything inside it goes, in dependency order, no checklist required.
az group delete --name rg-lab-dev-weu-001 --yes --no-wait # confirm (returns "false" once the delete completes): az group exists --name rg-lab-dev-weu-001
az group list --output table shows
a group you cannot explain, that is the cleanup list. On AWS the equivalent sweep means
checking a dozen service consoles; on Azure it is one column of names.11 · What breaks
MissingSubscriptionRegistrationon first deploy. The resource provider is not registered in this subscription.az provider register --namespace Microsoft.Whatever, wait a minute, retry. Common in fresh subscriptions and CI pipelines that never went through the portal.- Resources created in the wrong subscription. The CLI's default subscription after
az loginis whichever it found first, not the one you wanted.az account showbefore creating anything;az account setto fix. - Storage account name rejected or taken. Globally unique, 3–24 characters, lowercase letters and digits only.
my-storagefails on the hyphens;teststoragefails because someone claimed it in 2013. Random suffixes are not laziness, they are correctness. - Quota errors on the first real VM. vCPU quota is per family, per region, per subscription, and new subscriptions start very low. The error names the quota; request the increase in the portal before launch day, since approval is fast for small jumps and slow for big ones.
- Group delete blocked by a lock. A
CanNotDeleteorReadOnlylock anywhere in the group stops the whole deletion. That is the feature working — locks exist so a prod group cannot be removed in one keystroke — but you need Owner-level rights to remove the lock before retrying. - Recreating a deleted Key Vault fails on the name. Vaults soft-delete by default and the name stays reserved for the retention window. Purge the soft-deleted vault (if purge protection allows) or pick a new name. The same applies to a few other services with soft delete.
- Confusing the resource group's location with the resources' location. The group's location only pins its metadata. Resources inside specify their own region, and a typo can quietly put your database an ocean away from your app.
az resource listshows the location column for a reason. - Tags missing from cost reports. Tags do not inherit from group to resource without a policy, and untagged resources show up as unallocated spend. Assign the inherit-tag policy early, not after the finance meeting.
12 · Further reading
- Azure Resource Manager overview. The official statement of the single-control-plane model, scopes, and deployment behaviour.
- CAF naming conventions. The abbreviation list and naming pattern most teams adopt, with per-type uniqueness rules.
- Region pairs and cross-region replication. The full pair list, the nonpaired-region caveats, and what pairing does and does not promise.
- Azure landing zones. The reference management-group and subscription architecture, in as much depth as you can stand.
- az CLI reference. Bookmark the
az groupandaz accountpages first. - AWS foundations. The same map drawn for AWS — reading both side by side is the fastest way to see what each cloud bundles and splits.