Management group deployments with Bicep files

This article describes how to set scope with Bicep when deploying to a management group.

As your organization matures, you can deploy a Bicep file to create resources at the management group level. For example, you may need to define and assign policies or Azure role-based access control (Azure RBAC) for a management group. With management group level templates, you can declaratively apply policies and assign roles at the management group level.

Supported resources

Not all resource types can be deployed to the management group level. This section lists which resource types are supported.

For Azure Blueprints, use:

  • artifacts
  • blueprints
  • blueprintAssignments
  • versions

For Azure Policy, use:

  • policyAssignments
  • policyDefinitions
  • policySetDefinitions
  • remediations

For access control, use:

  • privateLinkAssociations
  • roleAssignments
  • roleAssignmentScheduleRequests
  • roleDefinitions
  • roleEligibilityScheduleRequests
  • roleManagementPolicyAssignments

For nested templates that deploy to subscriptions or resource groups, use:

  • deployments

For managing your resources, use:

  • diagnosticSettings
  • tags

Management groups are tenant-level resources. However, you can create management groups in a management group deployment by setting the scope of the new management group to the tenant. See Management group.

Set scope

To set the scope to management group, use:

targetScope = 'managementGroup'

Deployment commands

To deploy to a management group, use the management group deployment commands.

For Azure CLI, use az deployment mg create:

az deployment mg create \
  --name demoMGDeployment \
  --location ChinaNorth \
  --management-group-id myMG \
  --template-uri "https://raw.githubusercontent.com/Azure/azure-docs-json-samples/master/management-level-deployment/azuredeploy.json"

For more detailed information about deployment commands and options for deploying ARM templates, see:

Deployment location and name

For management group level deployments, you must provide a location for the deployment. The location of the deployment is separate from the location of the resources you deploy. The deployment location specifies where to store deployment data. Subscription and tenant deployments also require a location. For resource group deployments, the location of the resource group is used to store the deployment data.

You can provide a name for the deployment, or use the default deployment name. The default name is the name of the template file. For example, deploying a template named main.bicep creates a default deployment name of main.

For each deployment name, the location is immutable. You can't create a deployment in one location when there's an existing deployment with the same name in a different location. For example, if you create a management group deployment with the name deployment1 in chinaeast, you can't later create another deployment with the name deployment1 but a location of chinanorth. If you get the error code InvalidDeploymentLocation, either use a different name or the same location as the previous deployment for that name.

Deployment scopes

When deploying to a management group, you can deploy resources to:

  • the target management group from the operation
  • another management group in the tenant
  • subscriptions in the management group
  • resource groups in the management group
  • the tenant for the resource group

An extension resource can be scoped to a target that is different than the deployment target.

The user deploying the template must have access to the specified scope.

Scope to management group

To deploy resources to the target management group, add those resources with the resource keyword.

targetScope = 'managementGroup'

// policy definition created in the management group
resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  ...
}

To target another management group, add a module. Use the managementGroup function to set the scope property. Provide the management group name.

targetScope = 'managementGroup'

param otherManagementGroupName string

// module deployed at management group level but in a different management group
module exampleModule 'module.bicep' = {
  name: 'deployToDifferentMG'
  scope: managementGroup(otherManagementGroupName)
}

Scope to subscription

You can also target subscriptions within a management group. The user deploying the template must have access to the specified scope.

To target a subscription within the management group, add a module. Use the subscription function to set the scope property. Provide the subscription ID.

targetScope = 'managementGroup'

param subscriptionID string

// module deployed to subscription in the management group
module exampleModule 'module.bicep' = {
  name: 'deployToSub'
  scope: subscription(subscriptionID)
}

Scope to resource group

You can also target resource groups within the management group. The user deploying the template must have access to the specified scope.

To target a resource group within the management group, add a module. Use the resourceGroup function to set the scope property. Provide the subscription ID and resource group name.

targetScope = 'managementGroup'

param subscriptionID string
param resourceGroupName string

// module deployed to resource group in the management group
module exampleModule 'module.bicep' = {
  name: 'deployToRG'
  scope: resourceGroup(subscriptionID, resourceGroupName)
}

Scope to tenant

To create resources at the tenant, add a module. Use the tenant function to set its scope property. The user deploying the template must have the required access to deploy at the tenant.

targetScope = 'managementGroup'

// module deployed at tenant level
module exampleModule 'module.bicep' = {
  name: 'deployToTenant'
  scope: tenant()
}

Or, you can set the scope to / for some resource types, like management groups. Creating a new management group is described in the next section.

Management group

To create a management group in a management group deployment, you must set the scope to the tenant.

The following example creates a new management group in the root management group.

targetScope = 'managementGroup'

param mgName string = 'mg-${uniqueString(newGuid())}'

resource newMG 'Microsoft.Management/managementGroups@2021-04-01' = {
  scope: tenant()
  name: mgName
  properties: {}
}

output newManagementGroup string = mgName

The next example creates a new management group in the management group targeted for the deployment. It uses the management group function.

targetScope = 'managementGroup'

param mgName string = 'mg-${uniqueString(newGuid())}'

resource newMG 'Microsoft.Management/managementGroups@2021-04-01' = {
  scope: tenant()
  name: mgName
  properties: {
    details: {
      parent: {
        id: managementGroup().id
      }
    }
  }
}

output newManagementGroup string = mgName

Subscriptions

To deploy a template that moves an existing Azure subscription to a new management group, see Move subscriptions in ARM template

Azure Policy

Custom policy definitions that are deployed to the management group are extensions of the management group. To get the ID of a custom policy definition, use the extensionResourceId() function. Built-in policy definitions are tenant level resources. To get the ID of a built-in policy definition, use the tenantResourceId() function.

The following example shows how to define a policy at the management group level, and assign it.

targetScope = 'managementGroup'

@description('An array of the allowed locations, all other locations will be denied by the created policy.')
param allowedLocations array = [
  'chinaeast2'
  'chinaeast'
  'chinanorth'
]

resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'locationRestriction'
  properties: {
    policyType: 'Custom'
    mode: 'All'
    parameters: {}
    policyRule: {
      if: {
        not: {
          field: 'location'
          in: allowedLocations
        }
      }
      then: {
        effect: 'deny'
      }
    }
  }
}

resource policyAssignment 'Microsoft.Authorization/policyAssignments@2022-06-01' = {
  name: 'locationAssignment'
  properties: {
    policyDefinitionId: policyDefinition.id
  }
}

Next steps

To learn about other scopes, see: