Skip to main content

Theoretical Foundation: Apply and Manage Tags on Resources


1. Initial Intuition​

Imagine you manage a physical datacenter with hundreds of servers. To know which servers belong to the development team, which are for production, which are for the e-commerce project, and which are for ERP, you put physical labels on each server. When the electricity bill arrives, you can calculate how much each project consumed because you know exactly which servers belong to each one.

In Azure, tags are exactly these labels, but digital ones. A tag is a key:value pair that you associate with any Azure resource. They are metadata that you freely define to categorize, organize, and track your resources according to your organization's needs.

Concrete example:

KeyValue
EnvironmentProduction
CostCenterCC-2045
Projectecommerce-v2
Ownertime-backend
CreatedBypipeline-devops

With these five tags on each resource, you can answer questions like: "How much did the ecommerce-v2 project spend this month?" or "Which resources belong to the backend team?" or "Which production resources don't have a defined owner?"


2. Context​

Where tags fit in Azure governance​

Tags are a fundamental piece of any organization's governance strategy operating in Azure at scale. They are the mechanism that connects technical resources to business context.

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Why tags exist​

Azure has no way to know that a specific VM belongs to project X from department Y. This is business information, not technical. Tags are the mechanism by which you inject business context into technical infrastructure.

Without tags, the Azure bill shows cost lines by resource type and subscription, with no correlation to projects, teams, or cost centers. With tags, you get granular cost visibility aligned with your organizational model.


3. Concept Building​

3.1 What is a tag technically​

A tag is a key:value pair stored as metadata in Azure Resource Manager (ARM) associated with a resource. Technically:

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Technical limits per resource:

LimitValue
Tags per resource or Resource Group50
Maximum key length512 characters
Maximum value length256 characters
Maximum key length (Storage Accounts)128 characters
Maximum value length (Storage Accounts)256 characters

3.2 Case sensitivity​

Tags are case-insensitive for keys from the perspective of a single resource, but case-sensitive in filters and queries. This means that Environment and environment are treated as the same key on the same resource, but when filtering resources through the portal or CLI, you need to use the exact capitalization that was used.

The recommended practice is to define a capitalization convention and follow it rigorously. The most common patterns are:

  • PascalCase: CostCenter, ProjectName, Environment
  • lowercase: costcenter, projectname, environment
  • kebab-case: cost-center, project-name, environment

Choose a pattern and use Azure Policy to enforce it.

3.3 Which resources support tags​

Most Azure resources support tags, but not all. Resources that do not support tags include:

  • Azure AD (users, groups, service principals)
  • Classic resources (old deployment model)
  • Some types of sub-resources and system resources

To check if a resource type supports tags, consult the documentation for each resource provider or use:

az provider show --namespace "Microsoft.Compute" \
--query "resourceTypes[?resourceType=='virtualMachines'].capabilities"

3.4 Tags are not automatically inherited​

This is the most important and most frequently misunderstood point about tags in Azure:

Tags on a Resource Group are NOT automatically propagated to the resources within it.

If you tag the RG with Environment: Production, the VMs, storage accounts, and other resources within the RG do not receive this tag. Each resource has its own independent set of tags.

Similarly, tags on a Subscription are not propagated to RGs or resources within it.

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

VM-Web-01 and Key Vault are without tags despite being in a tagged RG. To ensure resources inherit tags from RG, it's necessary to use Azure Policy with Modify effect (covered in the integration section).


4. Structural View​

Decision flow for tagging strategy​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

How tags integrate with other services​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

5. Practical Operation​

Tag lifecycle​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Important and non-obvious behaviors​

Tags in Resource Groups are independent from resources within. An RG can have the tag Environment: Production and contain resources without any tags, or with different tags. The RG is a management scope, not a container that propagates its properties.

Tags are resource properties in ARM, not within the resource. Tags don't affect resource functionality. A VM with or without tags has identical behavior. Tags exist only in the management plane (ARM), not in the data plane.

Modifying resource tags doesn't cause downtime. Adding, modifying, or removing tags is a resource metadata operation. It doesn't restart services or affect availability.

Tags participate in Azure Resource Graph for complex queries. With consistent tags, you can make powerful queries like "all production resources without owner tag, across all subscriptions":

Resources
| where tags['Environment'] == 'Production'
| where isnull(tags['Owner'])
| project name, type, resourceGroup, subscriptionId

Some services propagate tags to child resources. For example, when creating a VM, Azure may propagate the VM's tags to associated disks and NICs, depending on configuration. But this is specific behavior of some resource providers, not a general rule.


6. Implementation Methods​

Azure Portal​

When to use: point application on individual resources, visual tag review, manual correction

To apply tags on a resource:

  1. Navigate to the resource
  2. In the side menu, click Tags
  3. Type the key and value in the provided fields
  4. Click Apply

To apply tags on multiple resources via portal:

  1. Use Azure Resource Manager > Tags in the portal to filter and edit tags at scale (limited, less practical for many resources)

Limitation: doesn't scale, not reproducible, typos create inconsistencies.


Azure CLI​

# Apply tags on a resource (replaces existing tags with specified set)
az resource tag \
--tags Environment=Production CostCenter=CC-2045 Project=ecommerce-v2 \
--resource-group "rg-producao" \
--name "vm-web-01" \
--resource-type "Microsoft.Compute/virtualMachines"

# Apply tags on a Resource Group
az group update \
--name "rg-producao" \
--tags Environment=Production CostCenter=CC-2045 Owner=time-backend

# IMPORTANT: The command above REPLACES all existing tags.
# To ADD tags without losing existing ones, use --set:
az resource update \
--resource-group "rg-producao" \
--name "vm-web-01" \
--resource-type "Microsoft.Compute/virtualMachines" \
--set tags.NewTag=NewValue

# Remove a specific tag without affecting others
az resource update \
--resource-group "rg-producao" \
--name "vm-web-01" \
--resource-type "Microsoft.Compute/virtualMachines" \
--remove tags.OldTag

# List resources with a specific tag
az resource list \
--tag Environment=Production \
--output table

# List resources WITHOUT a specific tag (requires Resource Graph)
az graph query \
-q "Resources | where isnull(tags['CostCenter']) | project name, type, resourceGroup"

# Apply tags on all resources in an RG (bash script)
for resource_id in $(az resource list --resource-group "rg-producao" --query "[].id" -o tsv); do
az resource tag \
--ids "$resource_id" \
--tags Environment=Production CostCenter=CC-2045
done

Critical attention: az resource tag and az group update --tags replace the complete set of tags. If you specify only one tag, all others are removed. To add tags while maintaining existing ones, use az resource update --set tags.NewKey=Value.


Azure PowerShell​

# Apply tags on a Resource Group (replaces all existing tags)
Set-AzResourceGroup `
-Name "rg-producao" `
-Tag @{
Environment = "Production"
CostCenter = "CC-2045"
Owner = "time-backend"
}

# Apply tags on a specific resource
$resource = Get-AzResource `
-ResourceGroupName "rg-producao" `
-ResourceName "vm-web-01" `
-ResourceType "Microsoft.Compute/virtualMachines"

$resource.Tags["Environment"] = "Production"
$resource.Tags["CostCenter"] = "CC-2045"
Set-AzResource -ResourceId $resource.ResourceId -Tag $resource.Tags -Force

# Add tag without losing existing ones
$resource = Get-AzResource -ResourceId "/subscriptions/<sub>/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-web-01"
$tags = $resource.Tags
$tags["NewTag"] = "NewValue"
Set-AzResource -ResourceId $resource.ResourceId -Tag $tags -Force

# Apply tags on all resources in an RG preserving existing tags
$resources = Get-AzResource -ResourceGroupName "rg-producao"
foreach ($resource in $resources) {
$existingTags = $resource.Tags ?? @{}
$existingTags["Environment"] = "Production"
$existingTags["CostCenter"] = "CC-2045"
Set-AzResource -ResourceId $resource.ResourceId -Tag $existingTags -Force
}

# List resources with a specific tag
Get-AzResource -Tag @{Environment = "Production"} | Select-Object Name, ResourceType, ResourceGroupName

# Remove a specific tag from a resource
$resource = Get-AzResource -ResourceId "<resource-id>"
$tags = $resource.Tags
$tags.Remove("OldTag")
Set-AzResource -ResourceId $resource.ResourceId -Tag $tags -Force

Bicep and ARM Templates​

In IaC, tags should be defined as reusable variables or parameters to ensure consistency:

// Define tags as reusable parameter
param resourceTags object = {
Environment: 'Production'
CostCenter: 'CC-2045'
Project: 'ecommerce-v2'
Owner: 'time-backend'
CreatedBy: 'pipeline-devops'
CreatedDate: '2026-03-24'
}

// Apply to Resource Group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-producao'
location: 'brazilsouth'
tags: resourceTags
}

// Apply to VM
resource vm 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name: 'vm-web-01'
location: resourceGroup().location
tags: union(resourceTags, {
Role: 'web-server' // additional tag specific to this VM
})
properties: {
// ... VM configurations
}
}

The union() function in Bicep merges two tag objects, allowing each resource to inherit common tags and add its own.


Terraform​

# Common tags variable (defined once, reused across all resources)
locals {
common_tags = {
Environment = "Production"
CostCenter = "CC-2045"
Project = "ecommerce-v2"
Owner = "time-backend"
ManagedBy = "terraform"
}
}

# Resource Group with tags
resource "azurerm_resource_group" "prod" {
name = "rg-producao"
location = "brazilsouth"
tags = local.common_tags
}

# VM with common tags + specific tags
resource "azurerm_linux_virtual_machine" "web" {
name = "vm-web-01"
resource_group_name = azurerm_resource_group.prod.name
location = azurerm_resource_group.prod.location

tags = merge(local.common_tags, {
Role = "web-server"
Tier = "frontend"
})

# ... rest of configurations
}

Using locals for common tags and merge() to combine with specific tags is the Terraform pattern for tag management at scale.


7. Control and Security​

Tag enforcement with Azure Policy​

Tags only have value if they are applied consistently. Azure Policy is the mechanism to ensure this:

1. Policy to require specific tag (Deny or Audit):

Built-in policy: "Require a tag on resources"

  • Deny effect: blocks resource creation without the mandatory tag
  • Audit effect: logs resources without the tag as non-compliant, without blocking

2. Policy to inherit tag from Resource Group (Modify):

Built-in policy: "Inherit a tag from the resource group if missing"

This policy uses Modify effect to automatically copy a tag from RG to resource, if the tag is missing on the resource. This is the solution for "tags are not automatically inherited".

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

3. Policy for controlled values (Deny with allowedValues):

Custom policy that restricts accepted values for a tag:

{
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Compute/virtualMachines"
},
{
"not": {
"field": "tags['Environment']",
"in": ["Production", "Staging", "Development", "Testing"]
}
}
]
},
"then": {
"effect": "Deny"
}
}
}

This policy ensures that the Environment tag on VMs can only have defined values. Any other value is blocked.

Who can modify tags​

Tags are resource properties in ARM. To modify tags on a resource, you need write permission on that resource. This means that:

  • Contributor can add, modify, and remove resource tags
  • Reader cannot modify tags
  • A tag cannot be used as access control; it's just metadata

To restrict who can modify tags specifically, without restricting other operations, would require a Custom Role that only allows */read and */write for tags, which is complex and rarely necessary.


8. Decision Making​

Tag taxonomy definition​

| Tag category | Key examples | Purpose | | Financial | CostCenter, BudgetCode, BusinessUnit | Chargeback, cost allocation | | Operational | Environment, Tier, Role, Criticality | Operational management, automation | | Owner | Owner, Team, Contact | Responsibility, onCall | | Lifecycle | CreatedDate, ExpiresOn, CreatedBy | Governance, automated cleanup | | Project | Project, Application, Version | Investment traceability | | Compliance | DataClassification, Regulation | Security, legal requirements |

When to use each application approach​

SituationApproachReason
New environment being provisionedIaC (Bicep/Terraform) with tags as variablesEnsure consistency from the start
Existing resources without tagsPowerShell/CLI mass script + Policy for new onesScale remediation + future prevention
Mandatory tags across organizationAzure Policy with Deny on Management GroupCentralized enforcement, doesn't depend on human discipline
Inherit tags from RG automaticallyAzure Policy with ModifyOfficial solution for tag propagation
Tag with controlled valuesCustom policy with allowedValuesAvoid inconsistent values like "prod", "Prod", "PROD"
Tag compliance auditAzure Resource Graph + Policy ComplianceScalable queries across organization

9. Best Practices​

Define taxonomy before starting to tag. Decide which tags are mandatory, which are optional, which values are accepted, and what the capitalization convention is before starting. Changing taxonomy after thousands of resources are already tagged is extremely laborious.

Start with few mandatory tags and add gradually. A set of 3 to 5 mandatory tags that the entire organization can fill correctly is much better than 20 tags where half remain empty or incorrect. Recommended tags to start: Environment, CostCenter, Owner, Project.

Use Policy with Deny for mandatory tags in production. In mature production environments, mandatory tags should be enforced with Deny. In new or transitional environments, start with Audit to understand the impact before blocking.

Use IaC as source of truth for tags. Tags should be defined in Terraform or Bicep templates, not applied manually in the portal. When a resource is recreated or redeployed, tags are automatically restored.

Implement tag inheritance from RG via Policy. Apply the built-in policy "Inherit a tag from the resource group if missing" for the most important tags. This way, if the RG has the correct tag, any resource created within it automatically receives it.

Use tags for lifecycle automation. Tags like ExpiresOn: 2026-06-30 can be used by Azure Automation runbooks to identify and shut down (or alert about) resources that have passed their expiration date.

Never use tags for security control. Tags are metadata, not access controls. Don't implement security logic that depends on tags (like "resources with Confidential tag have restricted access"). Use RBAC and Azure Policy for security control.


10. Common Mistakes​

MistakeWhy it happensHow to avoid
Assuming RG tags propagate to resourcesExpected behavior that doesn't exist in AzureUse Policy with Modify for explicit inheritance
Using az resource tag and losing existing tagsThe command replaces all tagsUse az resource update --set tags.Key=Value to add without losing
Capitalization inconsistency: "prod", "Prod", "PROD"Lack of defined and enforced standardDefine standard + Policy with allowedValues
Tags only on RG, not on individual resourcesMistaken belief in automatic inheritanceApply tags to each resource, or use inheritance Policy
Using tags as only organization mechanism without proper RGsTags don't replace scope structureUse RGs for isolation + tags for metadata
Not including tags in IaC templatesRush, considered minor detailInclude common tag variable in all templates
Creating tags with many possible values without controlNo allowedValues PolicyDefine enum of accepted values and enforce via Policy
Tags with sensitive information like passwords or tokensTags are visible to any ReaderNever put credentials in tags; use Key Vault

The most common mistake at scale​

In organizations with multiple teams, each team creates their own tags with different names and values to represent the same concept. Result: Env, env, Environment, ENVIRONMENT, environment, Ambiente all coexisting, making cost reports by environment impossible without massive cleanup.

The solution is to define a tag taxonomy document as official reference and enforce via Policy before proliferation begins.


11. Operation and Maintenance​

Query resources by tag in daily operations​

# List all resources with a specific tag
az resource list --tag Environment=Production --output table

# Count resources by tag value (how many per environment)
az resource list \
--query "[].tags.Environment" \
--output tsv | sort | uniq -c

# Resources without mandatory tag (via Resource Graph)
az graph query -q "
Resources
| where isnull(tags['CostCenter']) or tags['CostCenter'] == ''
| project name, type, resourceGroup, subscriptionId
| order by type asc"

# All unique values of a tag (to detect inconsistencies)
az graph query -q "
Resources
| where isnotnull(tags['Environment'])
| summarize count() by tostring(tags['Environment'])
| order by count_ desc"

Cost reports by tag in Cost Management​

  1. Portal > Cost Management + Billing > Cost analysis
  2. In Group by, select Tag and choose the desired key (ex: CostCenter)
  3. The report shows cost grouped by tag value

Attention: resources without the tag appear in a separate category (usually "untagged" or with empty value). This is a visual indication of tagging non-compliance.

Automatic cost report exports by tag​

Configure automatic exports in Cost Management to a Storage Account, with tag grouping, generating monthly CSV files that can be automatically processed for chargeback.

Monitor tag compliance via Policy​

The Policy > Compliance dashboard shows the percentage of compliant resources for each tag policy. This allows tracking tag coverage evolution over time.

Limits to monitor​

LimitValueImpact if reached
Tags per resource50Cannot add more tags; review and remove unnecessary tags
Key length512 charactersError when trying to create tag with too long key
Value length256 charactersError when trying to create tag with too long value

The 50 tags per resource limit is rarely reached with well-designed taxonomy. If you're approaching this limit, it's a sign that taxonomy needs to be reviewed and simplified.


12. Integration and Automation​

Tag-based automation with Azure Automation​

A powerful pattern is using tags to control automatic resource behaviors:

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Example PowerShell runbook that shuts down VMs based on tag:

# Runbook: Auto-Shutdown based on tag
$vms = Get-AzResource `
-ResourceType "Microsoft.Compute/virtualMachines" `
-Tag @{AutoShutdown = "true"}

$currentHour = (Get-Date).ToString("HH:mm")

foreach ($vm in $vms) {
$shutdownTime = $vm.Tags["ShutdownTime"]
if ($shutdownTime -eq $currentHour) {
Write-Output "Shutting down $($vm.Name)..."
Stop-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Force
}
}

Integration with Azure Cost Management for chargeback​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Integration with Azure Monitor for tag-segmented alerts​

It's possible to create alerts that filter by tag to notify only the team responsible for a set of resources:

# Create action group for backend team
az monitor action-group create \
--name "ag-backend-team" \
--resource-group "rg-monitoring" \
--short-name "backend" \
--email backend-team@company.com

# Create high CPU alert only for VMs with tag Owner=backend-team
az monitor metrics alert create \
--name "high-cpu-backend-vms" \
--resource-group "rg-monitoring" \
--scopes "/subscriptions/<sub-id>/resourceGroups/rg-prod" \
--condition "avg Percentage CPU > 90" \
--action "ag-backend-team" \
--description "High CPU on backend team VMs"

Automated cleanup of resources with expiration tag​

# Runbook: Identify and report expired resources
$today = Get-Date

$expiredResources = Get-AzResource | Where-Object {
$_.Tags.ContainsKey("ExpiresOn") -and
[DateTime]::Parse($_.Tags["ExpiresOn"]) -lt $today
}

foreach ($resource in $expiredResources) {
Write-Output "EXPIRED RESOURCE: $($resource.Name) - Expired on: $($resource.Tags['ExpiresOn'])"
# Optional: Send-MailMessage or New-AzMonitorAlertRule
# Optional: Remove-AzResource -ResourceId $resource.ResourceId -Force
}

13. Final Summary​

Essential points:

  • Tags are key:value pairs associated with resources as metadata in ARM
  • No automatic inheritance of tags: RG tags don't propagate to resources within
  • Limit is 50 tags per resource, with keys up to 512 characters and values up to 256
  • Tags exist only in the management plane (ARM) and don't affect resource functionality
  • To ensure RG tag inheritance, use Azure Policy with Modify effect ("Inherit tag from resource group if missing")

Critical differences:

  • az resource tag replaces the entire tag set; az resource update --set tags.Key=Value adds without losing existing ones
  • RG vs. resource tags: RG and its resources have completely independent tag sets
  • Policy Deny vs. Modify for tags: Deny blocks resources without tag; Modify adds the tag automatically. Both are complementary
  • Audit vs. Deny for enforcement: Audit logs non-compliance without blocking; Deny blocks at creation

What needs to be remembered for AZ-104:

  • Tags are not inherited automatically from parent to child scopes
  • Replacement vs. merge: attention when using commands that replace all tags versus those that add incrementally
  • Azure Resource Graph is the tool for complex queries of resources by tags at scale
  • Tags appear in Cost Management as grouping dimension for cost reports
  • The built-in policy "Inherit a tag from the resource group if missing" uses Modify effect and is the official solution for tag propagation
  • Tags can be used in automation (Runbooks, Logic Apps) to control resource behaviors
  • Tag keys are case-insensitive on the same resource but case-sensitive in external filters and queries