The ultimate Bicep validation pipeline guide: ADO Edition

Photo of author

Dan Rios

7 min read


Finding a robust validation solution for Bicep has been both straight forward and frustrating. We have amazing tools like PSRule (shout out to Bernie for his incredible tool here) for best practice. And more straight forward ones provided natively by Microsoft such as the Bicep linter.

However, the problem in my opinion, when it comes to seeing what would actually deploy with Bicep, we hit a few snags and limitations currently.

With Terraform, a plan output will tell us what is going to be created, deleted or changed. We don’t really have feature parity equivalent with Bicep yet. There is the ‘what if’ function – but it’s fundamentally broken so we have no real output to verify what Bicep will deploy, natively at least.

What if will not work for nested resources, rendering is quite useless for production. You can check more information on the issue here: Resources in nested template not shown if reference is used in the parameter values · Issue #157 · Azure/arm-template-whatif ( and comment to help push this issue along.

However, this means when we talk about a Bicep validation pipeline I find it difficult to put what if as part of the jobs because it is not always representative – hence why you won’t see me use it below. (The Bicep team are aware and are looking to resolve this soon). Now that my rant is over we can get into the juicy stuff :).

So what does my version of the bicep validation pipeline look like at the moment, and how do you implement this to your Azure DevOps environment?

Pre requisites

Some components to this which you’ll need to run through the rest of the guide smoothly.

  • Azure tenant and subscription (at least contributor rights)
  • Defender for Cloud (at least Security Admin role)
  • Azure DevOps organisation setup & project
  • ADO Organisation owner

*Defender for Cloud DevOps Plan is free to use during preview

Validation pipeline components

Microsoft Security DevOps scan

Since we’re taking advantage of the Defender for Cloud by connecting it to our ADO repository, we can leverage this to scan our Bicep with PSRule, secrets and containers if required.


MegaLinter is a super linting tool that can run against a huge list of codebases, such as Bicep (arm-tkk), json, yaml, markdown, etc. So it’s a useful tool to validate our repository files with – as it will ensure the repository remains in good order.

MegaLinter will also run the Bicep linter tool for us so we don’t have to have a separate linting job.

Getting started – ADO components

ADO third party access

  1. In your ADO organisation settings go to security / policies and enable third-party application access via OAuth

Service connection

  1. Create a service connection to your subscription in the Azure DevOps Project:

Microsoft already provide a comprehensive step-by-step for this here: Service connections in Azure Pipelines – Azure Pipelines | Microsoft Learn

2. Scope to the subscription and give it a relevant name. Take a note of this connection name for later.

Install required Azure DevOps extensions

In order for the bicep validation pipeline components to run we need to install some extensions into the ADO organisation so our project can utilise them:

Defender for Cloud – Connecting to your repository

For Defender for Cloud to connect and run scans we must first connect it into the ADO organisation & repo. This is currently in preview so it is free until it moves onto GA. I won’t actually go into too much detail here, mainly Microsoft have great documentation on how to set this up which you can run through here:

Connect your Azure DevOps repositories – Microsoft Defender for Cloud | Microsoft Learn

  1. Add the ADO environment
  2. Create the connector, giving it a relevant name, subscription and selecting a resource group for it to reside in
  3. Select the region. While in preview it is limited to only a handful.
  4. Enable the Defender for DevOps plan
  5. Authorise the connection to ADO (this is where the third party access is required from earlier)
  6. You can further scope the org & project in the connection settings to a specific org and/or project with auto discovery on

Validation Pipeline

Creating the YAML file

This is mostly assuming you’re a greenfield ADO project deployment.

  1. In your ADO project, if you haven’t already, initialise the repository to create the repo
  2. On the left, select Repos > Files > Select the meat balls to create a new file – give it a name like bicep-validation.yaml (or whatever you wish)

If you have branch policies and you already sync a copy of your repo locally via Git then you’ll already know how to create a new file in your root repo, and push this up to your repo.

4. Copy and paste in the YAML pipeline amending the ‘azureServiceConnection’ to the one noted from earlier

bicep/ado-pipelines/bicep-pr-pipeline.yaml at main · riosengineer/bicep (

# The ultimate Bicep validation Pull Request Pipeline

trigger: none

  vmImageName: 'ubuntu-latest'
  azureServiceConnection: 'ConnectionNameHere'

  vmImage: $(vmImageName)

- stage: Validation
  displayName: 'Validation'
      - job: MicrosoftSecurityDevOps
        displayName: Microsoft Security DevOps scan
          - task: MicrosoftSecurityDevOps@1
      - job: MegaLinter
        displayName: Mega Linter
          - checkout: self
          - script: docker pull oxsecurity/megalinter:v7
            displayName: Pull MegaLinter
          - script: |
              docker run -v $(System.DefaultWorkingDirectory):/tmp/lint \
                --env-file <(env | grep -e SYSTEM_ -e BUILD_ -e TF_ -e AGENT_) \
                -e VALIDATE_ALL_CODEBASE=false \
                -e SYSTEM_ACCESSTOKEN=$(System.AccessToken) \
                -e GIT_AUTHORIZATION_BEARER=$(System.AccessToken) \
            displayName: Run MegaLinter
          - task: PublishPipelineArtifact@1
            condition: succeededOrFailed()
            displayName: Upload MegaLinter reports
              targetPath: $(System.DefaultWorkingDirectory)/megalinter-reports/
              artifactName: MegaLinterReport

  - stage: Build
      - job: ArtifactDrop
        displayName: Build Bicep Artifact
          - task: ArchiveFiles@2
              includeRootFolder: true
              replaceExistingArchive: true
              rootFolderOrFile: $(System.DefaultWorkingDirectory)
              archiveType: tar
              archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId)-bicep-plan.tgz
          - upload: $(Build.ArtifactStagingDirectory)/$(Build.BuildId)-bicep-plan.tgz
            artifact: $(Build.BuildId)-bicep-plan.tgz

5. Commit the changes

Creating the validation pipeline

Now to create the pipeline!

  1. On the left navigation pane go to Pipelines > New Pipeline button at the top right
  2. Select Azure Repos Git
  3. Select your repository
  4. Select ‘Existing Azure Pipelines YAML file’ and the drop down should show your YAML file created earlier
  5. Save the pipeline

Create the Azure DevOps build validation policy

Next we need to create the validation policy in Azure DevOps branch to ensure the pipeline will run when a pull request is made to your main branch.

A small qwirk in ADO is no matter what you put in your YAML trigger (e.g. trigger: pr) it will not trigger on PR. We must set a validation policy via repository policies in case you were wondering!

  1. Go to the project settings
  2. Click on Repositories
  3. Click on Policies
  4. Go to Branch policies at the bottom and select your main branch
  5. From here you can click on the Add button to create your validation policy:

When you go to add a new build validation, you will be able to select the YAML file we created earlier from the drop down and specify other necessary requirements you may have for pull requests.

Pipeline overview

Defender for DevOps scan output for Bicep files

Because we installed the SARIF SAST Scans Tab extension, we now get a ‘scans’ (next to summary) tab to review the Defender for DevOps report on the pipeline.

This Defender for DevOps scan leverages PSRule. Each of the recommendations takes you to a rule giving you great detail on the recommendation and how to remediate.

MegaLinter output

So MegaLinter isn’t specifically for Bicep only, but it offers such vast linting features across tons of codebases that I feel it’s too good to miss out on – not only for your Bicep files but also other scripts, readmes, etc.

Our output example:

MegaLinter also supports Pull Request annotation – so the report can be automatically posted as a comment in the PR on completion, which is really nice.

Build stage

Lastly, the build stage. All we’re doing here is packing up the current files in the pull request into an ADO pipeline artifact. We can then utilise this in other pipelines or do more in terms of CI/CD as a next stage if required.

We’re finally done?

Yup. That’s it.

This is my current go to for Bicep validation for now. It’s definitely up for debate on if you could add other checks in, or remove some. I would like one day to add what-if to the validation and deployment stages knowing with confidence they will give us the true output.

Ideally, a lot of these checks are caught from the Bicep VSCode extension so we’re shifting left the issues right into the code phase. However, I like the gate keeping controls this pipeline components provides, especially best practice recommendations and more.

Hope you enjoyed the right up!

Thanks for reading and feel free to comment how you’re doing this, do you have a different approach?

2 thoughts on “The ultimate Bicep validation pipeline guide: ADO Edition”

Leave a comment

Skip to content