Enterprise Policy as Code (EPAC) — Introduction & Setup Guide

Enterprise Policy as Code (EPAC) — Introduction & Setup Guide

· 7 min read

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:

StrategyBehaviorRecommended for
ownedOnlyEPAC only manages policies it created itself. Manually created policies are left untouched.Existing environments — safest starting point
fullEPAC manages everything. Policies not present in code will be removed.Greenfield environments or a full migration to EPAC

Recommendation: Always start with ownedOnly in an existing environment. Switch to full only 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 pool configuration 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:

ParameterValue
PacOwnerIdThe GUID from step 3
ManagedIdentityLocationYour preferred Azure region (e.g. westeurope)
MainPacSelectorName of your production PAC environment
EpacPacSelectorepac (for the EPAC development environment)
CloudAzureCloud
TenantIdYour tenant ID
MainDeploymentRootManagement Group ID only (without any path prefix)
EpacDevelopmentRootManagement Group ID only (without any path prefix)
StrategyownedOnly (recommended for existing environments)

Important: For MainDeploymentRoot and EpacDevelopmentRoot, 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-HydrationGlobalSettingsFile may throw an error about an empty LogFilePath. 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:

RolePurpose
Resource Policy ContributorManage 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:

FilePurpose
epac-dev-pipeline.ymlPlan & deploy for the EPAC development environment
epac-prod-pipeline.ymlPlan & deploy for production
epac-nonprod-pipeline.ymlIntermediate environment (optional)
epac-prod-exemptions-only-pipeline.ymlDeploy exemptions only
epac-remediation-pipeline.ymlRun 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

  1. Set pacEnvironmentSelector to match the name defined in global-settings.jsonc:
pacEnvironmentSelector: epac
  1. Update additionalPacEnvironmentsToPlan to reference your production PAC selector:
parameters:
  - name: additionalPacEnvironmentsToPlan
    type: object
    default:
      - your-production-selector
  1. If you are using Azure Managed DevOps Pools, update the pool section to reference your pool.

epac-prod-pipeline.yml

  1. Set pacEnvironmentSelector to match your production PAC selector as defined in global-settings.jsonc.

  2. 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.

  1. If you are using Azure Managed DevOps Pools, update the pool section 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 pipeline
  • epac-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.

  1. Export existing policies using Export-AzPolicyResources to migrate any manually created policies to code
  2. Write your first assignment — start with a small, low-impact policy to validate the full flow
  3. Run the dev pipeline in plan-only mode to see what EPAC would deploy without making any changes
  4. Run the prod pipeline after review and approval