Skip to main content

Theoretical Foundation: Manage Resource Groups


1. Initial Intuition​

Think of a physical office. You don't throw all documents from all projects into a single room. You organize: separate folders by project, drawers by department, cabinets by document type. When a project ends, you take the entire folder and discard everything at once.

In Azure, a Resource Group (RG) is exactly that organizational container. It's a logical folder that groups related resources together, allowing you to manage, monitor, control, and delete them as a unit.

Every Azure resource, without exception, must belong to a Resource Group. There are no VMs, Storage Accounts, Key Vaults, or any other resource "loose" in Azure: they all have an RG as a mandatory container.

The practical value is immediate: when a project ends, you delete the Resource Group and all resources within it disappear simultaneously, without needing to remember and delete each one individually.


2. Context​

Where Resource Groups fit in the Azure hierarchy​

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

The Resource Group lives within a Subscription and contains resources. It's the most commonly used scope for applying RBAC, Azure Policy, Resource Locks, and tags, as seen in previous modules. Everything we learned about scopes has RG as the central use case.

Why Resource Groups exist​

Before Resource Groups, Azure had the classic deployment model where resources were managed individually without logical grouping. It was like trying to manage a company without folders, departments, or hierarchies. Azure Resource Manager (ARM) and Resource Groups were introduced to solve this organization and scale management problem.

What depends on Resource Groups​

Practically everything in Azure operates in relation to the Resource Group as a unit:

  • Billing: aggregated cost by RG visible in Cost Management
  • RBAC: most common scope for access assignments
  • Azure Policy: recommended scope for most assignments
  • Resource Locks: protection by RG is the most common approach
  • Tags: RG has its own tags independent of internal resources
  • ARM Templates: deployments are done to a target RG
  • Azure Monitor: alerts and logs can be filtered by RG

3. Building the Concepts​

3.1 Fundamental properties of a Resource Group​

A Resource Group has the following technical characteristics:

PropertyDetails
NameUnique within the subscription, 1-90 characters, alphanumeric + hyphens + underscores + parentheses + period
Location (region)Defines where RG metadata is stored
SubscriptionEvery RG belongs to exactly one subscription
TagsUp to 50 key:value pairs, independent of internal resources
ID/subscriptions/{subId}/resourceGroups/{rgName}

3.2 The Resource Group location doesn't limit resources inside​

This is the most counterintuitive point about Resource Groups:

The RG location only defines where the RG's own metadata is stored. Resources within the RG can be in any region in the world, regardless of the RG's region.

Example: an RG created in brazilsouth can contain a VM in eastus, storage in westeurope, and a database in southeastasia. Technically valid, although not a best practice for most cases.

Why does this matter? Because when creating the RG, you choose the region without worrying that this "forces" resources to that region. The RG's region is just an administrative detail of where the metadata lives.

3.3 Resources can only belong to one Resource Group​

A resource cannot be in two RGs simultaneously. It has exactly one RG as "owner" throughout its existence. However, resources in different RGs can interact freely: a VM in rg-compute can use a VNet in rg-networking without any technical restriction.

3.4 Deleting a Resource Group deletes everything inside​

When you delete an RG, ARM deletes all resources within it in a cascaded manner. There's no individual confirmation per resource. This operation is irreversible for most resources, except those with soft delete enabled (like Key Vault and Storage with protection).

This is a double-edged sword: it's extremely convenient for cleaning up temporary environments, and extremely dangerous if done accidentally in production. That's why Resource Locks CanNotDelete on production RGs are so important.

3.5 Moving resources between Resource Groups​

Resources can be moved from one RG to another, within the same subscription or between different subscriptions. However, not all resource types support movement.

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

What happens during a move:

  • The resource's Resource ID changes (the ARM path includes the RG)
  • The resource is briefly unavailable during the operation (some services have downtime, others don't)
  • Locks from the source RG don't move; they need to be recreated at the destination
  • Role assignments from the source RG don't accompany the resource
  • Individual resource tags are preserved
  • The operation can take from seconds to hours depending on type and size

Resource types that DON'T support move (common examples):

  • Azure Active Directory Domain Services
  • Some network resources (VPN Gateway has restrictions)
  • Resources in availability sets if the AS isn't moved together
  • Resources with delete locks CanNotDelete may require lock removal before move

To check if a type supports move before attempting:

az resource invoke-action \
--action "moveResources" \
--ids "/subscriptions/<sub-id>/resourceGroups/rg-origem" \
--request-body '{"resources": ["<resource-id>"], "targetResourceGroup": "<target-rg-id>"}'

Or check the documentation: https://learn.microsoft.com/azure/azure-resource-manager/management/move-support-resources


4. Structural View​

Resource Groups as lifecycle unit​

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

Resource Group organization strategies​

There are four main strategies, and the choice depends on context:

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

In practice, most organizations use a combination of these strategies. A common pattern is:

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

Shared resources (network, identity, monitoring) stay in their own RGs. Applications have their RGs separated by project.


5. Practical Functioning​

Resource Group lifecycle​

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

Important non-obvious behaviors​

RG deletion is asynchronous and can take time. Deleting an RG with many resources isn't instantaneous. ARM queues the deletion of each resource individually. For an RG with 50 resources, it can take 10 to 30 minutes. During this time, the RG appears as "Deleting" in the portal.

Resources can fail to be deleted due to dependencies. If resources within the RG have circular dependencies or if another resource outside the RG references a resource inside (e.g., a VM in another RG using a NIC from the RG being deleted), deletion can partially fail. ARM will try to delete in the correct order based on known dependencies, but doesn't always succeed.

An empty RG still takes up space in ARM and may have metadata costs. After removing all resources from an RG, the RG itself remains and should be deleted separately if no longer needed. Many empty RGs proliferate in organizations without cleanup processes.

Tags and locks on the RG don't automatically propagate to resources. As seen in previous modules, a lock on the RG prevents RG deletion and inherits protection for resources inside (you can't delete the RG, therefore can't delete its resources via RG removal). But tags are independent.

Moving a resource changes its Resource ID. Any resource's Resource ID includes the RG path: /subscriptions/{sub}/resourceGroups/{rg}/providers/{provider}/{type}/{name}. If the resource moves to another RG, its ID changes. Any hard-coded reference to the old ID (in scripts, alerts, RBAC, etc.) breaks.


6. Implementation Methods​

Azure Portal​

When to use: initial creation, exploration, one-off operations, visual verification

Create a Resource Group:

  1. Portal > Resource groups > + Create
  2. Select Subscription
  3. Define name (following naming convention)
  4. Select region
  5. Add tags
  6. Review + Create

Delete a Resource Group:

  1. Portal > Resource groups > select the RG
  2. Delete resource group
  3. Type the RG name to confirm (protection against accidental deletion)
  4. Click Delete

Move resources:

  1. Navigate to the resource to be moved
  2. Click Move > Move to another resource group or Move to another subscription
  3. Select the destination
  4. Confirm you understand the implications (IDs change, references may break)

Limitation: not reproducible, no version control, slow for multiple RGs.


Azure CLI​

# Create Resource Group
az group create \
--name "rg-ecommerce-prod" \
--location "brazilsouth" \
--tags Environment=Production Project=ecommerce Owner=time-backend

# List Resource Groups
az group list --output table

# List RGs with tag filter
az group list \
--query "[?tags.Environment=='Production']" \
--output table

# View details of a specific RG
az group show --name "rg-ecommerce-prod"

# Update RG tags (replaces all tags)
az group update \
--name "rg-ecommerce-prod" \
--tags Environment=Production Project=ecommerce Owner=time-backend CostCenter=CC-2045

# Delete Resource Group (asynchronous, doesn't wait for completion by default)
az group delete --name "rg-ecommerce-prod" --yes

# Delete and wait for completion
az group delete --name "rg-ecommerce-prod" --yes --no-wait false

# Check if an RG exists
az group exists --name "rg-ecommerce-prod"

# Move resource to another Resource Group (same subscription)
az resource move \
--destination-group "rg-ecommerce-prod-v2" \
--ids "/subscriptions/<sub-id>/resourceGroups/rg-ecommerce-prod/providers/Microsoft.Compute/virtualMachines/vm-web-01"

# Move multiple resources at once
az resource move \
--destination-group "rg-destino" \
--ids \
"/subscriptions/<sub-id>/resourceGroups/rg-origem/providers/Microsoft.Compute/virtualMachines/vm-01" \
"/subscriptions/<sub-id>/resourceGroups/rg-origem/providers/Microsoft.Network/networkInterfaces/nic-vm-01"

# Move resource to another subscription
az resource move \
--destination-group "rg-destino" \
--destination-subscription-id "<sub-id-destino>" \
--ids "<resource-id>"

# List resources within an RG
az resource list \
--resource-group "rg-ecommerce-prod" \
--output table

# Check if resource type supports move
az provider show \
--namespace "Microsoft.Compute" \
--query "resourceTypes[?resourceType=='virtualMachines'].capabilities" \
--output json

# Export ARM template from RG (for backup or replication)
az group export \
--name "rg-ecommerce-prod" \
--output-folder "./exports/"

Azure PowerShell​

# Create Resource Group
New-AzResourceGroup `
-Name "rg-ecommerce-prod" `
-Location "brazilsouth" `
-Tag @{
Environment = "Production"
Project = "ecommerce"
Owner = "time-backend"
CostCenter = "CC-2045"
}

# List Resource Groups
Get-AzResourceGroup | Select-Object ResourceGroupName, Location, ProvisioningState

# List RGs with filter
Get-AzResourceGroup -Tag @{Environment = "Production"}

# Check if RG exists
Get-AzResourceGroup -Name "rg-ecommerce-prod" -ErrorAction SilentlyContinue

# Delete Resource Group (waits for completion)
Remove-AzResourceGroup -Name "rg-ecommerce-prod" -Force

# Delete asynchronously
Remove-AzResourceGroup -Name "rg-ecommerce-prod" -Force -AsJob

# Move resources to another RG
Move-AzResource `
-DestinationResourceGroupName "rg-ecommerce-prod-v2" `
-ResourceId "/subscriptions/<sub-id>/resourceGroups/rg-origem/providers/Microsoft.Compute/virtualMachines/vm-01"

# Move to another subscription
Move-AzResource `
-DestinationResourceGroupName "rg-destino" `
-DestinationSubscriptionId "<sub-id-destino>" `
-ResourceId "<resource-id>"

# List resources in an RG
Get-AzResource -ResourceGroupName "rg-ecommerce-prod" |
Select-Object Name, ResourceType, Location

# Export ARM template from RG
Export-AzResourceGroup `
-ResourceGroupName "rg-ecommerce-prod" `
-Path "./exports/rg-ecommerce-prod-template.json"

# Create RG from existing template
New-AzResourceGroupDeployment `
-ResourceGroupName "rg-destino" `
-TemplateFile "./exports/rg-ecommerce-prod-template.json"

Bicep and ARM Templates​

In IaC, Resource Groups are created with targetScope = 'subscription' because RGs belong to subscriptions, not to other RGs:

// file: create-rgs.bicep
targetScope = 'subscription'

param location string = 'brazilsouth'
param environment string = 'prod'

// Main application Resource Group
resource rgApp 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-ecommerce-${environment}'
location: location
tags: {
Environment: environment
Project: 'ecommerce'
Owner: 'time-backend'
CostCenter: 'CC-2045'
ManagedBy: 'bicep'
}
}

// Shared network Resource Group
resource rgNetwork 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: 'rg-networking-${environment}'
location: location
tags: {
Environment: environment
Project: 'shared-infrastructure'
Owner: 'time-cloud'
ManagedBy: 'bicep'
}
}

// Use module to create resources INSIDE the RG
module appResources './app-resources.bicep' = {
name: 'deploy-app-resources'
scope: rgApp // scope is the RG created above
params: {
location: location
environment: environment
}
}

For deploying a template that creates RGs:

az deployment sub create \
--location "brazilsouth" \
--template-file "create-rgs.bicep" \
--parameters environment=prod

Note that the deployment is at the subscription level (az deployment sub create), not at the RG level, because we're creating RGs.


Terraform​

# Common variables
locals {
environment = "prod"
location = "brazilsouth"

common_tags = {
Environment = "Production"
ManagedBy = "terraform"
Owner = "time-cloud"
}
}

# Application resource group
resource "azurerm_resource_group" "app" {
name = "rg-ecommerce-${local.environment}"
location = local.location
tags = merge(local.common_tags, {
Project = "ecommerce"
CostCenter = "CC-2045"
})
}

# Network resource group
resource "azurerm_resource_group" "network" {
name = "rg-networking-${local.environment}"
location = local.location
tags = merge(local.common_tags, {
Project = "shared-infrastructure"
})
}

# Monitoring resource group
resource "azurerm_resource_group" "monitoring" {
name = "rg-monitoring"
location = local.location
tags = merge(local.common_tags, {
Project = "shared-infrastructure"
})
}

# Resources created within RGs referencing the RGs above
resource "azurerm_virtual_network" "main" {
name = "vnet-main"
resource_group_name = azurerm_resource_group.network.name # references the network RG
location = azurerm_resource_group.network.location
address_space = ["10.0.0.0/16"]
tags = local.common_tags
}

7. Control and Security​

RBAC on Resource Groups​

The Resource Group is the most common RBAC scope for work teams. The standard approach is:

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

Resource Locks on production Resource Groups​

Every production RG should have, at minimum, a CanNotDelete lock. This prevents accidental deletion of the entire environment with a single command.

# Apply mandatory lock on production RG
az lock create \
--name "rg-prod-nodelete" \
--resource-group "rg-ecommerce-prod" \
--lock-type CanNotDelete \
--notes "Production - Remove only with approval via change ticket"

Azure Policy on Resource Group scope​

Policies can be assigned directly to the RG for rules specific to that environment. For example, an allowed region policy on the Subscription but with exceptions for specific lab RGs.


8. Decision Making​

Resource Group organization strategy​

SituationRG StrategyReason
Small startup, few projectsBy environment (dev, prod)Operational simplicity
Medium company with multiple projectsBy application + environmentProject isolation, cost control per app
Large company with distinct teamsBy application + environment + shared infra RGIndependent teams, centralized infrastructure
Temporary project (POC, hackathon)Single RG for everythingEasy deletion at project end
Shared infrastructure (VNet, DNS)Dedicated separate RGDifferent lifecycle from applications

When to move resources vs. recreate​

SituationDecisionReason
Newly created resource in wrong RGMoveNo data, no significant impact
Production VM in wrong RG, with critical dataEvaluate move downtimeMove may have brief downtime
Resource that doesn't support moveRecreateNo technical alternative
Cross-subscription move with many dependenciesRecreate with blue/greenLess risk of broken references
Entire Resource Group to another subscriptionMove resources individually or recreate via IaCNo native "move entire RG" operation

Important: there is no native operation to move an entire Resource Group. You move individual resources within it. If you need to "move" an RG to another subscription, the safest approach is to export the RG's ARM template, create the RG in the target subscription, and deploy the template.

Resource Group region​

ScenarioRG RegionReason
Production RG with resources in BrazilbrazilsouthMetadata close to resources for minimal management latency
Disaster recovery RGsecondary region (eastus2)Metadata survives primary region failure
Global resources RG (CDN, Traffic Manager)eastus or westeuropeConventional for global resources

9. Best Practices​

Define a naming convention and follow it rigorously. Resource Groups are the visual entry point to your Azure environment. Names like rg-ecommerce-prod, rg-erp-dev, rg-networking-shared immediately communicate purpose. Names like ResourceGroup1 or Test create operational chaos.

One Resource Group per environment per application. Mixing dev and production resources in the same RG is risky (accidental prod deletion along with dev) and complicates cost reporting. Environment separation in distinct RGs is standard practice.

Resources with the same lifecycle stay in the same RG. The Resource Group golden rule: if resources are created together, managed together, and deleted together, they belong in the same RG. Shared infrastructure that survives individual projects gets its own RGs.

Always apply tags when creating the RG, before creating resources within. Tags applied to the RG serve as reference and can be inherited through Policy. If you create the RG without tags and want to apply them later, you'll need to do remediation on all resources within.

Never leave production RGs without locks. An az group delete --name rg-production --yes destroys months of work in seconds. CanNotDelete lock is the minimum mandatory protection.

Document RG strategy as code. Use Bicep or Terraform to create and manage RGs. This way, creating new environments always follows the same pattern, with the same tags, locks, and configurations.

Create a dedicated RG for monitoring and operations. Log Analytics Workspace, Application Insights, alerts, and dashboards should be in a separate RG from the resources they monitor. This prevents application RG deletion from destroying historical monitoring data.

Limit the number of Resource Groups per subscription. While the technical limit is 980 RGs per subscription, organizations with hundreds of RGs become difficult to manage. If you're reaching dozens of RGs, it's probably time to evaluate separation into multiple subscriptions.


10. Common Mistakes​

MistakeWhy it happensHow to avoid
Putting resources from different environments in the same RGInitial convenience without long-term visionDefine RG convention before starting
Accidentally deleting production RGNo lock, wrong CLI commandMandatory CanNotDelete Resource Lock in prod
Assuming RG region limits internal resourcesCommon conceptual confusionRemember: RG region is only for RG metadata
Not applying tags when creating RGRush, considered minor detailInclude tags in RG IaC definition
Moving resource without verifying supportTrying to save timeCheck move support documentation first
Believing that moving resource preserves IDsTechnical ignoranceResource ID changes; update all references
Empty RGs proliferating after projects endNo cleanup processOffboarding process that deletes empty RGs
Creating single RG for all subscription resourcesExcessive initial simplicityPrevents granular access control and chargeback
Resources with different lifecycles in same RGOrganization by technical type instead of lifecycleGroup by when resources live and die together

The most catastrophic mistake​

Executing az group delete without lock on a production RG is one of the most common and devastating accidents in Azure environments. The confirmation the CLI asks for (--yes) is easily passed in automated scripts. The only real protection is Resource Lock.


11. Operation and Maintenance​

Resource Group inventory and audit​

# List all RGs with tags, location, and state
az group list \
--query "[].{Name:name, Location:location, State:properties.provisioningState, Tags:tags}" \
--output table

# Identify RGs without mandatory tags (via Resource Graph)
az graph query -q "
ResourceContainers
| where type == 'microsoft.resources/subscriptions/resourcegroups'
| where isnull(tags['Environment']) or isnull(tags['CostCenter'])
| project name, location, tags, subscriptionId"

# Identify empty RGs (no resources)
az graph query -q "
ResourceContainers
| where type == 'microsoft.resources/subscriptions/resourcegroups'
| join kind=leftouter (
Resources
| summarize resourceCount = count() by resourceGroup
) on \$left.name == \$right.resourceGroup
| where isnull(resourceCount) or resourceCount == 0
| project name, location, subscriptionId"

# Check locks on all production RGs
az lock list \
--query "[?contains(id, 'prod')]" \
--output table

# Count resources by type in an RG
az resource list \
--resource-group "rg-ecommerce-prod" \
--query "[].type" \
--output tsv | sort | uniq -c | sort -rn

Monitor Resource Group changes​

# Check recent operations on an RG (Activity Log)
az monitor activity-log list \
--resource-group "rg-ecommerce-prod" \
--max-events 50 \
--output table

# Alert on RG creation or deletion
az monitor activity-log alert create \
--name "Alert-RG-Created-Deleted" \
--resource-group "rg-monitoring" \
--condition \
category=Administrative \
operationName=Microsoft.Resources/subscriptions/resourceGroups/write \
--scope "/subscriptions/<sub-id>"

Resource Group technical limits​

LimitValue
RGs per subscription980
Resources per Resource GroupNo documented limit, but practical ~800 per type
Tags per Resource Group50
Name length1 to 90 characters
Concurrent deployments per RG800 (deployment history; older ones are automatically deleted)

The 800 deployments per RG limit is important in CI/CD scenarios with many deployments. ARM maintains history of up to 800 deployments per RG; when reached, it starts deleting the oldest ones. In very active pipelines, this can be a traceability problem if deployment history is needed.


12. Integration and Automation​

Environment creation automation via pipeline​

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

Project offboarding process​

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

Environment replication via template export​

A useful pattern for creating identical environments (e.g., replicate prod to staging):

# Export current production RG configuration
az group export \
--name "rg-ecommerce-prod" \
--skip-resource-name-params \
--output-folder "./templates/"

# Parameterize exported template for staging environment
# (edit manually or via script to change resource names)

# Create staging RG
az group create \
--name "rg-ecommerce-staging" \
--location "brazilsouth" \
--tags Environment=Staging Project=ecommerce

# Deploy template to staging RG
az deployment group create \
--resource-group "rg-ecommerce-staging" \
--template-file "./templates/template.json" \
--parameters environment=staging

Azure Cost Management integration​

Resource Groups appear as a native dimension in Cost Management. Without needing tags or extra configuration, you can see:

  • Cost per Resource Group in the period
  • Cost trend per RG over time
  • Budget alerts per RG
# Create budget alert for a specific RG
az consumption budget create \
--budget-name "budget-ecommerce-prod" \
--amount 5000 \
--time-grain Monthly \
--resource-group "rg-ecommerce-prod" \
--notifications '{"actual_GreaterThan_80_Percent": {"enabled": true, "operator": "GreaterThan", "threshold": 80, "contactEmails": ["cloud-team@company.com"]}}'

13. Final Summary​

Essential points:

  • Every Azure resource must belong to exactly one Resource Group; there are no resources without RG
  • The RG region defines where RG metadata is stored, not where resources within can be created
  • Deleting an RG deletes all resources within in a cascaded and irreversible manner (except resources with soft delete)
  • RGs are the most common scope for RBAC, Policy, Locks, and tags in daily operations
  • RG tags do not automatically propagate to resources within it
  • Resources can be moved between RGs or subscriptions, but the Resource ID changes in the process

Critical differences:

  • RG vs. Subscription: Subscription is billing unit and administrative boundary; RG is logical container within subscription
  • RG region vs. Resource region: RG region is independent of its resources' regions
  • Move resource vs. recreate: Move preserves data but changes Resource ID and may have downtime; recreate is safer but loses state
  • Lock on RG vs. Lock on resource: Lock on RG protects the RG and inherits protection against deletion of entire RG; doesn't prevent deletion of individual resources within RG via direct delete on resource

What needs to be remembered for AZ-104:

  • The limit of RGs per subscription is 980
  • The deployment history limit per RG is 800
  • The move resource operation changes the Resource ID
  • There is no operation to move an entire RG; resources are moved individually
  • When deploying templates that create RGs, the deployment must be at subscription level (az deployment sub create), not RG level
  • Resource Group is not a network isolation unit; resources in different RGs can communicate freely
  • ARM template export of an RG via portal or CLI is a practical way to backup and replicate environments