A policy estate built by hand has no history, no review, and no way to answer why a rule exists. The portal records who changed an assignment and when. It does not record what anyone was trying to achieve, and that is the thing you need three years later.
The workflow Microsoft actually documents
Azure Policy has a documented policy as code workflow and it is worth knowing that it exists, because most discussions of the subject jump straight to tooling. The shape is create, test, deploy. Definitions and initiatives live as JSON in source control, one folder per policy holding its rule, parameters and environment values. A change to a definition updates it in Azure automatically. The new or changed definition is then assigned in a test environment with enforcement disabled, which produces compliance results without triggering effects. A gate reads those results. If they are what you expected, the pipeline grants the assignment identity its permissions, remediates the resources it needs to, and then updates the assignment with enforcement enabled.
Two ordering constraints are load-bearing and both bite the first time somebody builds this. An initiative cannot reference a definition that does not yet exist in Azure, so definitions deploy before the initiatives that contain them. And the identity grant has to happen before remediation rather than alongside it, which means a pipeline stage boundary rather than a single deployment.
The other recommendation in that guidance is the one people skip, and it is the one with teeth. Deploy policy through a centralised pipeline and restrict write permissions on policy resources to the identity that pipeline uses. If engineers retain the ability to create assignments by hand, the repository is a description of the estate rather than the definition of it, and the two diverge inside a month.
If anyone can still create an assignment in the portal, your repository is documentation. Removing that permission is what turns it into the source of truth.
Getting what you already have out
Almost nobody starts from an empty estate. Definitions, initiatives and assignments export as JSON through REST, the CLI and PowerShell, and each command takes the object’s name, which for a built-in is a GUID rather than the display name you know it by. The portal’s export-to-GitHub feature was deprecated in April 2023, so anything describing that path is out of date.
Export everything before you change anything. That is worth doing as a standalone exercise even if the policy as code programme never happens, because it produces the first honest inventory most estates have had, and it will surface assignments nobody remembers making and definitions duplicated across subscriptions by teams who could not see each other’s work.
The tooling, honestly
There are three positions on this and the right one depends on how much of your estate policy governs and who is going to maintain the machinery.
Raw templates in your existing pipeline. Policy objects are ARM resources, so Bicep or Terraform deploys them like anything else, in whatever pipeline you already run. Nothing new to learn, nothing new to depend on, and everything above has to be built by hand: the ordering, the test assignment, the compliance gate, the identity grant, the remediation trigger. For an estate with a few dozen assignments this is the correct answer and people talk themselves out of it too readily.
The landing zone accelerators. If your platform came from Azure Landing Zones, it arrived with a policy deployment mechanism, and each accelerator has its own documented path for customising assignments: the portal accelerator, the Bicep modules and the Terraform modules each do it differently. The advantage is that your policy changes ride the same machinery as the rest of the platform. The constraint is that you are working within somebody else’s structure, and Microsoft is actively migrating landing zone custom policies to built-ins, with documented manual steps for updating definitions and initiatives you may have assigned. That migration is real maintenance and it lands on whoever owns the platform repository.
EPAC. Enterprise Policy as Code is a purpose-built framework for exactly this problem, published in Microsoft’s GitHub organisation, actively developed, and referenced from Microsoft Learn in the landing zone deployment options and in the Cloud Adoption Framework governance guidance. As at August 2026 it is at version 11.4.8, released in July. It handles the parts that are tedious to build by hand: desired-state management across many scopes, the ordering, the identity grants, remediation, and the ability to plan a change before applying it.
Be precise about what it is, because the Microsoft GitHub organisation misleads people. EPAC is a Microsoft-sponsored community project, supported through GitHub issues, and it is not a supported Microsoft product. There is no support ticket to raise when it breaks during an audit. That is not a reason to avoid it and I have seen it work extremely well. It is a reason to make the decision consciously, with somebody named as the owner of the dependency, rather than discovering the support model at the worst possible moment.
My rule of thumb: below roughly fifty assignments and one platform team, use what you already have. Above that, or with several teams contributing policy and several tenants to keep aligned, the hand-built version becomes a product you are maintaining without meaning to, and EPAC is worth the dependency.
Two utilities worth knowing about
The Cloud Adoption Framework names two community tools and both earn their place. AzAdvertizer tracks changes to built-in definitions, initiatives, aliases and role definitions over time, which answers the question of what Microsoft changed underneath you and when. Azure Governance Visualizer produces a readable picture of an entire technical governance estate, including a version checker for landing zone policies.
Neither is in the enforcement path, which is what makes them easy to adopt. Both are community projects on the same footing as EPAC, and the same reasoning applies about what you are depending on.
What the repository is really for
The deployment automation is the obvious benefit and the smaller one. What a repository gives you that no portal can is the reason a rule exists: the pull request that introduced it, the discussion attached to it, the ticket it references, and the person who approved it. Three years later, when somebody asks why storage accounts in one landing zone are governed differently, that history is the whole answer, and the alternative is an assignment with a display name and a creation date.
Which is also the case for putting exemptions in the same repository, and it is the thing I most often find missing. An exemption is a decision about accepted risk, and a decision about accepted risk with a reviewer, a justification in the commit message and an expiry date in the file is worth considerably more than the same object created by hand in a portal by somebody who has since changed jobs.
From here
The build sheet that follows puts a definition in a repository and deploys it through a workflow that gates on compliance before enforcement flips, using no framework at all, so that the mechanics are visible rather than delegated.
Azure Policy
‹ Previous: [AP 11.1] Build Sheet: Assign a Standard, Read Coverage, Attest the Rest
Next: [AP 12.1] Build Sheet: Policy in a Repository, Deployed by Pipeline ›




