Azure
intermediate
35-45 minutes

Azure Container Apps

Deploy containerized applications to Azure Container Apps - serverless containers with automatic scaling.

What you'll accomplish

Your containerized application running on Azure Container Apps

Prerequisites

  • Azure account with subscription
  • Azure CLI installed
  • Docker installed locally
  • Container image or Dockerfile

What is Azure Container Apps?

Azure Container Apps is a serverless container platform:

  • Serverless - No infrastructure management
  • Scale to zero - Pay only when running
  • Built on Kubernetes - KEDA and Dapr integration
  • Microservices-ready - Service discovery, traffic splitting
  • Event-driven - Scale on events, queues, HTTP
Container Image → Container Apps Environment → Your App
                        │
                        ├── Auto-scaling (0 to N)
                        ├── Load balancing
                        ├── Ingress (HTTP/TCP)
                        └── Service discovery

Container Apps vs Other Options

FeatureContainer AppsApp ServiceAKS
ContainersRequiredOptionalRequired
ManagementServerlessManagedSelf-managed K8s
Scale to zeroYesNoYes (with KEDA)
KubernetesHiddenN/AFull access
ComplexityLowLowHigh
Note

Choose Container Apps for microservices, event-driven apps, and when you want Kubernetes benefits without the complexity.

Step 1: Enable Container Apps Extension

bash
# Add Container Apps extension
az extension add --name containerapp --upgrade

# Register required providers
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

Step 2: Create Container Apps Environment

An environment is a secure boundary for container apps:

bash
# Create resource group
az group create --name myapp-rg --location eastus

# Create Container Apps environment
az containerapp env create \
  --name myapp-env \
  --resource-group myapp-rg \
  --location eastus

Environment Features

  • Shared resources - Log Analytics, virtual network
  • Isolation - Apps in same environment can communicate
  • Dapr - Built-in sidecar support
  • Monitoring - Automatic log collection

Step 3: Deploy from Container Registry

Option A: Use Azure Container Registry

bash
# Create container registry
az acr create \
  --name myappregistry \
  --resource-group myapp-rg \
  --sku Basic \
  --admin-enabled true

# Build and push image
az acr build \
  --registry myappregistry \
  --image myapp:latest \
  --file Dockerfile .

# Get registry credentials
ACR_PASSWORD=$(az acr credential show \
  --name myappregistry \
  --query "passwords[0].value" \
  --output tsv)

# Deploy container app
az containerapp create \
  --name myapp \
  --resource-group myapp-rg \
  --environment myapp-env \
  --image myappregistry.azurecr.io/myapp:latest \
  --registry-server myappregistry.azurecr.io \
  --registry-username myappregistry \
  --registry-password $ACR_PASSWORD \
  --target-port 3000 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 10

Option B: Use Docker Hub

bash
az containerapp create \
  --name myapp \
  --resource-group myapp-rg \
  --environment myapp-env \
  --image docker.io/username/myapp:latest \
  --target-port 3000 \
  --ingress external

Option C: Deploy from Source Code

bash
# Deploy directly from source (builds in cloud)
az containerapp up \
  --name myapp \
  --resource-group myapp-rg \
  --environment myapp-env \
  --source . \
  --ingress external \
  --target-port 3000

Step 4: Configure the Application

Get App URL

bash
az containerapp show \
  --name myapp \
  --resource-group myapp-rg \
  --query properties.configuration.ingress.fqdn \
  --output tsv

Environment Variables

bash
# Set environment variables
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --set-env-vars "NODE_ENV=production" "API_URL=https://api.example.com"

# View environment variables
az containerapp show \
  --name myapp \
  --resource-group myapp-rg \
  --query properties.template.containers[0].env

Secrets

bash
# Create secrets
az containerapp secret set \
  --name myapp \
  --resource-group myapp-rg \
  --secrets "db-password=supersecret" "api-key=myapikey"

# Use secrets as env vars
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --set-env-vars "DB_PASSWORD=secretref:db-password"

Resource Limits

bash
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --cpu 0.5 \
  --memory 1.0Gi
CPUMemory Options
0.250.5Gi
0.51.0Gi
0.751.5Gi
1.02.0Gi
1.252.5Gi
1.53.0Gi
1.753.5Gi
2.04.0Gi

Step 5: Scaling Configuration

HTTP Scaling

bash
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --min-replicas 0 \
  --max-replicas 10 \
  --scale-rule-name http-rule \
  --scale-rule-type http \
  --scale-rule-http-concurrency 100

Custom Scale Rules

yaml
# scale-rules.yaml
properties:
  template:
    scale:
      minReplicas: 1
      maxReplicas: 30
      rules:
        - name: queue-based-scaling
          azureQueue:
            queueName: myqueue
            queueLength: 10
            auth:
              - secretRef: connection-string
                triggerParameter: connection
bash
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --yaml scale-rules.yaml

Step 6: Ingress Configuration

External Ingress (Public)

bash
az containerapp ingress enable \
  --name myapp \
  --resource-group myapp-rg \
  --type external \
  --target-port 3000 \
  --transport auto

Internal Ingress (Private)

bash
az containerapp ingress enable \
  --name myapp \
  --resource-group myapp-rg \
  --type internal \
  --target-port 3000

Traffic Splitting

bash
# Deploy new revision
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --image myappregistry.azurecr.io/myapp:v2

# Split traffic 80/20
az containerapp ingress traffic set \
  --name myapp \
  --resource-group myapp-rg \
  --revision-weight myapp--revision1=80 myapp--revision2=20

Step 7: Revisions

Every deployment creates a new revision:

bash
# List revisions
az containerapp revision list \
  --name myapp \
  --resource-group myapp-rg \
  --output table

# Activate/deactivate revision
az containerapp revision activate \
  --name myapp \
  --resource-group myapp-rg \
  --revision myapp--revision1

# Set revision mode (single or multiple active)
az containerapp revision set-mode \
  --name myapp \
  --resource-group myapp-rg \
  --mode multiple

Step 8: Custom Domains

bash
# Add custom domain
az containerapp hostname add \
  --name myapp \
  --resource-group myapp-rg \
  --hostname www.example.com

# Bind managed certificate
az containerapp hostname bind \
  --name myapp \
  --resource-group myapp-rg \
  --hostname www.example.com \
  --environment myapp-env \
  --validation-method CNAME

Configure DNS at your provider:

  • CNAME: www → myapp.azurecontainerapps.io
  • TXT: asuid.www → (validation token)

Step 9: Logging and Monitoring

View Logs

bash
# Stream logs
az containerapp logs show \
  --name myapp \
  --resource-group myapp-rg \
  --follow

# View system logs
az containerapp logs show \
  --name myapp \
  --resource-group myapp-rg \
  --type system

Query Logs (Log Analytics)

bash
# Get Log Analytics workspace
WORKSPACE_ID=$(az containerapp env show \
  --name myapp-env \
  --resource-group myapp-rg \
  --query properties.appLogsConfiguration.logAnalyticsConfiguration.customerId \
  --output tsv)

# Query logs in Azure Portal:
# ContainerAppConsoleLogs_CL
# | where ContainerAppName_s == "myapp"
# | project TimeGenerated, Log_s

Multi-Container Apps

Sidecar Containers

yaml
# app.yaml
properties:
  template:
    containers:
      - name: main-app
        image: myappregistry.azurecr.io/myapp:latest
        resources:
          cpu: 0.5
          memory: 1.0Gi
        env:
          - name: PORT
            value: "3000"
      - name: logging-sidecar
        image: fluent/fluentd:latest
        resources:
          cpu: 0.25
          memory: 0.5Gi
bash
az containerapp update \
  --name myapp \
  --resource-group myapp-rg \
  --yaml app.yaml

Dapr Integration

Enable Dapr for microservices patterns:

bash
# Enable Dapr
az containerapp dapr enable \
  --name myapp \
  --resource-group myapp-rg \
  --dapr-app-id myapp \
  --dapr-app-port 3000 \
  --dapr-app-protocol http

Service Invocation

javascript
// Call another service via Dapr
const response = await fetch('http://localhost:3500/v1.0/invoke/other-service/method/api/data');

State Store

bash
# Create state store component
az containerapp env dapr-component set \
  --name myapp-env \
  --resource-group myapp-rg \
  --dapr-component-name statestore \
  --yaml state-store.yaml

Jobs (Background Tasks)

For scheduled or event-triggered tasks:

bash
# Create scheduled job
az containerapp job create \
  --name myjob \
  --resource-group myapp-rg \
  --environment myapp-env \
  --image myappregistry.azurecr.io/myjob:latest \
  --trigger-type Schedule \
  --cron-expression "0 0 * * *" \
  --replica-timeout 1800

# Create manual job
az containerapp job create \
  --name myjob \
  --resource-group myapp-rg \
  --environment myapp-env \
  --image myappregistry.azurecr.io/myjob:latest \
  --trigger-type Manual

# Start job execution
az containerapp job start \
  --name myjob \
  --resource-group myapp-rg

VNet Integration

Create Environment with VNet

bash
# Create VNet
az network vnet create \
  --name myapp-vnet \
  --resource-group myapp-rg \
  --address-prefix 10.0.0.0/16 \
  --subnet-name infrastructure-subnet \
  --subnet-prefix 10.0.0.0/23

# Create environment in VNet
az containerapp env create \
  --name myapp-env \
  --resource-group myapp-rg \
  --location eastus \
  --infrastructure-subnet-resource-id /subscriptions/.../subnets/infrastructure-subnet \
  --internal-only false

Complete Deployment Example

bash
#!/bin/bash
RESOURCE_GROUP="myapp-rg"
LOCATION="eastus"
ENV_NAME="myapp-env"
APP_NAME="myapp"
REGISTRY_NAME="myappregistry"

# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION

# Create container registry
az acr create \
  --name $REGISTRY_NAME \
  --resource-group $RESOURCE_GROUP \
  --sku Basic \
  --admin-enabled true

# Build and push image
az acr build \
  --registry $REGISTRY_NAME \
  --image $APP_NAME:latest \
  --file Dockerfile .

# Create Container Apps environment
az containerapp env create \
  --name $ENV_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION

# Get registry password
ACR_PASSWORD=$(az acr credential show \
  --name $REGISTRY_NAME \
  --query "passwords[0].value" \
  --output tsv)

# Create container app
az containerapp create \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --environment $ENV_NAME \
  --image $REGISTRY_NAME.azurecr.io/$APP_NAME:latest \
  --registry-server $REGISTRY_NAME.azurecr.io \
  --registry-username $REGISTRY_NAME \
  --registry-password $ACR_PASSWORD \
  --target-port 3000 \
  --ingress external \
  --min-replicas 0 \
  --max-replicas 10 \
  --cpu 0.5 \
  --memory 1.0Gi \
  --env-vars "NODE_ENV=production"

# Get app URL
echo "App URL: https://$(az containerapp show \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --query properties.configuration.ingress.fqdn \
  --output tsv)"

Troubleshooting

Container Won't Start

bash
# Check logs
az containerapp logs show \
  --name myapp \
  --resource-group myapp-rg \
  --type system

# Common issues:
# - Wrong port (must match --target-port)
# - Missing environment variables
# - Image not accessible

Scaling Issues

bash
# Check replica count
az containerapp revision show \
  --name myapp \
  --resource-group myapp-rg \
  --revision REVISION_NAME \
  --query properties.replicas

# Check scale rules
az containerapp show \
  --name myapp \
  --resource-group myapp-rg \
  --query properties.template.scale

Connection Errors

bash
# Check ingress configuration
az containerapp ingress show \
  --name myapp \
  --resource-group myapp-rg

# Verify port matches application
# Ensure --target-port matches what your app listens on
Note

Checkpoint: Deploy a container to Azure Container Apps, configure scaling, and verify it's accessible via the generated URL.