How to Separate Production, Test and Development Resources in Azure

Read Time:5 Minute, 52 Second

When following the best practice of least privilege access, separating our production from our development and test resources is essential. Azure is very flexible, meaning achieving this can be done in multiple ways, however choosing the best approach may not be obvious at first.

I have had the opportunity to work in many different Azure environments and I have seen each of the scenarios we will discuss today in use. In addition, in recent years Microsoft has also given us clear guidance on best practice for this. Therefore, I thought I would use this post as an opportunity to talk over the pros and cons of each of the possible approaches and share my experiences.

This post is part of the Azure Spring Clean 2022 series and I would encourage you to check out the other posts.

The Azure Hierarchy

Before we start, lets recap on the hierarchy inside Azure. At each of these levels we can apply role-based access control (RBAC) permissions policies and various other controls.

How to Separate Production, Test and Development Resources in Azure

For each Azure tenant we have one or more management groups, in turn each management group holds one or more subscriptions. Inside each of these subscriptions we then have one or more resource groups that then ultimately hold the Azure resources. In theory we can use any of these to separate the production from development and test resources. The remainder of this post will review each of these in turn.

It is worth noting whichever approach is chosen it is recommended that resource names include the environment.

Separate Resource Groups

Separate resource groups may initially seem the simplest approach. to splitting resource up. Having a resource group per environment i.e. one for production, one for test and one for development each quickly become very large and hard to manage. More common is multiple resource groups for each environment, these then have tags or rely on names to differentiate them between environments.

I have found the main disadvantage of using resource groups is that each one must be assigned RBAC permissions and policies individually. This can make it easy to accidentally assign the wrong permissions or miss out assigning them completely when others add new resource groups.

With this approach you also miss out on the Azure Dev/Test offer that provides significant discounts. This is because it is possible to mark a whole subscription as development and save on the costs however you are unable to do this at the resource group level.

Separate Subscriptions

Subscriptions make a good choice for separating development, test, and production resources. This approach allows you to still make other finer grain groupings further down with resource groups and group the subscriptions higher up with management groups. Permissions and policies set at the subscription level will cascade down to each of the resource groups as more are added.

Having certain subscriptions only contain development or test resources allows you to take advantage of the Azure dev test discounts that are available.

This is now the recommended approach by Microsoft and can be read more about on the subscription decision guide

Using Management Groups

Management groups can be up to six levels deep, grouping together multiple subscriptions in a hierarchy. From each of these you can apply permissions set policies etc. that then cascade down. When it comes to grouping subscriptions that hold development, test, or production resources you have two logical options.

The Azure Hierarchy
  1. Group together all of your development subscriptions under one management group and all of your production subscriptions under another as above.
The Azure Hierarchy

2. Group together different areas keeping your development subscriptions alongside your production subscriptions in each of those areas.

The advantage of the first option is that you can permission all your development subscriptions at once through assigning RBAC permissions to the development management group while having a separate set of permissions for all production subscriptions. This is however, not scalable, as to keep with least privilege you will want different permissions for different test and production subscriptions as you scale.

In addition the significant advantage of the second option is that the same policies will be applied to the development, test and production subscriptions. This can prevent issues where some items work in test but not in production.

It is for these reasons that the azure landing zones guidance is that it is strongly recommended to avoid environment based management groups. This is covered in more detailed in the Microsoft cloud adoption framework landing zones FAQ.

Separate Tenants

Separate Azure tenants would give the maximum amount of separation. Each tenant is completely isolated with its own set of users, permissions, policies, and resources. It is much harder to accidentally assign the wrong permissions or link test resources to production in this scenario. It is also possible for users to only exist in one environment. For this reason, it may look appealing to separate environments by tenant, however there are many disadvantages and official guidance discourages using tenants in this way.

An Azure tenant is referred to as something that represents an organisation throughout all the documentation. In the Microsoft design recommendations documentation it explicitly says “Avoid creating multiple AD tenants”. The issues to using multiple tenants are given in more detail in the Using a single Azure AD tenant document.

With the separation provided by tenants it means that you risk inconsistencies and drift between environments. This means something that might work in the development environment might not configured to work in the same way in production. If you are using infrastructures code with tools such as BICEP then this risk is reduced as configuration is repeatable, however there is no guarantee that the bicep code has been run on all tenants.

It is worth noting that with separate tenant’s any users that need to be in all environments will have to have their accounts set up and managed separately potentially increasing licencing costs. Azure lighthouse can assist with this, but it does also have its limitations as it only supports delegation at the subscription and resource group scopes.
Overall, there may be scenarios such as highly regulated environments where this maybe a valid approach however for most cases it is advised against.


Microsoft Azure offers lots of flexibility in arranging your resources subscriptions and management groups along with where you can apply policies and RBAC permissions. In this article we looked at the various options.

All of the options offer different advantages however to ensure least privilege permissioning, scalability and consistency in policies across environments the recommended approach by Microsoft of using separate subscriptions provides the most benefit. With we looked at how you can use management groups to organise the subscriptions. As you organise the subscriptions in to management groups we looked at avoiding environment management groups so they receive the same polices.

For more information on organising your subscriptions I recommend you have a look at Microsoft’s cloud adoption framework documentation and Azure landing zones.

Title Photo by Erol Ahmed on Unsplash


Tag Cloud

Java Java Logical Programs OTP Generation in Java python Recursion youtube video ASCII Upper and Lower Case blockchain javascript graph learn to code software development Successful Software Engineers breadth first search Java Array Programs Java Programs Uncategorized android ios programming kotlin web-development django data sql cybersecurity database swiftui serverless aws swift rust react background-position gradients loader mask grid nth-child pseudo elements indieweb WordPress Print Array without brackets C++ factorial Java String Programs Final Keyword Static Variable Axie Infinity Cryptokitties NFT games tool inserting MISC Tips Codes python code python projects python3 system info python project Bigginers How to Do Integrations Payment Gateways PHP checkout page in php Implement stripe payment gateway in Step by step in PHP integrate stripe gatway in php mysql payment gateway integration in php step by step payment gateway integration in php step by step with source code payment gateway integration in website PHP Integrate Stripe Payment Gateway Tutorial PHP shopping cart checkout code shopping cart in php stripe php checkout PHP/MySQL/JSON best international payment gateway does google pay accept international payments how to accept international payments in india paytm payment gateway razorpay codeigniter github razorpay custom checkout github razorpay get payment details razorpay integration in codeigniter github razorpay international payments Razorpay payment gateway integration in CodeIgniter razorpay payment gateway integration in php code Razorpay payment gateway integration with PHP and CodeIgniter Razorpay payment gateway setup in CodeIgniter Library & Frameworks Tips & Tricks UI/UX & Front-end coding birds online html code for google sign in login with google account in PHP login with google account using javascript login with google account using javascript codeigniter login with google account using php login with google account using php source code
Top 25 Java Interview Questions Previous post Top 25 Java Interview Questions
Advanced parsing using Int.TryParse in C# Next post Advanced parsing using Int.TryParse in C#

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.