Introduction
Infrastructure as Code (IaC) brings automation and consistency to cloud deployments, but testing can be a challenge. Bicep — Microsoft’s domain-specific language for Azure Resource Manager — simplifies infrastructure definitions. However, without proper validation, teams often encounter deployment issues that could have been caught earlier.
This is where Microsoft Symphony shines. It orchestrates workflows that validate, preview, and test infrastructure templates before deployment. By integrating with either GitHub or Azure DevOps, Symphony connects to your existing infrastructure repository and introduces automated validation pipelines for Bicep or Terraform.
Note: Microsoft Symphony supports both Bicep and Terraform workflows. This post focuses on Bicep usage.
Setting Up Symphony for Bicep Testing
1. Install Symphony and Dependencies
To get started, clone the Symphony repository and follow the setup instructions:
Getting Started Guide: https://github.com/microsoft/symphony/blob/main/docs/GETTING_STARTED.md
On Windows, using WSL is recommended for ease of setup.
# Clone the Symphony repo
git clone https://github.com/microsoft/symphony.git && cd symphony
# Login to Azure
az login
# Set the target subscription
az account set --subscription <TargetSubscriptionId>
# Source the CLI setup
source setup.sh
# Deploy Symphony dependencies
symphony provision
# Configure the orchestrator (GitHub or Azure DevOps, with Bicep or Terraform)
symphony pipeline config <azdo|github> <terraform|bicep>
During setup, you’ll be prompted for:
- GitHub org and repo name
- Whether the repo should be private
- Whether to deploy private runners (recommended)
- Resource group and subnet ID for runners
- GitHub PAT for configuring secrets
- Whether to install sample workflows (yes!)
🛡️ Tip: Installing private runners provides a safer space to test, especially if any secrets are accidentally exposed. GitHub-hosted runners had minor compatibility issues for me during setup. 🗂️ Note: As part of this process, Symphony will create a new GitHub repository. This is where your Bicep code and automated workflow tests will run — separate from your original source repository. You can fork or sync your infrastructure code into this environment to use it for pre-deployment validation.
PR Workflow:
workflow.pr.bicep.yaml
Triggered by pull requests to main, this workflow spins up a temporary environment to test infrastructure changes in isolation.
What It Does
- Generates a unique environment name from a hash
- Validates the Bicep templates
- Stores environment metadata (commit SHA, name, etc.)
- Deploys the base branch to simulate the current state
- Overlays the PR branch to preview changes (
what-if
) - Runs integration or functional tests (if configured)
- Automatically destroys the environment unless labeled to preserve it
Why It Matters
This approach ensures changes are previewed and tested before merging into main
. You get fast feedback, catch issues early, and avoid last-minute surprises in production.
Continuous Integration Workflow: workflow.ci.bicep.yml
This workflow supports manual CI execution using the workflow_dispatch
trigger. It’s ideal for deploying to environments like dev or **test. **It also already contains the on push sections (commented out) ready for your real CI workflows.
Trigger Inputs
From the GitHub Actions UI, specify:
environmentName
(e.g.,dev
)locationName
(e.g.,australiaeast
)
What It Does
- Validates the Bicep templates
- Runs a
what-if
to preview upcoming changes - Deploys the changes if validation passes
- Executes tests in the deployed environment
This gives you full control to test and deploy infrastructure changes as part of a structured CI process.
What Symphony Does Under the Hood
Each stage of a Symphony workflow is powered by familiar, open tools — which means you can extend, debug, or replace them easily. Here’s a look at what happens during each major step:
Validate
Symphony’s validation step includes two critical checks:
- GitLeaks Scans the repository for accidental secrets and sensitive data (like passwords, connection strings, tokens, etc.). This helps catch security issues before anything is deployed.
- Bicep Lint
Runsaz bicep build
and syntax checks to ensure your templates are valid and follow best practices. This includes checking for missing parameters, unsupported properties, and invalid scopes.
Preview and Deploy
This step runs in two parts:
- Bicep What-If Deployment Symphony uses
az deployment what-if
to show exactly what changes would be applied without actually deploying. This helps you catch unintended infrastructure modifications before they go live. - Actual Deployment After the preview, if everything is valid, the infrastructure is deployed using
az deployment create
. This uses your.bicep
and.bicepparam
files along with dynamic environment variables.
Test
Symphony uses Pester (a PowerShell test framework) to validate the deployed resources.
Here’s an example of what a typical test file looks like:
BeforeAll {
Import-Module BenchPress.Azure
$storageAccountResourceGroupName = $env:storageAccountResourceGroupName
$storageAccountName = $env:storageAccountName
$appConfigResourceGroupName = $env:appConfigResourceGroupName
$appConfigAccountName = $env:appConfigAccountName
$appConfigItemsLength = $env:appConfigItemsLength
}
Describe "End to End Tests" {
Context "Test end to end" {
It "should check the parameters and environment variables configured correctly" {
$storageAccountResourceGroupName | Should -Not -Be $Null
$storageAccountName | Should -Not -Be $Null
$appConfigResourceGroupName | Should -Not -Be $Null
$appConfigAccountName | Should -Not -Be $Null
$appConfigItemsLength | Should -BeGreaterThan 0
}
It "should check the storage account is ready" {
$storageAccountResource = Confirm-AzBPStorageAccount -ResourceGroupName $storageAccountResourceGroupName -Name $storageAccountName
$storageAccountResource | Should -Be -Not $null
}
It "should check the storage account is configured correctly" {
$storageAccountResource = Confirm-AzBPStorageAccount -ResourceGroupName $storageAccountResourceGroupName -Name $storageAccountName
$storageAccountResource.ResourceDetails.ProvisioningState | Should -Be "Succeeded"
$storageAccountResource.ResourceDetails.Kind | Should -Be "StorageV2"
$storageAccountResource.ResourceDetails.Sku.Name | Should -Be "Standard_LRS"
$storageAccountResource.ResourceDetails.EnableHttpsTrafficOnly | Should -Be $true
}
To extend these tests, you can verify that the storage account has a private endpoint attached. Here’s an additional test you could add:
It "should have a private endpoint configured for the storage account" {
$peResources = Get-AzBPPrivateEndpoints -ResourceGroupName $storageAccountResourceGroupName -TargetResourceName $storageAccountName
$peResources.Count | Should -BeGreaterThan 0
}
This confirms that your storage account is securely accessible only from within your virtual network — a common security best practice in enterprise environments.
With detailed logging enabled in Pester, developers benefit from rich, readable output — showing every test case, execution time, and diagnostic messages. This makes it easy to understand what’s happening under the hood and quickly spot issues during validation.
At the same time, GitHub Actions provides a clean, high-level summary for managers and stakeholders. The test dashboard gives confidence that infrastructure changes have passed validation before deployment — reinforcing quality and reliability in the delivery pipeline.
What About AVMs?
Validating infrastructure is critical, but using Azure Verified Modules (AVMs) adds another layer of assurance. AVMs are pre-built, Microsoft-supported Bicep modules that follow Azure best practices and undergo rigorous testing before publication.
They reduce the burden of writing, testing, and maintaining complex infrastructure definitions from scratch — especially for common Azure resources like storage accounts, networking, compute, and identity.
However, while AVMs provide confidence in the individual modules, there’s still value in validating how they work together in your actual environment with your specific parameters. Real-world configurations often involve environment-specific customizations, naming conventions, or integrations with existing systems. Testing the assembled infrastructure holistically is where Symphony shines.
By combining AVMs with Symphony:
- You get reusable, trusted building blocks from Microsoft
- You validate the complete deployment as it will behave in your cloud environment
- You catch environment-specific misconfigurations before they become production issues
In short, AVMs are a solid foundation — Symphony helps prove that the house you’ve built on that foundation won’t fall over when you turn it on.
Look out for my next post where I’ll set up a real-life example of where Symphony can be used.
Conclusion
Testing Bicep with Microsoft Symphony ensures issues are caught early in the development cycle. By automating validation, previewing changes, and testing before merging, Symphony empowers teams to ship infrastructure faster and safer — with fewer rollbacks and more confidence.
Explore the Microsoft Symphony Repository:
https://github.com/microsoft/symphony
Start automating your IaC testing today.