Custom PSRule rules let you test Azure Bicep modules against your own organisation rules before deployment. This practical guide explains when to use them, how to structure the `.ps-rule` folder, how to configure `ps-rule.yaml`, and how to run the checks in Azure DevOps.

Azure Bicep · PSRule · DevOps validation

Custom PSRule Rules for Azure Bicep: Shift-Left Policy Checks in Azure DevOps

PSRule for Azure is useful out of the box, but custom rules let you test your own organisation standards before a Bicep module reaches deployment.

This guide shows where custom rules fit, how to structure the `.ps-rule` folder, how to configure Bicep expansion, and how to run the checks in an Azure DevOps pipeline.

Quick answer

Use custom PSRule rules when the built-in Azure best-practice rules are not enough and you need to enforce your own naming, tagging, SKU, location, security or governance standards in the pipeline.

Built-in rulesUse `PSRule.Rules.Azure` to catch common Azure configuration and Well-Architected issues.
Custom rulesAdd organisation-specific checks, such as required tags, approved regions or blocked public access.
Pipeline valueFail early, before a pull request merges or a deployment reaches Azure.
Best practical approach: use Azure Policy for runtime governance and PSRule for pre-deployment feedback. PSRule is not a replacement for Azure Policy, but it helps catch the same style of issue much earlier.

Why use custom PSRule rules?

In a previous article, I covered how to integrate PSRule into an Azure DevOps pipeline to validate Bicep modules against Azure best practices. This follow-up focuses on the next step: adding your own custom checks so the pipeline can catch organisation-specific policy problems before deployment.

PSRule sits in a useful place between a syntax linter and Azure Policy. The Bicep linter helps with Bicep code quality. Azure Policy controls what can exist in Azure. PSRule helps test the expanded resource configuration before deployment, which makes it useful for CI/CD validation and pull request feedback.

ToolWhat it checksWhen it runsWhy it matters
Bicep linter Bicep syntax, style and authoring issues. During development or build validation. Good first layer, but it does not fully replace resource configuration checks.
PSRule.Rules.Azure Azure resource configuration against built-in rules and baselines. Before deployment, usually in CI/CD. Gives fast feedback on Azure best-practice issues.
Custom PSRule rules Your own organisation rules, such as tags, naming, regions or allowed SKUs. Before deployment, usually in pull request or build validation. Moves internal compliance checks left into the engineering workflow.
Azure Policy Runtime Azure governance for deployed or deployable resources. At deployment time and continuously after deployment. Still required for real enforcement in Azure.
Important distinction: custom PSRule rules can simulate some Azure Policy-style checks, but they do not replace Azure Policy assignments. Use PSRule for early feedback and Azure Policy for enforcement in Azure.

Recommended repository structure

The official PSRule for Azure guidance recommends storing standalone custom rules in a root-level `.ps-rule` folder and using file names ending in `.Rule.ps1`, `.Rule.yaml` or `.Rule.jsonc`. This avoids discovery problems in build agents, especially on Linux-based pipelines.

repo-root/
├── modules/
│   ├── storage-account/
│   │   ├── main.bicep
│   │   └── main.test.bicepparam
│   └── app-service/
│       ├── main.bicep
│       └── main.test.bicepparam
├── .ps-rule/
│   └── Org.Azure.Tags.Rule.yaml
├── ps-rule.yaml
└── azure-pipelines.yml
Why `.ps-rule` instead of `ps-rule`? PSRule for Azure recommends `.ps-rule` in lower case for standalone rules. It is easy to miss this when copying older examples, and case sensitivity can matter on Linux build agents.

Create a custom PSRule rule

A YAML rule should use the current PSRule resource format: `apiVersion`, `kind`, `metadata` and `spec`. This is cleaner than older simplified examples because it matches the documented PSRule schema.

This example checks that expanded Azure resources have an `Environment` tag. In a real repository, you may restrict the check further by resource type or use selectors to avoid resources that do not support tags.

---
# Synopsis: Require an Environment tag on Azure resources.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
  name: Org.Azure.Tags.Environment
  displayName: Require Environment tag
  description: Azure resources should include an Environment tag.
spec:
  recommend: Add an Environment tag such as dev, test, prod, or shared.
  condition:
    field: tags.Environment
    exists: true
Practical note: do not try to convert every Azure Policy into a PSRule rule on day one. Start with a small set of high-value checks that developers understand and can fix quickly.

Configure `ps-rule.yaml` for Bicep expansion

Custom rules need PSRule to understand the expanded Azure resources inside your Bicep files. For Bicep source files, enable `AZURE_BICEP_FILE_EXPANSION`. For local standalone rules, configure type binding so rules can match Azure resource objects.

# ps-rule.yaml
configuration:
  AZURE_BICEP_FILE_EXPANSION: true
  AZURE_PARAMETER_FILE_EXPANSION: true
  AZURE_BICEP_FILE_EXPANSION_TIMEOUT: 15

binding:
  targetType:
    - resourceType
    - type

include:
  module:
    - PSRule.Rules.Azure
  • `AZURE_BICEP_FILE_EXPANSION` lets PSRule expand Bicep source files for analysis.
  • `AZURE_PARAMETER_FILE_EXPANSION` helps when you use ARM JSON parameter files.
  • `AZURE_BICEP_FILE_EXPANSION_TIMEOUT` gives larger Bicep files more time to compile during analysis.
  • `binding.targetType` helps standalone custom rules match expanded Azure resource objects.
  • `include.module` keeps the built-in Azure rules available alongside your custom rules.

Run PSRule in Azure DevOps

You can run PSRule in Azure DevOps by using the PSRule Azure DevOps extension, or by calling PowerShell directly. The extension provides tasks such as `ps-rule-install@2` and `ps-rule-assert@2`.

Option 1: Azure DevOps task example

steps:
- checkout: self

- task: ps-rule-install@2
  displayName: Install PSRule.Rules.Azure
  inputs:
    module: PSRule.Rules.Azure

- task: ps-rule-assert@2
  displayName: Run PSRule for Azure
  inputs:
    modules: PSRule.Rules.Azure
    inputPath: '$(System.DefaultWorkingDirectory)'
    option: '$(System.DefaultWorkingDirectory)/ps-rule.yaml'
    outputFormat: NUnit3
    outputPath: '$(Build.ArtifactStagingDirectory)/ps-rule-results.xml'
  continueOnError: true

- task: PublishTestResults@2
  displayName: Publish PSRule results
  inputs:
    testRunner: NUnit
    testResultsFiles: '$(Build.ArtifactStagingDirectory)/ps-rule-results.xml'
    testRunTitle: PSRule
  condition: succeededOrFailed()

Option 2: PowerShell example

If you prefer fewer pipeline-specific dependencies, call PSRule from PowerShell. This is often easier to debug locally because developers can run the same command on their machine.

Install-Module PSRule.Rules.Azure -Scope CurrentUser -Force

Assert-PSRule `
  -Path . `
  -Module PSRule.Rules.Azure `
  -Option ./ps-rule.yaml `
  -Format File `
  -OutputFormat NUnit3 `
  -OutputPath ./reports/ps-rule-results.xml
My preference: keep the pipeline simple first. Once the basic PSRule scan works, add custom rules, SARIF/NUnit publishing, pull request comments or quality gates.

Useful custom PSRule use cases

Required tagsCheck `Environment`, `Owner`, `CostCentre`, `Application` or `DataClassification` tags before deployment.
Approved regionsWarn or fail if a module deploys outside approved Azure regions.
Restricted SKUsBlock expensive, preview or unsupported SKUs in shared modules.
Public accessDetect storage accounts, databases or PaaS services that should not expose public endpoints.
Naming standardsCheck whether resource names follow internal naming conventions.
Policy pre-checksMirror high-value Azure Policy rules so engineers get feedback before deployment.

Common mistakes and troubleshooting

ProblemLikely causeWhat to check
Custom rules are ignored Missing type binding, wrong folder name, wrong file suffix or rules not included. Use `.ps-rule/`, file names ending in `.Rule.yaml`, and configure `binding.targetType`.
Bicep files are not analysed Bicep expansion is not enabled or the pipeline is not using file format processing. Enable `AZURE_BICEP_FILE_EXPANSION` and check your task/PowerShell input settings.
Rule works locally but not in CI Path or casing problem on Linux build agents. Use lower-case `.ps-rule` and avoid hard-coded Windows path assumptions.
Too many failures You enabled too many checks before teams were ready. Start with warning mode or `continueOnError`, then introduce blocking gates gradually.
Developers ignore results The output is hard to read or not tied to pull requests. Publish results, keep rules practical, and document how to fix each failure.

Final thoughts

Custom PSRule rules are useful when your Azure platform standards need to be visible earlier in the development workflow. Instead of waiting for Azure Policy to reject a deployment, you can give engineers a clear signal during pull request validation.

The best implementation is gradual. Start with built-in `PSRule.Rules.Azure` rules, then add a small set of custom rules for the highest-value checks: tags, allowed regions, public access and naming. Once teams trust the feedback, you can make the pipeline stricter.

Best summary: use PSRule to shift feedback left, not to replace governance. Azure Policy remains the enforcement layer. PSRule makes the engineering loop faster.

FAQ

Do custom PSRule rules replace Azure Policy?

No. Custom PSRule rules are useful for early feedback in CI/CD, but Azure Policy should still be used for enforcement and ongoing compliance inside Azure.

Where should custom PSRule rules be stored?

For standalone repository rules, use a root-level `.ps-rule` folder and rule files ending in `.Rule.ps1`, `.Rule.yaml` or `.Rule.jsonc`.

Why are my custom PSRule rules not running?

The common causes are missing `binding.targetType`, incorrect rule file naming, wrong folder casing, Bicep expansion not enabled, or the rule module/path not being included in the pipeline.

Should PSRule fail the pipeline immediately?

Usually not at the start. Begin by publishing results and allowing the pipeline to continue. Once the team understands the failures and fixes, move important rules to a blocking quality gate.

Can PSRule scan Bicep parameter files?

Yes, PSRule for Azure supports expansion options for Bicep source files and parameter files. Configure expansion in `ps-rule.yaml` and test the exact behaviour in your repository structure.

Useful references

Note: PSRule syntax and Azure DevOps task inputs can change. Always confirm against the current PSRule and PSRule.Rules.Azure documentation before using this in a production pipeline.