Azure Developer CLI Container Deployment: Setup, Usage & Best Practices
The Azure Developer CLI (azd) streamlines deployment of containerized applications to Azure Container Apps with remote builds, managed identity integration, and infrastructure-as-code via Bicep. This skill automates the entire workflow from local development to production deployment, handling Docker builds in Azure Container Registry, provisioning infrastructure idempotently, and managing environment-specific configuration.
What This Skill Does
This deployment framework uses azure.yaml to define services and infrastructure, Bicep templates to provision Azure resources declaratively, and the azd CLI to orchestrate the entire lifecycle. When you run azd up, it provisions infrastructure via Bicep, builds Docker images remotely in Azure Container Registry (avoiding local architecture issues), pushes images, and deploys containers to Azure Container Apps—all in a single command.
Environment variables flow through three layers: local .env files for development, .azure//.env managed by azd with Bicep outputs, and main.parameters.json mapping environment variables to Bicep parameters. This separation enables consistent configuration across environments (dev, staging, production) while keeping secrets secure and infrastructure reproducible.
The framework excels at multi-service deployments where frontend and backend containers need to communicate, share infrastructure like Azure OpenAI or Cognitive Search, and use managed identities for authentication. Hooks let you inject custom logic before and after provisioning or deployment—perfect for RBAC assignments, database migrations, or custom domain preservation. Because Bicep is declarative and remote builds are idempotent, running azd up repeatedly converges infrastructure to the desired state without breaking existing resources.
Getting Started
Install the Azure Developer CLI:
# macOS/Linux
curl -fsSL https://aka.ms/install-azd.sh | bash
# Windows
powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression"
Initialize a project with azd:
azd auth login
azd init # Create azure.yaml and infra/ folder
azd env new dev # Create development environment
azd up # Provision + build + deploy
Project structure after initialization:
project/
├── azure.yaml # Service definitions and hooks
├── infra/
│ ├── main.bicep # Infrastructure code
│ └── main.parameters.json
├── .azure/
│ └── dev/
│ └── .env # Auto-populated from Bicep outputs
└── src/
├── frontend/
└── backend/
Key Features
Remote Builds: Images build in Azure Container Registry, not locally. This solves cross-platform issues (M1 Macs deploying to AMD64 servers) and speeds up builds by reusing cached layers in the cloud.
Idempotent Infrastructure: Bicep templates are declarative. Running azd provision multiple times converges resources to the desired state. Added resources manually? They persist unless explicitly managed by Bicep.
Multi-Service Orchestration: Define frontend, backend, workers, and APIs in azure.yaml. azd deploys them together, handles service discovery (internal DNS), and wires up environment variables automatically.
Environment Management: Create multiple environments (dev, staging, prod) with separate configurations. Switch between them with azd env select. Each environment maintains its own .env file and deployed resources.
Managed Identity & RBAC: Enable system-assigned identities in Bicep, then use hooks to grant role assignments to Azure OpenAI, Cognitive Search, Key Vault, or databases. No secrets in code—authentication via managed identity.
Lifecycle Hooks: Run custom scripts before provisioning, after infrastructure setup, or post-deployment. Use for RBAC, database migrations, certificate uploads, or notification webhooks.
Usage Examples
Basic Two-Service App: Frontend and backend containers:
# azure.yaml
name: my-app
services:
frontend:
project: ./src/frontend
language: ts
host: containerapp
docker:
path: ./Dockerfile
remoteBuild: true
backend:
project: ./src/backend
language: python
host: containerapp
docker:
path: ./Dockerfile
remoteBuild: true
Environment Variables for AI Services: Configure Azure OpenAI and Search:
azd env set AZURE_OPENAI_ENDPOINT "https://my-openai.openai.azure.com"
azd env set AZURE_SEARCH_ENDPOINT "https://my-search.search.windows.net"
azd env set OPENAI_DEPLOYMENT_NAME "gpt-4o-mini"
Map to Bicep parameters in infra/main.parameters.json:
{
"parameters": {
"azureOpenAiEndpoint": {"value": "${AZURE_OPENAI_ENDPOINT}"},
"azureSearchEndpoint": {"value": "${AZURE_SEARCH_ENDPOINT}"},
"openAiDeploymentName": {"value": "${OPENAI_DEPLOYMENT_NAME=gpt-4o}"}
}
}
Post-Provision RBAC Hook: Grant backend managed identity access to AI services:
hooks:
postprovision:
shell: sh
run: |
az role assignment create \
--assignee-object-id "${BACKEND_PRINCIPAL_ID}" \
--assignee-principal-type ServicePrincipal \
--role "Cognitive Services OpenAI User" \
--scope "${OPENAI_RESOURCE_ID}" 2>/dev/null || true
Service-to-Service Communication: Backend URL injected into frontend:
// In frontend Container App definition
env: [
{
name: 'BACKEND_URL'
value: 'http://ca-backend-${resourceToken}'
}
]
Frontend proxies API calls to internal DNS name. No public internet required for service-to-service calls.
Best Practices
Always Use Remote Builds: Set remoteBuild: true in azure.yaml. Local builds fail when developing on ARM (M1/M2 Macs) and deploying to AMD64 servers. Remote builds handle architecture differences automatically.
Don't Manually Edit .azure//.env: This file is managed by azd and regenerated from Bicep outputs. Use azd env set to add variables, and define outputs in Bicep to populate downstream.
Use Hooks with Error Tolerance: Add || true to commands in hooks to prevent "already exists" errors from failing deployments. RBAC assignments and other idempotent operations often return errors on repeat runs.
Tag Container Apps for azd: Bicep resources need the azd-service-name tag matching the service in azure.yaml. Without this, azd can't find your Container Apps during deployment.
Separate Secrets from Config: Use azd env set for secrets (API keys, connection strings). Use main.parameters.json defaults for non-sensitive config (region, SKU sizes).
Version Infrastructure: Commit azure.yaml and infra/ to version control. This makes infrastructure changes reviewable and rollbacks possible. Never put .env or .azure/ in git—they contain secrets.
Test Incrementally: Run azd provision to test infrastructure without deploying code. Run azd deploy --service backend to deploy a single service. Use azd up for full deployments.
When to Use / When NOT to Use
Use azd when:
- You're deploying containerized applications to Azure Container Apps
- You need multi-service architectures (frontend, backend, workers)
- You want infrastructure-as-code with Bicep
- You're building AI applications using Azure OpenAI or Cognitive Services
- You need managed identity authentication to Azure resources
- You want a single command to provision and deploy everything
- You're working in a team and need consistent environments
Avoid azd when:
- You're deploying to AWS or GCP (use their native tools)
- You need Kubernetes-level control (use AKS instead)
- Your app runs on VMs or App Service (use Terraform or ARM templates)
- You have complex multi-region deployments (azd is region-scoped)
- You need GitOps workflows (use Flux or ArgoCD with AKS)
- Your infrastructure is already fully automated with another tool
Related Skills
- agents-v2-py: Deploy container-based AI agents to Azure
- azure-ai-projects-py: Manage Azure AI projects programmatically
- azure-appconfiguration-py: Centralized configuration management
Source
Maintained by Microsoft. View on GitHub