Azure Bicep

Azure Bicep at Scale: Architecture, Repos, and Landing Zones Done Right

Every platform team eventually reaches the same awkward moment.

The first few Bicep files were clean. A virtual network here, a storage account there, maybe a Key Vault module that everyone copied with only mild guilt. Then more product teams arrived. More environments appeared. Security wanted policy assignments. Networking needed hub-and-spoke updates. Finance wanted tagging enforced yesterday. Suddenly the “infrastructure repo” looks less like a tidy toolbox and more like the junk drawer in every developer’s kitchen.

That is the point where Bicep architecture stops being about syntax and starts being about operating model.

Bicep is excellent for Azure-native infrastructure. It gives you a readable abstraction over ARM templates, supports modules, works across Azure deployment scopes, and integrates naturally with Azure Resource Manager. Microsoft’s current Azure Landing Zone guidance also leans heavily into Azure Verified Modules and Bicep/Terraform-based accelerators for platform landing zones.

But Bicep will not magically organize your enterprise cloud estate. That part is still on us.

Start With Ownership, Not Folders

The biggest mistake large organizations make with Bicep is treating repository structure as a purely technical decision.

It is not.

Your repo structure should reflect who owns what, how changes are approved, how environments are promoted, and how much freedom product teams should have.

A platform team usually owns shared foundations:

  • Management groups
  • Policy assignments
  • Role assignments
  • Connectivity
  • Shared identity resources
  • Logging and monitoring foundations
  • Subscription vending
  • Landing zone baseline modules

Product teams usually own workload infrastructure:

  • App Service plans
  • Container Apps
  • AKS workloads
  • Storage accounts
  • Databases
  • Messaging resources
  • Application-specific private endpoints
  • App-level diagnostics

That ownership boundary matters more than whether the folder is called modulesinfra, or bicep.

A practical large-scale Bicep repository often looks something like this:

platform-infra/
├── bicepconfig.json
├── modules/
├── network/
├── policy/
├── roleAssignments/
├── monitoring/
└── subscription/
├── orchestration/
├── tenant/
├── managementGroups/
├── connectivity/
├── identity/
├── management/
└── landingZones/
├── environments/
├── dev/
├── test/
├── prod/
└── shared/
├── parameters/
├── dev/
├── test/
└── prod/
└── pipelines/
├── validate.yml
├── deploy-platform.yml
└── deploy-landing-zone.yml

The important idea is separation between reusable modules and orchestration templates.

Modules should be boring, predictable building blocks. Orchestration files should describe how those blocks are assembled for a real environment.

Mono-Repo vs. Poly-Repo: The Real Tradeoff

The mono-repo vs. poly-repo debate gets religious fast. For Bicep, the answer is usually: use a mono-repo for the platform foundation and separate repos for workload teams.

A platform mono-repo makes sense when infrastructure components are tightly coupled. Management groups, policy, connectivity, identity, logging, and subscription vending are not independent islands. A policy assignment change may affect every landing zone. A networking change may require coordination with DNS, firewall, route tables, and private endpoints.

Keeping those in one platform repo gives you:

  • One source of truth for shared architecture
  • Easier cross-module refactoring
  • Centralized validation and linting
  • Better visibility into platform-wide changes
  • Consistent release management

But product teams should not need to open pull requests against the platform repo every time they need a queue, database, or app setting. That creates a bottleneck and turns the platform team into a ticket queue with YAML privileges.

A healthier model is:

platform-infra repo
Owns shared landing zone, policies, networking, subscription vending, shared modules.
product-a-infra repo
Owns workload-specific infrastructure for Product A.
product-b-infra repo
Owns workload-specific infrastructure for Product B.

Shared Bicep modules can be published to a private module registry, which Microsoft explicitly supports for sharing modules across an organization.

That gives product teams autonomy without encouraging copy-paste infrastructure archaeology.

Design Modules Like APIs

A Bicep module is not just a file. It is a contract.

Microsoft describes Bicep modules as Bicep files deployed from other Bicep files, which helps encapsulate complexity and improve reuse. In a platform team, that encapsulation is the difference between “we provide paved roads” and “good luck reading this 900-line template.”

A good module should hide implementation details while exposing intentional choices.

For example, a storage account module should not ask product teams to understand every possible storage property. It should expose the things they are expected to decide:

param name string
param location string
param tags object = {}
param skuName string = 'Standard_LRS'
param allowBlobPublicAccess bool = false
param enableDiagnostics bool = true
resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: name
location: location
tags: tags
sku: {
name: skuName
}
kind: 'StorageV2'
properties: {
allowBlobPublicAccess: allowBlobPublicAccess
minimumTlsVersion: 'TLS1_2'
supportsHttpsTrafficOnly: true
}
}
output id string = storage.id
output name string = storage.name

The module should enforce your defaults. Secure transport should not be optional unless there is an extraordinary reason. Public blob access should not accidentally happen because someone missed a parameter.

That is the power of platform engineering with Bicep: not just automation, but opinionated automation.

Orchestrating Multi-Scope Deployments

Large Azure deployments rarely happen at one scope.

A full landing zone may touch:

  • Tenant scope
  • Management group scope
  • Subscription scope
  • Resource group scope

Bicep supports deployments at different scopes, and modules can be composed to deploy resources at the appropriate level. This is one of the reasons Bicep works well for Azure-native platform infrastructure.

A simplified orchestration model might look like this:

targetScope = 'managementGroup'
param location string = 'eastus'
param platformSubscriptionId string
param connectivitySubscriptionId string
module policy '../modules/policy/baseline.bicep' = {
name: 'deploy-baseline-policy'
params: {
location: location
}
}
module connectivity '../modules/network/connectivity.bicep' = {
name: 'deploy-connectivity'
scope: subscription(connectivitySubscriptionId)
params: {
location: location
}
}

The orchestration file becomes the deployment story. It says: at this management group, assign these policies, deploy this connectivity foundation into that subscription, and pass outputs where needed.

The trick is not to make one mega-template for the entire universe. That way lies sadness, circular dependencies, and pull requests nobody wants to review.

Instead, split orchestration by lifecycle:

orchestration/
├── managementGroups/
├── policy/
├── connectivity/
├── identity/
├── management/
└── workloadLandingZones/

Deploy these in ordered stages. Your CI/CD pipeline should understand that management groups and policy foundations come before subscription-level workload resources.

Parameter Files Are Environment Contracts

For small projects, parameters are just values.

At enterprise scale, parameter files become environment contracts.

Your Bicep files should define the shape of the deployment. Your parameter files should define how dev, test, prod, and shared environments differ.

parameters/
├── dev/
│ └── connectivity.bicepparam
├── test/
│ └── connectivity.bicepparam
└── prod/
└── connectivity.bicepparam

This keeps your templates stable while allowing environments to differ intentionally.

The wrong approach is sprinkling environment logic everywhere:

var sku = environmentName == 'prod' ? 'Premium' : 'Standard'

That looks harmless until every file contains little conditional decisions that nobody can audit easily.

Prefer explicit parameters. Production should be different because you said so clearly, not because a ternary operator was hiding in line 143.

Implementing Azure Landing Zones With Bicep

Azure Landing Zones are not just a deployment pattern. They are an operating model for enterprise Azure: management groups, governance, identity, connectivity, security, monitoring, and workload landing zones working together.

Microsoft’s current Azure Landing Zone Bicep guidance points to the alz-bicep-accelerator approach, which uses Azure Verified Modules to deploy landing zone capabilities in a modular way.

That is important because the older “one giant implementation” approach was hard to customize safely. The newer AVM-based model is more composable. Azure Verified Modules are intended to provide reusable, Microsoft-supported modules aligned with best practices and available for both Bicep and Terraform.

A real-world Bicep landing zone implementation usually has layers:

Layer 1: Tenant and management group hierarchy
Layer 2: Policy definitions and assignments
Layer 3: Platform subscriptions
Layer 4: Connectivity
Layer 5: Identity and management services
Layer 6: Workload landing zones
Layer 7: Product workload infrastructure

That layering matters because dependencies are real. You cannot assign policy to a management group that does not exist. You should not deploy workload subscriptions before you know where diagnostics, networking, DNS, and security controls live.

The pain points are predictable:

First, permissions are harder than the template. Deploying landing zones often requires elevated rights across management groups, subscriptions, and role assignments. This is where many teams discover that “Global Administrator” and “can deploy everything” are not the same thing.

Second, policy can break deployments in surprising ways. That is the point of policy, of course, but it means your Bicep code and your governance model must be tested together.

Third, networking dependencies get complicated quickly. Hub-and-spoke, Azure Firewall, private DNS zones, forced tunneling, route tables, and private endpoints are not difficult individually. They become difficult when every product team needs them slightly differently.

Fourth, module versioning becomes a governance problem. When you publish shared modules, you need semantic versioning, changelogs, deprecation policies, and upgrade guidance. Otherwise, the private registry becomes a prettier copy-paste folder.

When Bicep Is Enough — And When It Is Not

Bicep is usually the right tool when the target is Azure Resource Manager and the team is primarily Azure-focused.

Use Azure Bicep for:

  • Azure-native infrastructure
  • Landing zones
  • Policy assignments
  • Role assignments
  • Resource group and subscription deployments
  • Azure networking
  • Shared platform modules
  • Workload infrastructure on Azure

Use HashiCorp Terraform when:

  • You need one workflow across multiple clouds
  • Your organization already standardized on Terraform state and modules
  • You are managing SaaS providers alongside Azure
  • Your platform team wants the Terraform ecosystem and workflow

Use Pulumi when:

  • Your infrastructure logic benefits from a general-purpose programming language
  • Developers want to define infrastructure with TypeScript, Python, C#, Go, or Java
  • You need richer abstraction patterns than declarative templates provide

Use scripts when:

  • You are doing one-time migration work
  • You need imperative orchestration around APIs
  • The resource provider has gaps
  • You are bootstrapping prerequisites
  • You are gluing deployment stages together

The mistake is pretending one tool must win everywhere.

A practical enterprise architecture may use Bicep for Azure landing zones, Terraform for multi-cloud shared services, PowerShell or Azure CLI for operational tasks, and GitHub Actions or Azure DevOps for orchestration. That is not failure. That is adulthood.

CI/CD Is Part of the Architecture

A Bicep repository without pipelines is just a collection of hopeful text files.

At minimum, your pipeline should:

1. Run formatting and linting
2. Validate Bicep templates
3. Run what-if analysis
4. Require approval for sensitive scopes
5. Deploy in ordered stages
6. Publish versioned modules
7. Record deployment outputs

The what-if step is especially valuable because it gives reviewers a preview of changes before they affect shared infrastructure.

For platform deployments, require stronger controls around:

  • Management group changes
  • Policy assignments
  • Role assignments
  • Networking changes
  • Production subscriptions
  • Module version updates

The goal is not bureaucracy. The goal is confidence.

The Best Repository Structure Is the One That Matches Your Cloud Operating Model

There is no universal perfect Bicep repo.

There is only a repo that matches how your organization builds, reviews, owns, deploys, and supports Azure infrastructure.

For a platform team serving multiple product teams, the winning pattern is usually:

  • Central platform repo for shared landing zone architecture
  • Product-owned repos for workload infrastructure
  • Shared modules published through a private registry
  • AVM adoption where it reduces custom maintenance
  • Clear orchestration layers by Azure scope and lifecycle
  • Environment-specific parameter files
  • Pipelines that validate before they deploy
  • Strong versioning and governance around shared modules

Bicep scales well when we treat it as more than template syntax. It becomes the platform team’s language for expressing Azure architecture.

And like any language, the grammar matters less than the conversations it enables.

The best Bicep architecture gives product teams a paved road, not a locked gate. It gives security and operations guardrails, not a thousand manual reviews. Most importantly, it lets the organization move faster without pretending that enterprise cloud infrastructure is simple.

That is the real promise of Bicep at scale: not fewer files, but fewer surprises.

What patterns have worked best in your organization: one big platform repo, team-owned infrastructure repos, or a hybrid model?

Related Articles

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.