Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

5. Naming and Tagging

12 min read

Naming feels like the least interesting decision in a cloud estate and is one of the least reversible. Azure resources cannot be renamed. There is no rename operation — you create a new resource with the new name, move the data, repoint everything, and delete the old one. For a storage account or a key vault that also means a DNS-addressable endpoint changes, which means every consumer changes too.

Tags are the opposite: cheap, changeable, and the only mechanism that answers "whose is this and why is it costing us money". Most estates get the naming roughly right and the tagging badly wrong, then discover the tagging gap six months later during a cost review, when backfilling it means touching four hundred resources.

Analogy: the name is the address stamped into the building's foundation. The tags are the labels on the door — easy to change, and the only reason anyone can find the right occupant.

Two rules carry most of the value on this page:

Encode only stable facts in the name. Put everything volatile in tags.

A team owner changes. A cost centre changes. A criticality rating changes. A resource's region, environment, and workload almost never change without the resource being rebuilt anyway. That line tells you exactly what belongs where.

Coming from AWS: two habits will hurt. First, AWS lets you name most things freely and rename many of them, and S3 bucket names are the rare globally-unique exception — in Azure, globally-unique DNS-addressable names are normal, not exceptional. Second, the Terraform AWS provider's default_tags block means you can set tags once at the provider level; azurerm has no equivalent, so tags are passed explicitly per resource ⚠️ verify current provider capability. Plan for a locals map and discipline, not a provider setting.

An immutable name carrying stable facts, mutable tags carrying ownership and cost metadata


Naming rules are per resource type, and they genuinely differ

There is no single Azure naming rule. Each resource type has its own constraints on length, allowed characters, case, and — crucially — the scope at which the name must be unique. That last one is the part people miss.

Uniqueness scope Means Examples
Global Unique across every Azure customer, because it becomes a public DNS name Storage account, Key Vault, App Service, Container Registry, Cosmos DB account, SQL logical server, Service Bus namespace
Resource group Unique within its group only VM, NIC, NSG, virtual network, disk, App Service Plan
Subscription Unique within the subscription Resource group
Parent resource Unique within the parent Blob container, subnet, queue, topic

Globally-unique names are the ones that hurt, because you're competing with every Azure customer on Earth for storage1 and because the name lands in a hostname:

https://stpaymentsproduks.blob.core.windows.net
https://kv-payments-prod.vault.azure.net
https://app-payments-prod.azurewebsites.net

Which produces a rule people learn the hard way: don't put anything confidential in a name. Customer names, project codenames under embargo, and internal system names all end up in public DNS, in TLS certificates, and in certificate transparency logs. Assume the name is public, because it is.

The character rules vary more than seems reasonable. The two worst offenders, worth memorising because you'll meet them constantly:

  • Storage account — lowercase letters and digits only. No hyphens, no underscores, no uppercase. 3–24 characters. Globally unique.
  • Key Vault — letters, digits, and hyphens; must start with a letter; 3–24 characters. Globally unique.

⚠️ Verify exact lengths and character sets per type against the current Azure naming-rules documentation — they're stable but occasionally revised, and there are well over a hundred types.

A separate trap that isn't a naming rule but behaves like one: guest OS name limits. A Windows VM's computer name is capped at 15 characters by NetBIOS, independent of the Azure resource name. You can have an Azure VM resource called vm-payments-prod-uks-001 whose Windows hostname is truncated, which makes correlating a monitoring alert to an Azure resource unexpectedly annoying. Keep the two aligned if you can.

Soft delete squats on your name

This one is worth its own paragraph because it turns into a confusing failure at exactly the wrong moment. Several services support soft delete — Key Vault most notoriously — where a deleted resource is retained for a recovery window. During that window, the name is still taken. Recreating a vault with the same name fails until you either recover it or purge it:

az keyvault list-deleted -o table
az keyvault purge -n kv-payments-dev            # blocked if purge protection is on

And if purge protection is enabled, you cannot purge — you wait out the retention period. That's the correct security posture for production and a genuine obstacle in a CI pipeline that creates and destroys ephemeral vaults. The standard workaround is a random suffix on ephemeral resource names, which is also the standard reason ephemeral resources accumulate untidily. Pick your poison deliberately; terraform destroy will not save you from either.

The same shape appears elsewhere — soft-deleted storage accounts, recovery vaults with backup items, resources behind a CanNotDelete lock. Every Deployment page in this article names what destroy will not remove for its service, and this is why.


A convention worth adopting

Microsoft's Cloud Adoption Framework publishes recommended abbreviations, and using them buys you the one thing a convention is for: someone unfamiliar with your estate can read a resource ID and know what they're looking at. The pattern:

<type>-<workload>-<environment>-<region>-<instance>
rg-payments-prod-uks              resource group
vnet-payments-prod-uks            virtual network
snet-payments-prod-uks-app        subnet
vm-payments-prod-uks-001          virtual machine
kv-payments-prod-uks              key vault
st paymentsproduks → stpaymentsproduks    storage account (no hyphens allowed)
app-payments-prod-uks             app service
plan-payments-prod-uks            app service plan
law-payments-prod-uks             log analytics workspace

Common abbreviations: rg, vnet, snet, nsg, vm, vmss, st, kv, app, func, plan, aks, acr, sql, cosmos, sb, evh, law, appi, agw, afd, pip, nic, lb.

Three honest observations about conventions in general:

The type prefix is redundant and worth keeping anyway. The resource ID already contains Microsoft.Storage/storageAccounts. But names appear stripped of context — in alert emails, cost reports, CLI output, KQL results, Terraform plans — and stpaymentsproduks is self-describing where paymentsproduks isn't.

Character limits will break your convention, and that's fine. A 24-character storage account name cannot hold st + workload + environment + region + instance for any real workload name. Shorten the workload (pay), drop the region if you're single-region, drop the instance number. Consistency matters more than completeness, and a documented exception beats an undocumented improvisation.

Don't encode ownership or cost centre in the name. Those change; the name can't. They're tags.

Two more things worth deciding once: case (use lowercase everywhere — some types demand it, none forbid it, and mixed case creates lookup errors that are invisible on screen) and numbering (use 001 not 1, so sorting works).


Tags

Technically: a tag is a key–value string pair attached to a resource, resource group, or subscription, stored by ARM, queryable, and surfaced in Cost Management as a grouping dimension. Limits are roughly 50 tags per resource, key up to 512 characters, value up to 256 ⚠️ verify against current Azure docs; storage accounts have a lower key length limit.

Two facts that shape everything else about tags:

Tags are not inherited. A tag on a resource group does not appear on the resources inside it. Cost Management can group by resource-group tags in some views, but the tag itself doesn't propagate. This surprises everyone once. The fix is Azure Policy with the modify effect and the Inherit a tag from the resource group built-in definition, which writes the tag onto resources — and remediate tasks to backfill existing ones.

Not every resource type supports tags. Some child resources and classic resources don't. A tagging policy that assumes universal support will produce non-compliance you can't fix.

A tag set that earns its keep

Five tags cover most needs. More than about eight and compliance decays.

Tag Why Example
env Filtering, policy targeting, cost splitting prod, staging, dev
owner Who to contact. The single highest-value tag payments-team or a distribution list
costCenter Chargeback — the reason finance cares about any of this CC-4471
workload Groups resources into an application across resource groups payments-api
managedBy Distinguishes IaC-managed from hand-built terraform, manual

owner and costCenter are the two that justify the effort. When a resource shows up in a cost anomaly report or an unused-resource review, those two tags are the difference between a five-minute decision and a three-week email chain. managedBy earns its place the first time you need to know whether deleting something will be reverted by the next pipeline run.

Optional additions when they're genuinely used: dataClassification (if you have a classification scheme with teeth), criticality (if it drives an actual paging policy), expiresOn (if something actually reaps them — otherwise it's decoration).

Tags in Terraform

No provider-level default tags in azurerm, so the pattern is a locals map merged per resource:

locals {
  common_tags = {
    env        = var.environment
    owner      = var.owner
    costCenter = var.cost_center
    workload   = var.workload
    managedBy  = "terraform"
  }
}

resource "azurerm_storage_account" "this" {
  name                     = "st${var.workload_short}${var.environment}"
  resource_group_name      = azurerm_resource_group.this.name
  location                 = azurerm_resource_group.this.location
  account_tier             = "Standard"
  account_replication_type = "LRS"

  tags = merge(local.common_tags, { component = "artifacts" })
}

One gotcha that produces permanent Terraform drift: if Azure Policy appends or inherits tags, Terraform sees tags it didn't write and plans to remove them, every run, forever. Either exclude those keys from Terraform's view or, more bluntly:

lifecycle {
  ignore_changes = [tags["inheritedCostCenter"]]
}

The cleaner resolution is to pick one owner of each tag key — policy or Terraform, never both. Splitting ownership by key is fine; overlapping it is a permanent nuisance. This is the most common policy-versus-IaC conflict in Azure and it's entirely avoidable at design time.


Enforcing both with Azure Policy

Conventions that aren't enforced decay within a quarter. Azure Policy is the enforcement mechanism, and it has more range here than AWS SCPs do — it can deny, audit, modify in flight, and deploy missing configuration (why that matters).

The three assignments worth having from day one:

Require the tags you actually use. Require a tag on resources in deny mode for env and owner, at the management group covering your landing zones. Deny is deliberate — audit mode for required tags produces a compliance dashboard nobody reads.

Inherit from the resource group. Inherit a tag from the resource group in modify mode for costCenter and workload, plus a remediation task to backfill. This is how you get inheritance that Azure doesn't give you natively, and it turns tagging from per-resource discipline into per-group discipline.

Enforce the naming convention. A custom policy denying names that don't match a pattern:

{
  "if": {
    "allOf": [
      { "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
      { "field": "name", "notMatch": "st??????????" }
    ]
  },
  "then": { "effect": "deny" }
}

Azure Policy's match / notMatch uses # for a digit, ? for a letter, and . for any character — not regex, and the source of much confusion. There's also matchInsensitively. Start with audit for naming policies rather than deny: a naming policy in deny mode that's slightly wrong blocks all deployments, and unlike a tag you can't remediate a name after the fact.

Then verify with Resource Graph, which queries the whole tenant at once:

# Untagged resources, by subscription
az graph query -q "Resources | where isnull(tags.owner) | summarize count() by subscriptionId, type" -o table

# Everything a team owns, across every subscription
az graph query -q "Resources | where tags.owner =~ 'payments-team' | project name, type, resourceGroup, location" -o table

# Names that break the convention
az graph query -q "Resources | where type =~ 'microsoft.storage/storageaccounts' and name !startswith 'st' | project name, resourceGroup" -o table

Those three queries are the whole feedback loop. Run the first one before deciding your tagging is working.

Azure Policy enforcing naming and tagging at management group scope via deny, audit, and modify


The mistakes worth pre-empting

Assuming you can rename. You can't. Recreate, migrate, repoint, delete.

Putting something confidential in a globally-unique name. It becomes public DNS and lands in certificate transparency logs.

Encoding volatile facts in the name. Owner, cost centre, and criticality all change. Names don't.

Designing a convention that doesn't fit a storage account. 24 characters, lowercase alphanumeric. Test the convention against the tightest type before adopting it.

Forgetting soft delete holds the name. Especially Key Vault, especially with purge protection on, especially in a pipeline that creates and destroys.

Expecting tags to inherit. They don't. Use a policy with the modify effect.

Letting both Terraform and Policy own the same tag key. Permanent drift. One owner per key.

Requiring twelve tags. Compliance collapses. Require two, inherit two, and make them ones people actually query.

Enforcing naming with deny before auditing. A slightly-wrong naming policy in deny mode stops every deployment in its scope, and names can't be remediated after creation the way tags can.


That's the Foundations topic complete. Every service topic in this article assumes these five pages and links back to them in a line rather than re-explaining — so from here, pick a service and start.

Next: Back to the article's table of contents →

← Back to the Foundations overview · ← Previous: Regions and Availability