Azure Deployment Environments: Self-service para desarrolladores
Resumen
Voy al grano: Azure Deployment Environments es el reemplazo de DevTest Labs, diseñado para que equipos de desarrollo creen entornos completos (App Service + Cosmos + Key Vault + Storage) desde un catálogo de plantillas IaC sin esperar a Platform Engineering. Dev Centers centralizan la gobernanza, Projects definen permisos, y developers despliegan con CLI/Portal. Zero acceso a subscriptions de producción.
¿Qué es Azure Deployment Environments?
Azure Deployment Environments (ADE) es una plataforma de infraestructura self-service que permite a developers crear entornos pre-aprobados desde plantillas IaC (Bicep, ARM, Terraform con extensibilidad).
Actores clave:
| Rol | Responsabilidad |
|---|---|
| Platform Engineers | Crean Dev Centers, definen environment types (Dev, Test, Prod), aprueban catalogs |
| Developers | Crean environments desde definitions aprobadas, despliegan apps |
| Security/Compliance | Auditan deployments, definen Azure Policies en subscriptions target |
Arquitectura:
flowchart TD
subgraph Platform["Platform Engineering Team"]
DC[Dev Center]
CAT[Catalog: GitHub/Azure Repos
Environment Definitions]
ET[Environment Types:
Dev, Test, Prod]
end
subgraph Projects["Development Teams"]
P1[Project: Team A]
P2[Project: Team B]
end
subgraph Developers["Self-service"]
D1[Developer creates
WebApp + DB environment]
D2[Environment deployed
to target subscription]
end
DC --> CAT
DC --> ET
DC --> P1
DC --> P2
P1 --> D1
D1 --> D2
D2 -->|Managed Identity| SUB[(Target Subscription
Resource Group created)]
Ventajas vs. DevTest Labs:
- ✅ IaC nativo: Bicep/ARM/Terraform (vs. custom JSON en Labs)
- ✅ Multi-resource: Despliegues complejos (App Service + dependencies)
- ✅ RBAC granular: Deployment Environments User, Project Admin
- ✅ Integration: Azure CLI, Portal, Azure Developer CLI (azd)
- ✅ Catalogs: Git-based (versioning, PR approval)
Crear Dev Center con catalog
# Variables
RESOURCE_GROUP="rg-devcenter"
LOCATION="westeurope"
DEVCENTER_NAME="dc-contoso"
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# Crear resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Crear Dev Center
az devcenter admin devcenter create \
--name $DEVCENTER_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--identity-type SystemAssigned
# Obtener principal ID de Managed Identity
DEVCENTER_PRINCIPAL=$(az devcenter admin devcenter show \
--name $DEVCENTER_NAME \
--resource-group $RESOURCE_GROUP \
--query identity.principalId -o tsv)
# Asignar roles en subscription (necesario para deployments)
az role assignment create \
--assignee $DEVCENTER_PRINCIPAL \
--role "Contributor" \
--scope "/subscriptions/$SUBSCRIPTION_ID"
az role assignment create \
--assignee $DEVCENTER_PRINCIPAL \
--role "User Access Administrator" \
--scope "/subscriptions/$SUBSCRIPTION_ID"
Managed Identity roles:
- Contributor: Crear recursos en target subscriptions
- User Access Administrator: Asignar RBAC a developers en environments creados
Añadir catalog con environment definitions
# Opción 1: Microsoft Quick Start Catalog (samples)
az devcenter admin catalog create \
--name "QuickStartCatalog" \
--resource-group $RESOURCE_GROUP \
--dev-center $DEVCENTER_NAME \
--git-hub \
uri="https://github.com/microsoft/devcenter-catalog.git" \
branch="main" \
path="/Environments"
# Opción 2: Catalog privado (GitHub con PAT)
GITHUB_PAT="ghp_XXXXXXXXXXXXXXXX" # Personal Access Token
KEYVAULT_NAME="kv-devcenter"
# Crear Key Vault para almacenar PAT
az keyvault create \
--name $KEYVAULT_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION
# Dar acceso al Dev Center al Key Vault
az keyvault set-policy \
--name $KEYVAULT_NAME \
--object-id $DEVCENTER_PRINCIPAL \
--secret-permissions get
# Almacenar PAT
az keyvault secret set \
--vault-name $KEYVAULT_NAME \
--name "github-pat" \
--value "$GITHUB_PAT"
# Crear catalog privado
PAT_SECRET_ID=$(az keyvault secret show --vault-name $KEYVAULT_NAME --name "github-pat" --query id -o tsv)
az devcenter admin catalog create \
--name "PrivateCatalog" \
--resource-group $RESOURCE_GROUP \
--dev-center $DEVCENTER_NAME \
--git-hub \
uri="https://github.com/contoso/infrastructure-templates.git" \
branch="main" \
path="/environments" \
secret-identifier="$PAT_SECRET_ID"
# Opción 3: Azure DevOps Repos
az devcenter admin catalog create \
--name "AzureDevOpsCatalog" \
--resource-group $RESOURCE_GROUP \
--dev-center $DEVCENTER_NAME \
--ado-git \
uri="https://dev.azure.com/contoso/InfrastructureTemplates/_git/environments" \
branch="main" \
path="/" \
secret-identifier="$PAT_SECRET_ID"
Catalog sync: - Manual: Portal → Catalog → Sync button - Automático: Every 30 min (habilitable en catalog settings)
Estructura de Environment Definition
# Repositorio GitHub/Azure DevOps
/environments/
├── webapp-sql/
│ ├── environment.yaml # Manifest
│ ├── main.bicep # IaC template
│ └── parameters.json # Default parameters
├── aks-cluster/
│ ├── environment.yaml
│ └── main.bicep
└── sandbox/
├── environment.yaml
└── main.bicep
environment.yaml (manifest):
name: WebApp with SQL Database
version: 1.0.0
description: Azure App Service with SQL Database and Key Vault
runner: ARM # ARM, Bicep, or custom runner (Terraform via extensibility)
templatePath: main.bicep
parameters:
- id: appName
name: Application Name
description: Name for the App Service
type: string
required: true
- id: sqlAdminUser
name: SQL Admin Username
type: string
default: sqladmin
- id: environment
name: Environment Type
type: string
allowed:
- dev
- test
- prod
default: dev
main.bicep (IaC template):
@description('App Service name')
param appName string
@description('SQL admin username')
param sqlAdminUser string
@description('SQL admin password')
@secure()
param sqlAdminPassword string
@description('Environment type')
@allowed(['dev', 'test', 'prod'])
param environment string = 'dev'
@description('Location')
param location string = resourceGroup().location
// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: 'plan-${appName}-${environment}'
location: location
sku: {
name: environment == 'prod' ? 'P1v3' : 'B1'
tier: environment == 'prod' ? 'PremiumV3' : 'Basic'
}
}
// App Service
resource appService 'Microsoft.Web/sites@2023-01-01' = {
name: '${appName}-${environment}'
location: location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
}
}
// SQL Server
resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
name: 'sql-${appName}-${environment}'
location: location
properties: {
administratorLogin: sqlAdminUser
administratorLoginPassword: sqlAdminPassword
version: '12.0'
}
}
// SQL Database
resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-05-01-preview' = {
parent: sqlServer
name: 'db-${appName}'
location: location
sku: {
name: environment == 'prod' ? 'S1' : 'Basic'
tier: environment == 'prod' ? 'Standard' : 'Basic'
}
}
// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'kv-${appName}-${uniqueString(resourceGroup().id)}'
location: location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
accessPolicies: []
}
}
// Store connection string in Key Vault
resource secret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'SqlConnectionString'
properties: {
value: 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${sqlDatabase.name};User ID=${sqlAdminUser};Password=${sqlAdminPassword};'
}
}
output appServiceUrl string = 'https://${appService.properties.defaultHostName}'
output keyVaultName string = keyVault.name
Environment Types y Projects
# Crear Environment Types en Dev Center (global)
az devcenter admin environment-type create \
--name "Dev" \
--resource-group $RESOURCE_GROUP \
--dev-center $DEVCENTER_NAME
az devcenter admin environment-type create \
--name "Test" \
--resource-group $RESOURCE_GROUP \
--dev-center $DEVCENTER_NAME
az devcenter admin environment-type create \
--name "Prod" \
--resource-group $RESOURCE_GROUP \
--dev-center $DEVCENTER_NAME
# Crear Project (para un team específico)
PROJECT_NAME="project-team-frontend"
az devcenter admin project create \
--name $PROJECT_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--dev-center-id "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.DevCenter/devcenters/$DEVCENTER_NAME" \
--description "Frontend team development environments"
# Configurar Project Environment Types (mapea a subscriptions)
TARGET_SUB_DEV="/subscriptions/$SUBSCRIPTION_ID" # Subscription para Dev
TARGET_SUB_PROD="/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e" # Subscription para Prod
# Dev environment type en project
az devcenter admin project-environment-type create \
--name "Dev" \
--resource-group $RESOURCE_GROUP \
--project $PROJECT_NAME \
--deployment-target-id "$TARGET_SUB_DEV" \
--status "Enabled" \
--identity-type "SystemAssigned" \
--roles "{\"b24988ac-6180-42a0-ab88-20f7382dd24c\":{}}" # Contributor role UUID
# Prod environment type (diferente subscription, más restrictivo)
az devcenter admin project-environment-type create \
--name "Prod" \
--resource-group $RESOURCE_GROUP \
--project $PROJECT_NAME \
--deployment-target-id "$TARGET_SUB_PROD" \
--status "Enabled" \
--identity-type "SystemAssigned" \
--roles "{\"acdd72a7-3385-48ef-bd42-f606fba81ae7\":{}}" # Reader role UUID (restrictivo)
# Asignar developers al project
DEVELOPER_USER_ID="user@contoso.com"
az role assignment create \
--assignee $DEVELOPER_USER_ID \
--role "Deployment Environments User" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.DevCenter/projects/$PROJECT_NAME"
Roles en Project:
- Deployment Environments User: Crear/eliminar environments
- DevCenter Project Admin: Gestionar environment types del project
- Deployment Environments Reader: Solo lectura
Developers: Crear environments
# Listar environment definitions disponibles
az devcenter dev environment-definition list \
--dev-center $DEVCENTER_NAME \
--project-name $PROJECT_NAME \
-o table
# Crear environment
ENVIRONMENT_NAME="frontend-dev-jdoe"
az devcenter dev environment create \
--dev-center-name $DEVCENTER_NAME \
--project-name $PROJECT_NAME \
--environment-name $ENVIRONMENT_NAME \
--environment-type "Dev" \
--catalog-name "QuickStartCatalog" \
--environment-definition-name "webapp-sql" \
--parameters '{
"appName": "myapp",
"sqlAdminUser": "sqladmin",
"sqlAdminPassword": "P@ssw0rd123!",
"environment": "dev"
}' \
--description "John Doe frontend development"
# Ver status del deployment
az devcenter dev environment show \
--dev-center-name $DEVCENTER_NAME \
--project-name $PROJECT_NAME \
--environment-name $ENVIRONMENT_NAME \
--query "{name:name, provisioningState:provisioningState, resourceGroupId:resourceGroupId}" -o yaml
# Listar environments del developer
az devcenter dev environment list \
--dev-center $DEVCENTER_NAME \
--project-name $PROJECT_NAME \
-o table
# Eliminar environment (limpieza)
az devcenter dev environment delete \
--dev-center-name $DEVCENTER_NAME \
--project-name $PROJECT_NAME \
--environment-name $ENVIRONMENT_NAME \
--yes
Developer Portal (UI):
https://devportal.microsoft.com
→ Projects → Select Project
→ New Environment
→ Choose Definition (webapp-sql)
→ Fill Parameters
→ Deploy
Azure Developer CLI integration
# Instalar azd
curl -fsSL https://aka.ms/install-azd.sh | bash
# Inicializar proyecto azd con ADE
mkdir my-app && cd my-app
azd init --template minimal
# Configurar para usar ADE
cat > azure.yaml <<EOF
name: my-app
services:
web:
project: ./src
language: dotnet
host: appservice
pipeline:
provider: azdo
platform:
type: devcenter
EOF
# Crear environment con azd
azd env new dev-jdoe
# Set ADE variables
azd env set AZURE_DEVCENTER_NAME $DEVCENTER_NAME
azd env set AZURE_DEVCENTER_PROJECT $PROJECT_NAME
azd env set AZURE_DEVCENTER_ENVIRONMENT_TYPE "Dev"
azd env set AZURE_DEVCENTER_CATALOG_NAME "QuickStartCatalog"
azd env set AZURE_DEVCENTER_DEFINITION_NAME "webapp-sql"
# Provision infrastructure
azd provision
# Deploy application code
azd deploy
azd vs. az CLI:
- azd: Developer workflow (provision + deploy code)
- az devcenter: Admin/Platform Engineering tasks
Buenas prácticas
Catalog organization:
/environments/
├── web-apps/ # Agrupado por tipo
│ ├── dotnet-webapp/
│ └── nodejs-webapp/
├── databases/
│ ├── sql-basic/
│ └── cosmos-nosql/
└── shared-services/
├── monitoring-stack/
└── networking/
Environment Definition naming:
- Descriptivo: webapp-postgresql-redis vs. env1
- Versionado: v1.0.0 en environment.yaml
- Tags: Incluye tags en Bicep para cost tracking
tags: {
Environment: environment
ManagedBy: 'AzureDeploymentEnvironments'
CostCenter: '${appName}-${environment}'
}
Security:
# Secretos NUNCA en parameters (usa Key Vault references)
param sqlAdminPassword string = '@Microsoft.KeyVault(SecretUri=${keyVaultSecretUri})'
# Managed Identity para acceso a resources
resource appService 'Microsoft.Web/sites@2023-01-01' = {
identity: {
type: 'SystemAssigned'
}
}
# RBAC en target subscriptions
az role assignment create \
--assignee $DEVCENTER_PRINCIPAL \
--role "Contributor" \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/rg-allowed-*" # Scope limitado
Cost management:
// Auto-shutdown VMs en Dev environments
resource vm 'Microsoft.Compute/virtualMachines@2023-09-01' = if (environment == 'dev') {
properties: {
scheduledEventsProfile: {
terminateNotificationProfile: {
enable: true
notBeforeTimeout: 'PT5M'
}
}
}
}
// SKUs por environment type
var skuMap = {
dev: 'B1'
test: 'S1'
prod: 'P1v3'
}
Monitorización y governance
# Ver deployments en Activity Log
az monitor activity-log list \
--resource-group "rg-ade-environments-*" \
--caller $DEVCENTER_PRINCIPAL \
--query "[?operationName.value contains 'Microsoft.Resources/deployments']" -o table
# Azure Policy para compliance (aplicado en target subscription)
az policy assignment create \
--name "EnforceTagsOnEnvironments" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/96670d01-0a4d-4649-9c89-2d3abc0a5025" \
--scope "/subscriptions/$SUBSCRIPTION_ID" \
--params '{
"tagName": {"value": "ManagedBy"},
"tagValue": {"value": "AzureDeploymentEnvironments"}
}'
# Cost analysis por environment
az consumption usage list \
--start-date 2025-10-01 \
--end-date 2025-10-31 \
--query "[?contains(tags.ManagedBy, 'AzureDeploymentEnvironments')].{name:instanceName, cost:pretaxCost}" \
--output table
Azure Monitor queries:
// Environments creados por usuario
AzureActivity
| where OperationNameValue contains "MICROSOFT.DEVCENTER/PROJECTS/ENVIRONMENTS/WRITE"
| summarize count() by Caller, bin(TimeGenerated, 1d)
| render timechart
// Failed deployments
AzureActivity
| where ResourceProvider == "Microsoft.DevCenter"
| where ActivityStatusValue == "Failed"
| project TimeGenerated, Caller, OperationNameValue, ActivityStatusValue, Properties
Costos
Pricing model: - €0.007/vCore-hour (aprox.) por environments activos - Catalogs: Sin coste (storage en Git gratuito) - Recursos desplegados: Según pricing de cada servicio (App Service, SQL, etc.)
Ejemplo mensual:
Team de 10 developers:
- 10 environments Dev activos 24/7 × 720h × €0.007/vCore = €50/mes
- Recursos Azure (App Service B1 + SQL Basic) × 10 = €100/mes
- Total ADE: €150/mes
Vs. manual provisioning:
- Platform Engineer time: 20h/mes × €80/h = €1,600/mes
- Ahorro: €1,450/mes (90% reducción)
Free tier: - No existe free tier específico - Alternativa: Usa Azure Free Account (primeros 12 meses con créditos)