Azure Deployment Environments

Azure Deployment Environments (ADE) is the piece of the Azure developer platform that solves the “I need a test environment” problem. Developers self-serve environments from approved templates without filing tickets or waiting for infrastructure teams. This page covers how ADE works, how to set up the catalog, and how to wire it into a pipeline.

How Azure Deployment Environments works

ADE has four main components that work together:

  • Dev Center. The top-level Azure resource. Holds catalogs, environment types, and network settings shared across projects.
  • Project. A logical group under the Dev Center, typically corresponding to a team or product. Controls which environment types are available and sets spending limits.
  • Catalog. A git repository (Azure Repos or GitHub) containing environment definition folders. Each folder is one deployable template.
  • Environment type. A named deployment target (Dev, Test, Staging, Prod) with an associated Azure subscription and deployment identity.

When a developer requests an environment, ADE provisions it in the subscription and resource group associated with the environment type. The developer does not need direct Azure access — ADE uses a managed identity to deploy on their behalf.

# Install the ADE CLI extension
az extension add --name devcenter --upgrade

# Create a Dev Center
az devcenter admin devcenter create \
  --name "my-devcenter" \
  --resource-group "platform-rg" \
  --location "eastus" \
  --identity-type SystemAssigned

# Create a project under the Dev Center
az devcenter admin project create \
  --name "my-project" \
  --resource-group "platform-rg" \
  --dev-center-id $(az devcenter admin devcenter show \
    --name my-devcenter \
    --resource-group platform-rg \
    --query id -o tsv) \
  --max-dev-boxes-per-user 3

Setting up the catalog repository

The catalog is a git repository with a specific folder structure. Each subfolder is an environment definition — a folder name that developers will see in the catalog browser. Inside each folder you need at minimum an environment.yaml manifest and your IaC files.

catalog-repo/
├── environments/
│   ├── web-app-with-db/
│   │   ├── environment.yaml      # manifest describing this template
│   │   ├── main.bicep            # or main.tf for Terraform
│   │   └── parameters.json
│   ├── api-service/
│   │   ├── environment.yaml
│   │   ├── main.tf
│   │   └── variables.tf
│   └── microservices-sandbox/
│       ├── environment.yaml
│       └── main.bicep
# environments/web-app-with-db/environment.yaml
name: Web App with Database
version: 1.0.0
summary: App Service (B1) + Azure SQL + Storage Account for dev/test use
description: >
  Provisions a complete web application stack including an App Service Plan,
  App Service, Azure SQL database (Basic tier), and a storage account.
  Suitable for feature branch testing and integration test runs.
runner: Bicep
templatePath: main.bicep
parameters:
  - id: appName
    name: Application Name
    description: Short name used as a prefix for all resources
    type: string
    required: true
  - id: location
    name: Azure Region
    description: Region to deploy into
    type: string
    default: eastus
    allowed:
      - eastus
      - westus2
      - westeurope
// environments/web-app-with-db/main.bicep
@description('Short name used as a prefix for all resources')
param appName string

@description('Azure region')
param location string = resourceGroup().location

@description('Environment type tag')
param environmentType string = 'dev'

var appServicePlanName = '${appName}-plan'
var appServiceName = '${appName}-web'
var sqlServerName = '${appName}-sql-${uniqueString(resourceGroup().id)}'
var storageAccountName = '${replace(appName, '-', '')}${uniqueString(resourceGroup().id)}'

resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
  name: appServicePlanName
  location: location
  sku: {
    name: 'B1'
    tier: 'Basic'
  }
  properties: {}
}

resource appService 'Microsoft.Web/sites@2023-01-01' = {
  name: appServiceName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
  }
  tags: {
    environment: environmentType
    managedBy: 'azure-deployment-environments'
  }
}

resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
  name: sqlServerName
  location: location
  properties: {
    administratorLogin: 'sqladmin'
    administratorLoginPassword: uniqueString(resourceGroup().id, appName)
  }
}

resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-05-01-preview' = {
  parent: sqlServer
  name: '${appName}-db'
  location: location
  sku: {
    name: 'Basic'
    tier: 'Basic'
  }
}

output appServiceUrl string = 'https://${appService.properties.defaultHostName}'
output sqlServerFqdn string = sqlServer.properties.fullyQualifiedDomainName

Connecting the catalog to the Dev Center

# Get the Dev Center's managed identity object ID
DEVCENTER_ID=$(az devcenter admin devcenter show \
  --name my-devcenter \
  --resource-group platform-rg \
  --query identity.principalId -o tsv)

# Grant the Dev Center identity read access to the catalog repo
# (for Azure Repos — also need to configure PAT in the catalog connection)

# Add the catalog to the Dev Center
az devcenter admin catalog create \
  --name "app-templates" \
  --dev-center-name "my-devcenter" \
  --resource-group "platform-rg" \
  --ado-git \
    branch="main" \
    path="/environments" \
    secret-identifier="https://mykeyvault.vault.azure.net/secrets/ado-pat" \
    uri="https://dev.azure.com/contoso/PlatformInfra/_git/catalog-repo"

# Sync the catalog (also happens automatically on git push)
az devcenter admin catalog sync \
  --name "app-templates" \
  --dev-center-name "my-devcenter" \
  --resource-group "platform-rg"

Creating environments from a pipeline

ADE environments can be provisioned programmatically from a CI/CD pipeline. This pattern is useful for creating a fresh environment per pull request, running integration tests against it, then deleting it when the PR closes.

# azure-pipelines.yml — create environment per PR, run tests, then delete
trigger: none
pr:
  branches:
    include:
      - main

variables:
  ENVIRONMENT_NAME: 'pr-$(System.PullRequest.PullRequestId)'
  PROJECT_NAME: 'my-project'
  DEVCENTER_NAME: 'my-devcenter'

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: Provision
    displayName: 'Provision PR Environment'
    jobs:
      - job: CreateEnv
        steps:
          - task: AzureCLI@2
            displayName: 'Create ADE environment'
            inputs:
              azureSubscription: 'My Azure Subscription'
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az extension add --name devcenter --upgrade

                az devcenter dev environment create \
                  --dev-center-name "$(DEVCENTER_NAME)" \
                  --project-name "$(PROJECT_NAME)" \
                  --name "$(ENVIRONMENT_NAME)" \
                  --environment-type "dev" \
                  --catalog-name "app-templates" \
                  --environment-definition-name "web-app-with-db" \
                  --parameters '{"appName": "pr$(System.PullRequest.PullRequestId)"}' \
                  --only-show-errors

                # Get the output URL
                APP_URL=$(az devcenter dev environment show \
                  --dev-center-name "$(DEVCENTER_NAME)" \
                  --project-name "$(PROJECT_NAME)" \
                  --name "$(ENVIRONMENT_NAME)" \
                  --query "outputs.appServiceUrl.value" -o tsv)

                echo "##vso[task.setvariable variable=APP_URL;isOutput=true]$APP_URL"
            name: createEnv

  - stage: Test
    displayName: 'Run Integration Tests'
    dependsOn: Provision
    variables:
      APP_URL: $[ stageDependencies.Provision.CreateEnv.outputs['createEnv.APP_URL'] ]
    jobs:
      - job: IntegrationTests
        steps:
          - script: |
              npm ci
              APP_URL=$(APP_URL) npm run test:integration
            displayName: 'Run integration tests'

  - stage: Cleanup
    displayName: 'Delete PR Environment'
    dependsOn: Test
    condition: always()   # delete even if tests fail
    jobs:
      - job: DeleteEnv
        steps:
          - task: AzureCLI@2
            displayName: 'Delete ADE environment'
            inputs:
              azureSubscription: 'My Azure Subscription'
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az devcenter dev environment delete \
                  --dev-center-name "$(DEVCENTER_NAME)" \
                  --project-name "$(PROJECT_NAME)" \
                  --name "$(ENVIRONMENT_NAME)" \
                  --yes
Tip

Configure environment expiration on environment types in ADE. Set a maximum lifetime (for example, 7 days) so environments are automatically deleted even if a PR is abandoned or the cleanup stage fails. This prevents sprawl and unnecessary Azure spending.

Common mistakes

  1. Using production-tier SKUs in catalog templates. Developers create and destroy environments frequently. If the template provisions P3 App Service Plans and Premium SQL databases, costs explode quickly. Use the cheapest tier that supports the testing use case (B1, Basic SQL, LRS storage) and let production infrastructure be managed separately.
  2. Not granting the Dev Center identity the right roles. ADE uses the Dev Center’s managed identity (or a deployment identity you configure on the environment type) to deploy resources. If that identity lacks Contributor access on the target subscription or resource group, every environment creation will fail with an authorization error.
  3. Storing secrets in Bicep or ARM parameters files in the catalog repo. Template parameters for things like SQL passwords should use securestring parameter types (Bicep/ARM) or Key Vault references. Never commit actual secret values to the catalog repository, even if the repository is private.

Frequently asked questions

What is Azure Deployment Environments?

Azure Deployment Environments (ADE) is a service that lets developers provision their own infrastructure environments on demand using pre-approved templates. Instead of waiting for an ops team to set up a test environment, a developer clicks a button and gets a full environment — App Service, database, storage — provisioned from a template in minutes.

What templates does Azure Deployment Environments support?

ADE supports ARM templates, Bicep files, Terraform configurations, and Pulumi. Templates are stored in a catalog repository (typically an Azure DevOps repo or GitHub repo). Administrators define which templates are available to which projects.

How is Azure Deployment Environments different from Azure DevOps Environments?

These are two different things with similar names. Azure DevOps Environments are a CI/CD concept — they track deployment history and host approval gates for pipelines. Azure Deployment Environments is a separate Azure service for provisioning infrastructure on demand. Both can be used together in a developer platform workflow.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.