Deploy with Azure DevOps
This guide explains how to deploy the AI Landing Zone (Bicep) from Azure DevOps Pipelines. It covers what the pipelines do, what you need to set up first, how to wire everything together in your project, and how the pipelines relate to the pre-flight checks that run before each deployment.
Use this when you want repeatable, gated deployments to Dev, Test, and Prod
instead of running azd provision by hand. If you only need a one-off local
deployment, see How to Deploy instead.
The pipeline assets live in the landing zone repository under
pipelines/azuredevops/. This page is the source of truth for setup and usage;
the YAML files in that folder are the assets it references.
What the pipelines do
There are two pipelines that work together.
CI pipeline (ci-pipeline.yml) validates the Bicep templates. It runs on
pull requests and on pushes to main, filtered to the paths that affect the
deployment (*.bicep, *.json, modules/, constants/, and
pipelines/azuredevops/). It has a single Validate stage that:
- installs the Bicep CLI,
- compiles the template with
az bicep build, - lints the template with
az bicep lint, - publishes the repository as a pipeline artifact named
bicep-templates.
No Azure sign-in or provisioning happens in CI. It only proves the templates are well formed, and it hands a validated artifact to the CD pipeline.
CD pipeline (cd-pipeline.yml) deploys the landing zone. It does not trigger
automatically (trigger: none and pr: none); you run it on demand and choose
which environments to promote to. It consumes the artifact produced by CI rather
than rebuilding from source, then promotes through Dev, Test, and Prod with
manual approval gates on the environments you choose to protect.
flowchart LR
PR[PR or push to main] --> CI[CI pipeline\nbuild + lint]
CI --> ART[(bicep-templates\nartifact)]
ART --> CD[CD pipeline\non demand]
CD --> Dev[DEV]
Dev --> Test[TEST]
Test --> Prod[PROD]
Deployment stages and gating
When you queue the CD pipeline, you pick the environments to deploy with three
boolean parameters: Deploy to DEV, Deploy to TEST, and Deploy to PROD. All
default to off, so a run deploys only what you opt into.
The stages run in a fixed order through stage dependencies: Dev depends on the
artifact download, Test depends on Dev, and Prod depends on Test. If a selected
environment fails, the later environments are skipped. Each environment deploys
with the reusable deploy-bicep.yml template, which runs azd provision against
that environment.
flowchart LR
Build[Download CI artifact] --> Dev[DEV\nno approval]
Dev --> Test[TEST\napproval gate]
Test --> Prod[PROD\napproval gate]
Approvals are not defined in YAML. They live on the Azure DevOps Environment
resource, so you control them from the portal without editing the pipeline. The
recommended setup is no approval on dev and a required approval on test and
prod. When an approval is configured, the pipeline pauses before that stage
until an approver allows it to continue.
Reusable templates
The pipelines are built from small templates under
pipelines/azuredevops/templates/:
validate-bicep.ymlcompiles and lints the template and publishes the artifact. Used by CI.deploy-bicep.ymlinstallsazdand the Bicep CLI, resolves the deploying principal, sets the azd environment, and runsazd provisionwith an optional retry loop. Used by each CD stage.preview-bicep.ymlrunsazd provision --preview(a what-if) against a target environment. It is provided for teams that want a preview step before deploy.variables.ymlholds the shared values you fill in for your environment.
Prerequisites
Before the first run, set up the following in Azure DevOps.
1. Service connections. Create one Azure Resource Manager service connection
per environment you plan to use (Dev, Test, Prod). Each service principal needs
Contributor and User Access Administrator on the target subscription, because
the landing zone creates resources and assigns roles. You will reference these
connection names in variables.yml.
2. Environments. Create Azure DevOps Environments named dev, test, and
prod. Add an approval check on test and prod so deployments to those
environments pause for a human. Leave dev without an approval for fast
iteration.
3. Variable group. Create a variable group named ailz-secrets and add a
secret variable secretOrRandomPassword. This value is used as the jumpbox VM
admin password in Zero Trust deployments. Link the group to the CD pipeline.
4. Fill in variables.yml. Edit
pipelines/azuredevops/templates/variables.yml and set the values for your
environment:
| Variable | What to set |
|---|---|
azureServiceConnectionDev |
Name of the Dev ARM service connection |
azureServiceConnectionTest |
Name of the Test ARM service connection |
azureServiceConnectionProd |
Name of the Prod ARM service connection |
location |
Azure region for the deployment, for example eastus2 |
environmentName |
Base azd environment name, default ailz-bicep |
deploymentMode |
zeroTrust for network isolation, or basic for public networking |
agentPool |
ubuntu-latest for Microsoft-hosted agents, or your self-hosted pool name |
deployRetryCount |
Extra azd provision attempts on transient Azure failures, default 0 |
templateFile |
Bicep entry point, default main.bicep |
The deploymentMode value maps directly to network isolation. zeroTrust sets
NETWORK_ISOLATION true (VNet, private endpoints, Bastion), and basic sets it
to false for public networking. This is the same toggle described in
How to Deploy.
Note
The pipelines deploy with the Azure Developer CLI (azd provision), which
handles resource group creation, parameter filtering, and principal ID
resolution. You do not need to pre-create the resource group.
How to wire it up in Azure DevOps
1. Create the CI pipeline. In your Azure DevOps project, go to Pipelines, New
pipeline, point it at this repository, and select
pipelines/azuredevops/ci-pipeline.yml. Name it exactly AI Landing Zone - CI.
The CD pipeline references the CI pipeline by that name to pull its artifact, so
the name must match.
2. Create the CD pipeline. Create a second pipeline from
pipelines/azuredevops/cd-pipeline.yml. Link the ailz-secrets variable group
to it (Edit, Variables, Variable groups).
3. Run a deployment. Run the CD pipeline manually. Turn on Deploy to DEV
to start, and add Deploy to TEST or Deploy to PROD when you are ready to
promote. The run downloads the CI-validated artifact, then deploys to each
selected environment in order, pausing at any environment that has an approval
gate.
Tip
For strict promotion where Test must wait for a successful Dev run, change
the Test stage condition to require succeeded('Dev'). The default condition
uses not(failed()), which lets you deploy directly to Test or Prod without
deploying Dev first.
Relationship to the pre-flight checks
Every deployment runs the repository pre-flight script,
scripts/Invoke-PreflightChecks.ps1, through the azd preprovision hook before
anything reaches Azure Resource Manager. The pipelines do not call the script
directly. They run azd provision, and azd runs the hook.
The script is read-only and never changes Azure state. It catches the common topology mistakes that otherwise surface as deep, late ARM errors, such as conflicting Private DNS settings, mutually exclusive hub-integration parameters, invalid IP allow-list shapes, subnet prefixes that overflow or overlap, subnets too small for the services that use them, and bring-your-own resources that were promised but do not exist. It also checks regional readiness, including provider registration, jumpbox VM SKU availability, and AI model quota.
If a check fails, the deployment stops before ARM is called, and the pipeline
reports the failure. You can narrow or skip checks with the script switches and
environment variables documented in the script header, for example
LZ_PREFLIGHT_REGIONAL_SKIP to skip only the regional block, or
PREFLIGHT_SKIP as an emergency escape hatch. Skipping is meant for unblocking,
not for normal runs.
Troubleshooting
The CD pipeline cannot find the CI artifact. Confirm the CI pipeline is named
exactly AI Landing Zone - CI and that it has run successfully at least once, so
the bicep-templates artifact exists.
Deployment fails with a missing password error. Confirm the ailz-secrets
variable group is linked to the CD pipeline and contains a secret variable named
secretOrRandomPassword.
Deployment fails resolving the deployment principal. Set the
AZURE_PRINCIPAL_ID pipeline variable to the Entra object ID of the deploying
identity. The deploy template tries to resolve it automatically from the service
connection, but you can set it explicitly if resolution fails.
Role assignment errors during provisioning. Confirm the service principal
behind the environment's service connection has both Contributor and
User Access Administrator on the target subscription.