DevOps & CloudDocumentedScanned

azd-deployment

Deploy containerized applications to Azure Container Apps using Azure Developer CLI (azd).

Share:

Installation

npx clawhub@latest install azd-deployment

View the full skill documentation and source below.

Documentation

Azure Developer CLI (azd) Container Apps Deployment

Deploy containerized frontend + backend applications to Azure Container Apps with remote builds, managed identity, and idempotent infrastructure.

Quick Start

# Initialize and deploy
azd auth login
azd init                    # Creates azure.yaml and .azure/ folder
azd env new <env-name>      # Create environment (dev, staging, prod)
azd up                      # Provision infra + build + deploy

Core File Structure

project/
├── azure.yaml              # azd service definitions + hooks
├── infra/
│   ├── main.bicep          # Root infrastructure module
│   ├── main.parameters.json # Parameter injection from env vars
│   └── modules/
│       ├── container-apps-environment.bicep
│       └── container-app.bicep
├── .azure/
│   ├── config.json         # Default environment pointer
│   └── <env-name>/
│       ├── .env            # Environment-specific values (azd-managed)
│       └── config.json     # Environment metadata
└── src/
    ├── frontend/Dockerfile
    └── backend/Dockerfile

azure.yaml Configuration

Minimal Configuration

name: azd-deployment
services:
  backend:
    project: ./src/backend
    language: python
    host: containerapp
    docker:
      path: ./Dockerfile
      remoteBuild: true

Full Configuration with Hooks

name: azd-deployment
metadata:
  template: my-project@1.0.0

infra:
  provider: bicep
  path: ./infra

azure:
  location: eastus2

services:
  frontend:
    project: ./src/frontend
    language: ts
    host: containerapp
    docker:
      path: ./Dockerfile
      context: .
      remoteBuild: true

  backend:
    project: ./src/backend
    language: python
    host: containerapp
    docker:
      path: ./Dockerfile
      context: .
      remoteBuild: true

hooks:
  preprovision:
    shell: sh
    run: |
      echo "Before provisioning..."
      
  postprovision:
    shell: sh
    run: |
      echo "After provisioning - set up RBAC, etc."
      
  postdeploy:
    shell: sh
    run: |
      echo "Frontend: ${SERVICE_FRONTEND_URI}"
      echo "Backend: ${SERVICE_BACKEND_URI}"

Key azure.yaml Options

OptionDescription
remoteBuild: trueBuild images in Azure Container Registry (recommended)
context: .Docker build context relative to project path
host: containerappDeploy to Azure Container Apps
infra.provider: bicepUse Bicep for infrastructure

Environment Variables Flow

Three-Level Configuration

  • Local .env - For local development only

  • .azure//.env - azd-managed, auto-populated from Bicep outputs

  • main.parameters.json - Maps env vars to Bicep parameters
  • Parameter Injection Pattern

    // infra/main.parameters.json
    {
      "parameters": {
        "environmentName": { "value": "${AZURE_ENV_NAME}" },
        "location": { "value": "${AZURE_LOCATION=eastus2}" },
        "azureOpenAiEndpoint": { "value": "${AZURE_OPENAI_ENDPOINT}" }
      }
    }

    Syntax: ${VAR_NAME} or ${VAR_NAME=default_value}

    Setting Environment Variables

    # Set for current environment
    azd env set AZURE_OPENAI_ENDPOINT ""
    azd env set AZURE_SEARCH_ENDPOINT ""
    
    # Set during init
    azd env new prod
    azd env set AZURE_OPENAI_ENDPOINT "..."

    Bicep Output → Environment Variable

    // In main.bicep - outputs auto-populate .azure/<env>/.env
    output SERVICE_FRONTEND_URI string = frontend.outputs.uri
    output SERVICE_BACKEND_URI string = backend.outputs.uri
    output BACKEND_PRINCIPAL_ID string = backend.outputs.principalId

    Idempotent Deployments

    Why azd up is Idempotent

  • Bicep is declarative - Resources reconcile to desired state

  • Remote builds tag uniquely - Image tags include deployment timestamp

  • ACR reuses layers - Only changed layers upload
  • Preserving Manual Changes

    Custom domains added via Portal can be lost on redeploy. Preserve with hooks:

    hooks:
      preprovision:
        shell: sh
        run: |
          # Save custom domains before provision
          if az containerapp show --name "$FRONTEND_NAME" -g "$RG" &>/dev/null; then
            az containerapp show --name "$FRONTEND_NAME" -g "$RG" \
              --query "properties.configuration.ingress.customDomains" \
              -o json > /tmp/domains.json
          fi
    
      postprovision:
        shell: sh
        run: |
          # Verify/restore custom domains
          if [ -f /tmp/domains.json ]; then
            echo "Saved domains: $(cat /tmp/domains.json)"
          fi

    Handling Existing Resources

    // Reference existing ACR (don't recreate)
    resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = {
      name: containerRegistryName
    }
    
    // Set customDomains to null to preserve Portal-added domains
    customDomains: empty(customDomainsParam) ? null : customDomainsParam

    Container App Service Discovery

    Internal HTTP routing between Container Apps in same environment:

    // Backend reference in frontend env vars
    env: [
      {
        name: 'BACKEND_URL'
        value: ''  // Internal DNS
      }
    ]

    Frontend nginx proxies to internal URL:

    location /api {
        proxy_pass $BACKEND_URL;
    }

    Managed Identity & RBAC

    Enable System-Assigned Identity

    resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
      identity: {
        type: 'SystemAssigned'
      }
    }
    
    output principalId string = containerApp.identity.principalId

    Post-Provision RBAC Assignment

    hooks:
      postprovision:
        shell: sh
        run: |
          PRINCIPAL_ID="${BACKEND_PRINCIPAL_ID}"
          
          # Azure OpenAI access
          az role assignment create \
            --assignee-object-id "$PRINCIPAL_ID" \
            --assignee-principal-type ServicePrincipal \
            --role "Cognitive Services OpenAI User" \
            --scope "$OPENAI_RESOURCE_ID" 2>/dev/null || true
          
          # Azure AI Search access
          az role assignment create \
            --assignee-object-id "$PRINCIPAL_ID" \
            --role "Search Index Data Reader" \
            --scope "$SEARCH_RESOURCE_ID" 2>/dev/null || true

    Common Commands

    # Environment management
    azd env list                        # List environments
    azd env select <name>               # Switch environment
    azd env get-values                  # Show all env vars
    azd env set KEY value               # Set variable
    
    # Deployment
    azd up                              # Full provision + deploy
    azd provision                       # Infrastructure only
    azd deploy                          # Code deployment only
    azd deploy --service backend        # Deploy single service
    
    # Debugging
    azd show                            # Show project status
    az containerapp logs show -n <app> -g <rg> --follow  # Stream logs

    Reference Files

    • Bicep patterns: See references/bicep-patterns.md for Container Apps modules
    • Troubleshooting: See references/troubleshooting.md for common issues
    • azure.yaml schema: See references/azure-yaml-schema.md for full options

    Critical Reminders

  • Always use remoteBuild: true - Local builds fail on M1/ARM Macs deploying to AMD64

  • Bicep outputs auto-populate .azure//.env - Don't manually edit

  • Use azd env set for secrets - Not main.parameters.json defaults

  • Service tags (azd-service-name) - Required for azd to find Container Apps

  • || true in hooks - Prevent RBAC "already exists" errors from failing deploy