Enterprise Policy as Code (EPAC) — Introduction & Setup Guide
What is EPAC?
Enterprise Policy as Code (EPAC) is an open-source framework maintained by Microsoft’s FastTrack team that allows you to manage Azure Policy entirely from code. Instead of creating and managing policies manually through the Azure Portal, everything is stored in a Git repository and deployed through a CI/CD pipeline.
EPAC is the de-facto standard for managing Azure Policy at scale and is widely adopted in complex or multi-tenant Azure environments.
What does EPAC manage?
EPAC takes over the following from manual management:
- Policy Definitions — custom policy definitions you write or import
- Policy Set Definitions (Initiatives) — bundles of multiple policies
- Policy Assignments — assigning policies to management groups, subscriptions, or resource groups
- Role Assignments — the RBAC roles required for remediation of non-compliant resources
- Exemptions — exceptions for specific resources or scopes
Everything is described in JSON/JSONC files, stored in Git, and rolled out via a pipeline.
How does this help?
For any party managing multiple Azure environments or tenants, EPAC provides a number of concrete benefits:
Repeatability — policies deployed for one environment can easily be reused for another by adjusting the scope.
Auditability — every change is visible in Git history: who changed what, when, and why.
Consistency — no more drift between environments caused by manual changes in the portal.
Scalability — compliance frameworks such as BIO, CIS, or the Microsoft Cloud Security Benchmark consist of dozens to hundreds of policies. Managing those manually is unsustainable; EPAC makes it manageable.
Safety — by using the ownedOnly strategy, EPAC only manages what it created itself. Manually created policies are left untouched.
Should or do I want to use this?
When EPAC is a good fit
- You manage multiple Azure environments or tenants
- You work with compliance frameworks (BIO, CIS, ISO 27001, MCSB)
- You want to peer-review policy changes via pull requests
- You want rollback capabilities in case of a faulty policy rollout
When EPAC might be overkill
- You have a single small Azure environment with few or no custom policies
- No CI/CD infrastructure is in place and there is no willingness to set it up
- The team has no Git experience
Conclusion
For any organization managing multiple Azure environments with compliance requirements, EPAC is an investment that pays off. The initial setup takes roughly a day; the long-term benefit is a fully auditable, repeatable, and scalable policy baseline.
Deployment strategy: ownedOnly vs full
EPAC supports two strategies for managing existing policies:
| Strategy | Behavior | Recommended for |
|---|---|---|
ownedOnly | EPAC only manages policies it created itself. Manually created policies are left untouched. | Existing environments — safest starting point |
full | EPAC manages everything. Policies not present in code will be removed. | Greenfield environments or a full migration to EPAC |
Recommendation: Always start with
ownedOnlyin an existing environment. Switch tofullonly after all existing policies have been migrated to code.
Setup — Step by step
Prerequisites
- PowerShell 7+
- Azure CLI or Az PowerShell module
- Azure DevOps organization with a project
- Permissions on the root Management Group
Note: This guide assumes the use of Azure Managed DevOps Pools as the pipeline agent pool. If you are using self-hosted agents or Microsoft-hosted agents, adjust the
poolconfiguration in the pipeline YAML files accordingly.
Step 1 — Create a repository
Before anything else, create a Git repository in Azure DevOps (or another Git provider) to store your EPAC configuration. Without a repository you cannot version, collaborate on, or deploy your EPAC definitions.
Clone the repository locally before proceeding:
git clone <your-repository-url>
cd <repository-folder>
Step 2 — Install the module
Install-Module -Name EnterprisePolicyAsCode -Force
Step 3 — Generate a GUID for PacOwnerId
New-Guid
Save this GUID. It is the unique identifier EPAC uses to recognize the resources it owns.
Step 4 — Sign in to Azure
Connect-AzAccount
Then validate the connection:
Test-HydrationConnection
Step 5 — Create the Global Settings file
New-HydrationGlobalSettingsFile -DefinitionsRootFolder ./Definitions
Use the following values as input:
| Parameter | Value |
|---|---|
PacOwnerId | The GUID from step 3 |
ManagedIdentityLocation | Your preferred Azure region (e.g. westeurope) |
MainPacSelector | Name of your production PAC environment |
EpacPacSelector | epac (for the EPAC development environment) |
Cloud | AzureCloud |
TenantId | Your tenant ID |
MainDeploymentRoot | Management Group ID only (without any path prefix) |
EpacDevelopmentRoot | Management Group ID only (without any path prefix) |
Strategy | ownedOnly (recommended for existing environments) |
Important: For
MainDeploymentRootandEpacDevelopmentRoot, enter only the Management Group ID, not the full resource path. EPAC constructs the full path automatically. Providing the full path will result in a duplicated path in the configuration, for example:/providers/Microsoft.Management/managementGroups//providers/Microsoft.Management/managementGroups/<id>
Known bug:
New-HydrationGlobalSettingsFilemay throw an error about an emptyLogFilePath. This is a known bug in the module and has no effect on the outcome — the file is created correctly.
Step 6 — Create the Definitions folder structure
New-HydrationDefinitionsFolder -DefinitionsRootFolder ./Definitions
This creates the following folder structure:
Definitions/
├── policyAssignments/
├── policyDefinitions/
├── policySetDefinitions/
├── policyDocumentations/
└── global-settings.jsonc
Step 7 — Add .gitkeep files
Git does not track empty folders. Add a .gitkeep file to each empty folder:
New-Item ./Definitions/policyAssignments/.gitkeep -ItemType File
New-Item ./Definitions/policyDefinitions/.gitkeep -ItemType File
New-Item ./Definitions/policySetDefinitions/.gitkeep -ItemType File
New-Item ./Definitions/policyDocumentations/.gitkeep -ItemType File
Commit and push to your repository.
Step 8 — Create a Service Connection in Azure DevOps
Create a service connection (e.g. sc-epac) with the following RBAC roles on the root Management Group:
| Role | Purpose |
|---|---|
| Resource Policy Contributor | Manage policy definitions and assignments |
| Role Based Access Control Administrator (or Owner) | Create role assignments for managed identities used during remediation |
Step 9 — Generate pipeline files from the Starter Kit
git clone https://github.com/Azure/enterprise-azure-policy-as-code.git ./EPACSource
New-PipelinesFromStarterKit `
-StarterKitFolder ./EPACSource/StarterKit `
-PipelinesFolder ./Pipelines `
-PipelineType AzureDevOps `
-BranchingFlow Release `
-ScriptType Module
This generates the following pipeline files:
| File | Purpose |
|---|---|
epac-dev-pipeline.yml | Plan & deploy for the EPAC development environment |
epac-prod-pipeline.yml | Plan & deploy for production |
epac-nonprod-pipeline.yml | Intermediate environment (optional) |
epac-prod-exemptions-only-pipeline.yml | Deploy exemptions only |
epac-remediation-pipeline.yml | Run remediation tasks |
The ./EPACSource folder is only needed to generate the pipeline files and can be removed afterwards.
Step 10 — Customize the pipeline files
The generated pipeline files require several adjustments before they are ready to use. Review both files carefully and update the following:
epac-dev-pipeline.yml
- Set
pacEnvironmentSelectorto match the name defined inglobal-settings.jsonc:
pacEnvironmentSelector: epac
- Update
additionalPacEnvironmentsToPlanto reference your production PAC selector:
parameters:
- name: additionalPacEnvironmentsToPlan
type: object
default:
- your-production-selector
- If you are using Azure Managed DevOps Pools, update the
poolsection to reference your pool.
epac-prod-pipeline.yml
-
Set
pacEnvironmentSelectorto match your production PAC selector as defined inglobal-settings.jsonc. -
The generated file contains placeholder service connection names, for example:
planServiceConnection: "sc-epac-plan"
deployServiceConnection: "sc-epac-prod-deploy"
rolesServiceConnection: "sc-epac-prod-roles"
Update these to reflect your actual service connection setup. You can use a single service connection for all three roles, or use separate service connections per role — this is an organizational choice depending on your security requirements and the level of separation you want to enforce.
- If you are using Azure Managed DevOps Pools, update the
poolsection accordingly.
Commit and push.
Step 11 — Register the pipelines in Azure DevOps
Create a pipeline in Azure DevOps for each file:
epac-dev-pipeline.yml→ Development pipelineepac-prod-pipeline.yml→ Production pipeline
Next steps
After the initial setup, run an initial test to validate the full pipeline flow:
git checkout -b feature/initial-test
git push --set-upstream origin feature/initial-test
If the dev pipeline is configured with a trigger on feature branches (which the default starter kit config does), this push will automatically start the pipeline in plan-only mode, allowing you to see what EPAC would deploy without making any actual changes.
- Export existing policies using
Export-AzPolicyResourcesto migrate any manually created policies to code - Write your first assignment — start with a small, low-impact policy to validate the full flow
- Run the dev pipeline in plan-only mode to see what EPAC would deploy without making any changes
- Run the prod pipeline after review and approval