Azure Policy is a powerful governance tool, but when policies are created manually through the portal they quickly become undocumented and inconsistent across environments. By storing policy definitions in a Git repository and deploying them through GitHub Actions, you get full auditability, peer review, and automated enforcement.

Prerequisites

You will need an Azure subscription, a service principal with the Resource Policy Contributor role, and GitHub repository secrets configured for authentication.

# Create service principal and assign role
$sp = New-AzADServicePrincipal -DisplayName "github-policy-deployer"
New-AzRoleAssignment -ObjectId $sp.Id `
    -RoleDefinitionName "Resource Policy Contributor" `
    -Scope "/subscriptions/$(Get-AzContext).Subscription.Id"

Structuring Your Repository

Organise policies in a consistent folder structure so the GitHub Actions workflow can discover and deploy them automatically.

policies/
  definitions/
    allowed-locations.json
    require-tags.json
  initiatives/
    security-baseline.json
  assignments/
    prod-subscription.json

Deploying Policies with GitHub Actions

The following workflow deploys all policy definitions whenever changes are pushed to the main branch.

name: Deploy Azure Policies
on:
  push:
    branches: [main]
    paths: ['policies/**']

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
      - name: Deploy policy definitions
        run: |
          foreach ($file in Get-ChildItem policies/definitions/*.json) {
            az policy definition create --name $file.BaseName \
              --rules (Get-Content $file.FullName -Raw | ConvertFrom-Json).policyRule \
              --mode All
          }
        shell: pwsh

Enforcing Tag Compliance

A common first policy is requiring specific tags on all resource groups. Here is the JSON definition.

$policy = @{
    "if" = @{
        "field"  = "type"
        "equals" = "Microsoft.Resources/subscriptions/resourceGroups"
    }
    "then" = @{
        "effect" = "deny"
        "details" = @{
            "requiredTags" = @("Environment","Owner","CostCenter")
        }
    }
} | ConvertTo-Json -Depth 10
$policy | Out-File "policies/definitions/require-rg-tags.json"

Summary

Treating Azure Policy as code significantly improves governance consistency. Every change is reviewed, tested, and deployed in a controlled manner — eliminating the risk of accidentally applying overly restrictive policies in production. Combined with Azure Policy’s built-in compliance reporting, you gain end-to-end visibility of your governance posture.