Hey, Azure Guy!

Azure Policy at Scale, Part 1: EPAC or native ALZ? Asking a better question

Written in a personal capacity. I work as a Microsoft Cloud Solution Architect, but this series is my own research and my own opinion. It is not an official Microsoft position.

This is Part 1 of a series called Azure Policy at Scale: Native ALZ Governance vs EPAC. I have wanted to write it for a long time. It is one of the few technical questions where I keep hearing confident answers that contradict each other, and where the people giving those answers are all competent and all acting in good faith.

The question sounds simple: if you are running Azure Landing Zones, should you manage Azure Policy with the native tooling, or with Enterprise Policy as Code (EPAC)?

I should say up front how this series started. I set out to argue that Microsoft had left customers without guidance on this decision, and partway through the research I found the guidance. It is published on Microsoft Learn and it is reasonably clear. That changed the article I was writing, and I have left the correction visible rather than quietly rewriting history.

The trigger

Two things pushed me into this.

The first is that there are genuinely different views on this, including inside Microsoft. I am not going to characterise anyone else’s position or quote internal material, because that would be both inappropriate and unverifiable for you as a reader. What I can say is that in my own conversations with colleagues, and in customer engagements, I have heard the native path and the EPAC path each described as the obvious choice, sometimes in the same week.

The second is more concrete, and you can check it yourself. Both approaches live in repositories under the same Azure/ GitHub organisation:

If you are a customer or a partner, that is a confusing signal: two well-maintained approaches to the same problem, in the same organisation, with no obvious signpost telling you which one is meant for you. It comes up in customer conversations regularly.

Why the usual framing fails

The debate is almost always held as a tooling comparison: which one deploys policy better?

I think that question is largely settled. Both approaches can do the basics. Both can manage policy definitions, initiatives (policy set definitions) and assignments. Both can handle exemptions as Azure resources. Both can run in a pipeline. Both are open source. Both are actively maintained.

The feature-checklist version of this argument therefore produces long tables that convince nobody, because whichever tool you already prefer will win on the criteria you chose to put in the table. I have read several of those comparisons, and I wrote a first draft of one myself before throwing it away.

A good deal of what gets compared is also simply out of date, which I come back to later in this post.

The reframe

I would rather ask:

When does native ALZ policy governance suffice, and when do additional governance and operational requirements justify introducing EPAC as a separate policy operating model?

The thesis this series builds toward:

EPAC is not “ALZ Policy, but for grown-ups”. It is a different category of thing.

EPAC is a policy lifecycle and operations engine. The native ALZ path is a landing zone implementation that includes policy. Comparing them directly is a category error, and I think a good deal of the confusion follows from it.

That leads to the question the rest of the series is really about:

The question is no longer whether native Azure Landing Zones can manage Azure Policy at scale. The real question is whether Azure Policy needs to become a separately operated governance product in your organisation.

That is an architecture question rather than a tooling preference, and the answer differs per organisation.

The thing almost nobody quotes

Microsoft publishes decision guidance for exactly this choice. I had planned to write that no such guidance existed, and I was wrong about that.

It sits in the Azure Architecture Center article Deploy Azure landing zones, in a section titled Enterprise policy as code for policy management. EPAC is described there as:

“Enterprise policy as code (EPAC) is an alternative method to deploy, manage, and operate Azure Policy across your organization’s Azure estate. You can use EPAC instead of the standard platform options to manage the policies in an Azure landing zone environment.”

The next paragraph is the one I think matters most in this entire discussion:

“Compare the life cycle and flexibility of the two approaches before you decide on what approach to use long term. Begin by evaluating the native policy management in the default implementation. If that implementation doesn’t meet your governance needs, then do a minimum viable product (MVP) or proof of concept by using EPAC. Compare options, validate your findings, and confirm your choice before you implement an approach because you can’t easily change policy governance methods after you establish them.”

That is a complete decision sequence:

  1. Evaluate the native policy management in the default implementation first.
  2. Move to EPAC only if that does not meet your governance needs.
  3. Prove it with an MVP or proof of concept before committing.
  4. Decide carefully, because you cannot easily change course later.

The same article also notes that “EPAC is best suited for more advanced development operations (DevOps) and IaC customers, but organizations of any scale can use EPAC after they assess it.” It is worth reading in full, because it does not restrict EPAC to large enterprises, but it does attach an assessment step.

Separately, the Cloud Adoption Framework article Enforce cloud governance policies recommends, under Use policy as code:

“Consider using Enterprise Azure Policy as Code (EPAC) to manage policies aligned with recommended Azure landing zone policies.”

So EPAC is documented and acknowledged in Microsoft’s own architecture and governance guidance.

I formed my own position before I found any of this, and it turned out to match the published sequence closely. I mention that as evidence that the reasoning holds up independently, not as vindication. If the guidance had contradicted me I would have had to say so, and Part 5 does contain a couple of findings that went against my position.

The guidance is not missing, then, but it is easy to miss, which is a different and more interesting problem. It lives in an Architecture Center article about landing zone deployment, in a subsection under Variants and specializations, alongside things like sovereign landing zones and partner implementations. A practitioner comparing two policy tools is unlikely to start there.

The positioning tells you something in itself. EPAC is documented as a variant rather than as one of the standard deployment options, which is a verifiable statement about how it is meant to be picked up.

What each thing actually is

Some plain definitions before going further, with no judgement attached.

I am going to spend longer on EPAC than on the native path here. When I shared an early version of this post with colleagues, the most useful piece of feedback I got was that I had assumed the reader already knew what EPAC does. Several of them work with Azure Policy regularly and did not, which I do not think reflects badly on anyone. It is a fairly self-contained tool that you have either used or not.

EPAC describes itself as:

“a number of PowerShell scripts which can be used in CI/CD based system or a semi-automated use to deploy Policies, Policy Sets, Policy Assignments, Policy Exemptions and Role Assignments. It also contains operational scripts to simplify operational tasks.”

That list is the entire scope. EPAC deploys policy resources and the role assignments those policy resources need. It does not deploy networking, it has nothing to say about subscription vending, and outside its optional Hydration Kit it does not create management groups. A landing zone is a great deal more than policy, and EPAC covers the policy part of it.

You install it as the EnterprisePolicyAsCode PowerShell module, or run it from source. On provenance, its documentation groups it with AzAdvertizer and Azure Governance Visualizer as “three distinct open source projects or tools internally developed and maintained by Microsoft employees”.

Your repository is the source of truth. You create a Definitions/ folder whose subfolders map onto Azure Policy concepts:

Definitions/
  global-settings.jsonc
  policyDefinitions/
  policySetDefinitions/
  policyAssignments/
  policyExemptions/
  policyDocumentations/

Definitions are JSON with comments. Assignments can also be driven from CSV, which EPAC presents as a deliberate accessibility choice: operation and maintenance “can actually be done via CSV files, reducing the skill expertise needed to operate the solution once implemented”. In practice that lets a compliance specialist adjust effects and parameters for a regulatory initiative in a spreadsheet rather than in JSON.

The deployment flow is three stages:

Build-DeploymentPlans  →  Deploy-PolicyPlan  →  Deploy-RolesPlan

Plan first, then apply policy resources, then apply the role assignments that DeployIfNotExists and Modify policies need. The three stages run under different privileges, and the split exists so you can put an approval gate between planning and applying. If you have used Terraform, the shape will feel familiar.

Desired state is the characteristic that defines EPAC, and it is the one I moved past too quickly the first time. EPAC puts it in a caution box on its own front page:

“EPAC is a true desired state deployment technology. It takes possession of all Policy Resources at the deploymentRootScope and its children. It will delete any Policy resources not defined in the EPAC repo.”

Two terms in that sentence carry most of the weight.

The deploymentRootScope is where EPAC’s authority begins. The documentation defines it as the scope at which “EPAC can deploy and manage policies at this scope and any scope below it in the Azure hierarchy”, and says EPAC is “meant to manage all policy resources within the specified deploymentRootScope and act as the ‘single source of truth’ for Azure Policy”. EPAC recommends pointing it at an intermediate root management group rather than the tenant root group, to preserve flexibility and avoid lockout.

Taking possession is meant literally. Under the default full strategy, a policy assignment that somebody creates by hand in the portal inside that scope will be deleted on the next deployment, because it is not in the repository. That is desired state working correctly, and it is also the behaviour that catches teams out. The rule is narrower than “it deletes everything”, though: full “deletes any Policy resources without a pacOwnerId; however, it does not delete Policy resources with a different pacOwnerId”. The alternative strategy, ownedOnly, “manages only Policy resources defined in the EPAC repo” and deletes nothing else. Either can be narrowed further with exclusions for scopes, definitions, assignments and individual files.

pacEnvironments provide isolation and multi-tenancy. Each environment has a symbolic name (pacSelector) and its own deploymentRootScope. A typical setup has one pointed at the production hierarchy and one pointed at a cloned management group hierarchy used for policy development, which exists because you cannot safely test a policy change in a hierarchy where real workloads live. The pacOwnerId, which EPAC suggests you set to a GUID, records which instance a policy resource came from. That is what lets two EPAC instances manage the same tenant root without treating each other’s resources as undeclared, and EPAC notes that other policy-as-code solutions can take part too if they set metadata.pacOwnerId.

The operational scripts get less attention than the deployment flow, and they matter for the argument this series makes. Alongside deployment, EPAC ships New-AzRemediationTasks to create remediation tasks in bulk for non-compliant resources, Build-PolicyDocumentation to generate documentation and parameter CSV files from what is actually deployed, Export-AzPolicyResources to pull an existing policy estate into EPAC’s file format, and Get-AzExemptions to extract exemptions.

None of those are deployment features. They are the day-two operations of running Azure Policy as something in its own right, which is why I keep describing EPAC as an operations engine rather than a deployment tool. Part 4 goes through them properly, because they are where the genuine EPAC-only cases turn out to live.

EPAC is also direct about who it thinks should use it. Its documentation says it is “designed for medium and large organizations with a larger number of Policies, Policy Sets and Assignments and/or complex deployment scenarios, such as, multiple tenants, multiple teams managing Policies.”

The native path is harder to define, because it is not one thing. I spent a while writing “AVM/ALZ” as if it were a single product, and it is not. The distinction matters more than I first assumed, particularly for Part 2.

ALZ and AVM are different layers, with different owners:

Layer What it does Where it lives
Guidance Conceptual architecture, design areas, policy documentation Azure/Azure-Landing-Zones, described as “Azure Landing Zones documentation”
Data The policy assets: definitions, set definitions, assignments, archetypes Azure/Azure-Landing-Zones-Library, described as “Library of assets to deploy Azure Landing Zones architectures”
Engine The modules that actually create the resources AVM: avm/ptn/alz/empty on Bicep, terraform-azurerm-avm-ptn-alz on Terraform
Composition Wires the Library data into the AVM modules, exposes the customisation surface Azure/alz-bicep-accelerator, described as “Azure Verified Modules for Platform Landing Zones - Bicep”
Bootstrap Repository setup, pipelines, deployment Azure/accelerator-bootstrap-modules

The engine row is the one that changed how I write about this. You follow ALZ guidance, and the ALZ documentation describes the policy story well, but in the current AVM based ALZ implementation, the component that actually creates your policy definitions and assignments is an AVM module. On the Bicep path that module is avm/ptn/alz/empty, whose README lists what it deploys: management groups, policyDefinitions, policySetDefinitions, policyAssignments, roleDefinitions and roleAssignments.

Stated precisely, “native ALZ policy management” means ALZ Library data, composed by the ALZ starter module, deployed by AVM pattern modules, driven by the accelerator pipeline. That is a mouthful, which is why people compress it, and the compression is where a lot of the confusion starts.

The layering also clarifies what EPAC is being compared against. EPAC replaces the data, engine and composition layers for policy specifically. It does not replace the guidance layer, and it does not replace AVM for anything else in your landing zone. You do not stop using Azure Verified Modules because you adopted EPAC.

All of which brings me back to the category point. A lot of the disagreement starts because EPAC and ALZ are being compared as if they were the same type of solution.

A critical distinction: there is no single “ALZ”

“ALZ” splits along two axes, and most comparisons flatten both of them.

Axis one: Bicep and Terraform are not interchangeable for this discussion. They solve policy lifecycle through fundamentally different mechanisms:

ALZ Bicep + AVM ALZ Terraform + AVM
Lifecycle mechanism Azure Deployment Stacks Terraform state
Removal of dropped resources ActionOnUnmanage semantics plan / apply / destroy
Where the “desired state” lives In Azure, on the stack In the state file

Treating them as one thing is, in my experience, the most common error in this comparison, and I made it myself in the first draft of my research notes. Part 3 goes into the mechanics properly, including one widely repeated claim about Deployment Stacks that turns out to be wrong.

Axis two: ALZ and AVM are not the same layer, as the table above showed. That one caught me out as well. It matters because when you ask whether something is supported, the answer depends on which layer you are asking about. The guidance, the data, the engine and the pipeline are maintained by different teams, in different repositories, under different statements.

Part 2 is entirely about that, and it produced the finding that surprised me most.

Throughout this series I name the layer and the IaC language. Where neither distinction matters for a sentence, I say “the native path”.

Why the old comparison is out of date

Most of the disagreement I run into has a fairly simple explanation.

EPAC was historically introduced to compensate for real gaps in classic ALZ Bicep implementations. Those gaps were not imaginary. If your reference point is an ALZ Bicep deployment from a few years ago, EPAC solved problems you actually had.

The native implementation has moved substantially since then, and I still encounter decisions that were made against a version of ALZ that no longer exists.

You can check this without relying on documentation. The Azure/accelerator-bootstrap-modules repository is public, and it currently ships two generations of deployment pipeline side by side:

  • bicep-classic uses plain incremental ARM deployments (New-AzManagementGroupDeployment and friends), with a separate helper to tear things down
  • bicep uses Azure Deployment Stacks (New-AzManagementGroupDeploymentStack), configured with ActionOnUnmanage = "DeleteAll"

Two eras of the operating model in one repository, readable in about five minutes. I find that better evidence than documentation, because it is the code that actually runs rather than a statement of intent.

For dating the shift: Microsoft announced that AVM for Platform Landing Zone using Bicep reached general availability in the Azure Tools Blog post of 20 January 2026, which also states that Bicep AVM is “the default starter module and the focus of all future development”.

If you evaluated this in 2023, the thing you evaluated is not the thing that exists now, which on its own is reason enough to revisit the decision.

My hypothesis, stated up front

Research papers state the hypothesis before the evidence, so that you can watch it succeed or fail. Mine is:

Default to native ALZ policy governance. Treat EPAC as a deliberate exception that requires a named, concrete requirement.

I have not yet personally encountered a customer case where that justification held up under questioning. I want to be careful about how much weight that carries: it is an observation from my own engagements, not proof, and my sample is not the world. If you have a case that breaks it, I would genuinely like to hear about it. Part 5 ends with an open invitation and a list of things that would change my mind.

I also want to be explicit that this is not a criticism of EPAC. It is a well-engineered project, actively maintained, shipping frequently, and built by people who understand this domain deeply. Part 4 is a deliberate attempt to prove myself wrong by hunting for the cases where EPAC is genuinely the right answer, and it finds several.

Method, credentials and scope

Since I am asking you to weigh evidence, you should know how it was gathered.

How the research was done. Four sources, deliberately combined:

  • Review of public primary-source documentation, conducted with AI assistance
  • My own deployments and hands-on testing
  • Experience from customer engagements
  • Conversations with colleagues

The sourcing rule I have held myself to. Every published claim in this series stands on a public primary source: Microsoft Learn, the ALZ documentation site, the ALZ Library repository, AVM documentation, the accelerator repositories, or EPAC’s own public documentation. Where something could not be sourced publicly, it is either cut or clearly restated as my own reasoning rather than presented as fact.

Two conventions follow from that, and I apply them consistently:

  • Absences are phrased as absences of documentation. I write “I found no documented X”, not “X does not exist”. The first is something I can stand behind; the second is a claim about the world that I am not in a position to make.
  • Factual claims are date-stamped. Everything in this series reflects what I could verify in August 2026. This area moves quickly (one relevant capability went generally available while I was writing), so please check current sources before making decisions on it.

What is out of scope. The quality of the policy content itself, RBAC, networking, and subscription vending. This series is about the policy operating model, not about whether a given initiative is a good one.

What is coming

The series runs to five parts:

  • Part 1: The question (this post). Why the usual framing fails, and the decision guidance that already exists.
  • Part 2: What “supported” actually means. The part that needs the most precision. I read the support statements word for word, and the result changed my view of the debate. Read Part 2 →
  • Part 3: One library, two engines. How both routes now consume the same ALZ Library, why Deployment Stacks are not a generic “AVM capability”, and a correction to a claim I see repeated constantly.
  • Part 4: Hunting for the EPAC-only case. A deliberate attempt at self-falsification. Where does native ALZ genuinely fall short?
  • Part 5: The verdict. A decision framework, my position labelled as opinion, and what would change my mind.

If you already have a strong view on this, I would particularly like to hear from you after Part 4. That is where I go looking for evidence against myself, and it is the part most likely to be incomplete.

One last thing, and it is why I think this decision deserves five posts rather than a tweet. Microsoft’s own guidance ends its recommendation with a warning:

“you can’t easily change policy governance methods after you establish them”

So whichever way you go, it is worth making the choice deliberately.

Revision log

  1. Expanded the explanation of EPAC after a colleague pointed out that the original version assumed too much prior knowledge. Added its desired state semantics, deploymentRootScope, pacEnvironments and the operational scripts, all quoted from EPAC's own documentation.