Introduction
Containers revolutionized application deployment, but managing Kubernetes clusters adds operational complexity. Azure Container Apps delivers the best of both worlds: deploy containers with Kubernetes power, but with serverless simplicityβno cluster management, automatic scaling, and pay-per-use pricing.
In this comprehensive guide, you'll master Azure Container Apps from basics through advanced production scenarios. You'll deploy multi-container applications, configure custom domains with TLS, implement blue-green deployments, integrate Dapr for microservices, and build event-driven architectures.
What You'll Learn:
- Azure Container Apps architecture and concepts
- Creating container apps with Azure CLI and Bicep
- Container registry integration (ACR)
- Environment configuration and secrets management
- Auto-scaling rules (HTTP, CPU, memory, custom metrics)
- Ingress configuration and custom domains
- Multi-container applications with sidecars
- Dapr integration for service-to-service communication
- Event-driven scaling with KEDA
- Blue-green and canary deployments
- Monitoring with Application Insights
- Cost optimization strategies
Time to Complete: 90-120 minutes
Skill Level: Intermediate to Advanced
Azure Container Apps Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Azure Container Apps Architecture β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Internet / VNet β
β β β
β βΌ β
β ββββββββββββββββββββββββββββ β
β β Azure Load Balancer β β
β β (Automatic Ingress) β β
β ββββββββββββββ¬ββββββββββββββ β
β β β
β ββββββββββββββΌββββββββββββββ β
β β Container Apps β β
β β Environment β β
β β (Kubernetes Cluster) β β
β ββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββΌββββββββββββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Container β β Container β β Container β β
β β App 1 β β App 2 β β App 3 β β
β β (Frontend) β β (API) β β (Worker) β β
β β β β β β β β
β β βββββββββββ β β βββββββββββ β β βββββββββββ β β
β β βRevision β β β βRevision β β β βRevision β β β
β β β v1.0 β β β β v2.1 β β β β v1.3 β β β
β β β (100%) β β β β (80%) β β β β (100%) β β β
β β βββββββββββ β β βββββββββββ β β βββββββββββ β β
β β β β βββββββββββ β β β β
β β β β βRevision β β β β β
β β β β β v2.0 β β β β β
β β β β β (20%) β β β β β
β β β β βββββββββββ β β β β
β β β β β β β β
β β Replicas: β β Replicas: β β Replicas: β β
β β Min: 1 β β Min: 2 β β Min: 0 β β
β β Max: 10 β β Max: 30 β β Max: 50 β β
β β Current: 3 β β Current: 8 β β Current: 5 β β
β ββββββββ¬βββββββ ββββββββ¬βββββββ ββββββββ¬βββββββ β
β β β β β
β β β β β
β βββββββββββββ¬ββββββββββββ΄ββββββββββββ¬ββββββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββββββββ ββββββββββββββββββββββββ β
β β Dapr Sidecar β β Managed Services β β
β ββββββββββββββββββββββ€ ββββββββββββββββββββββββ€ β
β β β’ Service Invoc. β β β’ Azure SQL β β
β β β’ Pub/Sub β β β’ Cosmos DB β β
β β β’ State Store β β β’ Redis Cache β β
β β β’ Bindings β β β’ Service Bus β β
β ββββββββββββββββββββββ β β’ Storage Account β β
β β β’ Key Vault β β
β ββββββββββββββββββββββββ β
β β
β Scaling Triggers: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β’ HTTP Requests (concurrent requests per replica) β β
β β β’ CPU Utilization (percentage) β β
β β β’ Memory Usage (bytes or percentage) β β
β β β’ Azure Queue Length (Service Bus, Storage Queue) β β
β β β’ Custom Metrics (App Insights, Prometheus) β β
β β β’ Event Hub Messages β β
β β β’ Kafka Topics β β
β β β’ CRON Schedule β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Networking: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β External Ingress: β β
β β β’ Public endpoint with auto-generated domain β β
β β β’ Custom domain with managed certificates β β
β β β’ CORS configuration β β
β β β β
β β Internal Ingress: β β
β β β’ VNet integration (private endpoints) β β
β β β’ Service-to-service within environment (Dapr) β β
β β β’ No public internet exposure β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Security: β
β β’ Managed Identity (Azure AD authentication) β
β β’ Secrets stored in environment (encrypted at rest) β
β β’ TLS/SSL automatic certificates (Let's Encrypt) β
β β’ Network policies (NSG integration) β
β β’ Container image scanning (ACR integration) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Prerequisites
Required Azure Resources
- Azure Subscription with Owner or Contributor role
- Azure Container Registry (or Docker Hub account)
- Resource Group for Container Apps resources
Required Tools
- Azure CLI 2.40+ with containerapp extension
- Docker Desktop (for local container building)
- Visual Studio Code with Docker extension
- Git (for sample application)
Verify Prerequisites
# Check Azure CLI version
az version
# Install Container Apps extension
az extension add --name containerapp --upgrade
# Check Docker installation
docker --version
# Login to Azure
az login
# Set default subscription
az account set --subscription "<Your-Subscription-ID>"
# Verify subscription
az account show --output table
Step 1: Create Container Apps Environment
Create Resource Group
# Set variables
RESOURCE_GROUP="rg-containerapps-prod"
LOCATION="eastus"
ENVIRONMENT_NAME="cae-prod-env"
LOG_ANALYTICS_WORKSPACE="law-containerapps"
echo "Creating resource group..."
az group create \
--name $RESOURCE_GROUP \
--location $LOCATION
echo "β Resource group created: $RESOURCE_GROUP"
Create Log Analytics Workspace
echo "Creating Log Analytics workspace..."
az monitor log-analytics workspace create \
--resource-group $RESOURCE_GROUP \
--workspace-name $LOG_ANALYTICS_WORKSPACE \
--location $LOCATION
# Get workspace credentials
LOG_ANALYTICS_WORKSPACE_ID=$(az monitor log-analytics workspace show \
--resource-group $RESOURCE_GROUP \
--workspace-name $LOG_ANALYTICS_WORKSPACE \
--query customerId \
--output tsv)
LOG_ANALYTICS_WORKSPACE_KEY=$(az monitor log-analytics workspace get-shared-keys \
--resource-group $RESOURCE_GROUP \
--workspace-name $LOG_ANALYTICS_WORKSPACE \
--query primarySharedKey \
--output tsv)
echo "β Log Analytics workspace created"
echo " Workspace ID: $LOG_ANALYTICS_WORKSPACE_ID"
Create Container Apps Environment
echo "Creating Container Apps environment..."
az containerapp env create \
--name $ENVIRONMENT_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--logs-workspace-id $LOG_ANALYTICS_WORKSPACE_ID \
--logs-workspace-key $LOG_ANALYTICS_WORKSPACE_KEY
echo "β Container Apps environment created: $ENVIRONMENT_NAME"
# Get environment details
az containerapp env show \
--name $ENVIRONMENT_NAME \
--resource-group $RESOURCE_GROUP \
--output table
Step 2: Create Azure Container Registry
Deploy ACR
ACR_NAME="acrcontosoprod$RANDOM"
echo "Creating Azure Container Registry..."
az acr create \
--resource-group $RESOURCE_GROUP \
--name $ACR_NAME \
--sku Standard \
--admin-enabled true \
--location $LOCATION
echo "β Container Registry created: $ACR_NAME"
# Get ACR credentials
ACR_LOGIN_SERVER=$(az acr show \
--name $ACR_NAME \
--resource-group $RESOURCE_GROUP \
--query loginServer \
--output tsv)
ACR_USERNAME=$(az acr credential show \
--name $ACR_NAME \
--resource-group $RESOURCE_GROUP \
--query username \
--output tsv)
ACR_PASSWORD=$(az acr credential show \
--name $ACR_NAME \
--resource-group $RESOURCE_GROUP \
--query passwords[0].value \
--output tsv)
echo " Login Server: $ACR_LOGIN_SERVER"
echo " Username: $ACR_USERNAME"
Build and Push Sample Application
# Create sample Node.js application
mkdir -p ~/containerapp-demo
cd ~/containerapp-demo
# Create package.json
cat <<EOF > package.json
{
"name": "containerapp-demo",
"version": "1.0.0",
"description": "Sample API for Azure Container Apps",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.0"
}
}
EOF
# Create server.js
cat <<EOF > server.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// API endpoint
app.get('/api/hello', (req, res) => {
const name = req.query.name || 'World';
res.json({
message: \`Hello, \${name}!\`,
version: '1.0.0',
hostname: require('os').hostname()
});
});
// List all routes
app.get('/', (req, res) => {
res.json({
endpoints: [
{ path: '/', method: 'GET', description: 'List all routes' },
{ path: '/health', method: 'GET', description: 'Health check' },
{ path: '/api/hello', method: 'GET', description: 'Hello API' }
]
});
});
app.listen(port, () => {
console.log(\`Server running on port \${port}\`);
});
EOF
# Create Dockerfile
cat <<EOF > Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
EOF
# Create .dockerignore
cat <<EOF > .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
EOF
echo "β Sample application created"
# Build Docker image
docker build -t $ACR_LOGIN_SERVER/demo-api:v1 .
echo "β Docker image built"
# Login to ACR
az acr login --name $ACR_NAME
# Push image to ACR
docker push $ACR_LOGIN_SERVER/demo-api:v1
echo "β Image pushed to ACR: $ACR_LOGIN_SERVER/demo-api:v1"
# List images in ACR
az acr repository list --name $ACR_NAME --output table
Step 3: Deploy Container App
Create Container App with CLI
CONTAINER_APP_NAME="api-demo"
echo "Deploying container app..."
az containerapp create \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--environment $ENVIRONMENT_NAME \
--image "$ACR_LOGIN_SERVER/demo-api:v1" \
--registry-server $ACR_LOGIN_SERVER \
--registry-username $ACR_USERNAME \
--registry-password $ACR_PASSWORD \
--target-port 3000 \
--ingress external \
--min-replicas 1 \
--max-replicas 10 \
--cpu 0.5 \
--memory 1.0Gi \
--env-vars "PORT=3000" "ENVIRONMENT=production"
echo "β Container app deployed: $CONTAINER_APP_NAME"
# Get app URL
APP_URL=$(az containerapp show \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--query properties.configuration.ingress.fqdn \
--output tsv)
echo "β Application URL: https://$APP_URL"
# Test the application
echo ""
echo "Testing application..."
curl -s "https://$APP_URL/health" | jq
curl -s "https://$APP_URL/api/hello?name=Azure" | jq
Deploy with Bicep
// main.bicep
param location string = resourceGroup().location
param environmentName string = 'cae-prod-env'
param containerAppName string = 'api-demo'
param containerImage string
param containerPort int = 3000
param minReplicas int = 1
param maxReplicas int = 10
// Container Apps Environment
resource environment 'Microsoft.App/managedEnvironments@2023-05-01' existing = {
name: environmentName
}
// Container App
resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
name: containerAppName
location: location
properties: {
managedEnvironmentId: environment.id
configuration: {
ingress: {
external: true
targetPort: containerPort
transport: 'auto'
allowInsecure: false
}
registries: [
{
server: split(containerImage, '/')[0]
username: acrUsername
passwordSecretRef: 'acr-password'
}
]
secrets: [
{
name: 'acr-password'
value: acrPassword
}
]
}
template: {
containers: [
{
name: 'api'
image: containerImage
resources: {
cpu: json('0.5')
memory: '1.0Gi'
}
env: [
{
name: 'PORT'
value: string(containerPort)
}
{
name: 'ENVIRONMENT'
value: 'production'
}
]
}
]
scale: {
minReplicas: minReplicas
maxReplicas: maxReplicas
rules: [
{
name: 'http-rule'
http: {
metadata: {
concurrentRequests: '50'
}
}
}
]
}
}
}
}
output fqdn string = containerApp.properties.configuration.ingress.fqdn
output latestRevisionName string = containerApp.properties.latestRevisionName
# Deploy with Bicep
az deployment group create \
--resource-group $RESOURCE_GROUP \
--template-file main.bicep \
--parameters \
environmentName=$ENVIRONMENT_NAME \
containerAppName=$CONTAINER_APP_NAME \
containerImage="$ACR_LOGIN_SERVER/demo-api:v1" \
minReplicas=1 \
maxReplicas=10
echo "β Bicep deployment completed"
Step 4: Configure Auto-Scaling Rules
HTTP Scaling Rule
echo "Configuring HTTP-based scaling..."
az containerapp update \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--min-replicas 2 \
--max-replicas 20 \
--scale-rule-name http-scale \
--scale-rule-type http \
--scale-rule-http-concurrency 100
echo "β HTTP scaling configured (100 concurrent requests per replica)"
CPU and Memory Scaling Rules
echo "Adding CPU scaling rule..."
az containerapp update \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--scale-rule-name cpu-scale \
--scale-rule-type cpu \
--scale-rule-metadata "type=Utilization" "value=70"
echo "β CPU scaling configured (70% threshold)"
# Add memory scaling
az containerapp update \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--scale-rule-name memory-scale \
--scale-rule-type memory \
--scale-rule-metadata "type=Utilization" "value=80"
echo "β Memory scaling configured (80% threshold)"
Azure Queue Scaling (KEDA)
# Create Storage Account for queue
STORAGE_ACCOUNT="stcontosoqueueprod$RANDOM"
az storage account create \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Standard_LRS
# Get connection string
STORAGE_CONNECTION_STRING=$(az storage account show-connection-string \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--query connectionString \
--output tsv)
# Create queue
az storage queue create \
--name tasks \
--connection-string "$STORAGE_CONNECTION_STRING"
echo "β Storage queue created"
# Add queue scaling rule to worker container app
WORKER_APP_NAME="worker-demo"
az containerapp create \
--name $WORKER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--environment $ENVIRONMENT_NAME \
--image "$ACR_LOGIN_SERVER/demo-api:v1" \
--registry-server $ACR_LOGIN_SERVER \
--registry-username $ACR_USERNAME \
--registry-password $ACR_PASSWORD \
--min-replicas 0 \
--max-replicas 30 \
--cpu 0.25 \
--memory 0.5Gi \
--secrets "storage-connection=$STORAGE_CONNECTION_STRING" \
--scale-rule-name queue-scale \
--scale-rule-type azure-queue \
--scale-rule-metadata "queueName=tasks" "queueLength=10" \
--scale-rule-auth "connection=storage-connection"
echo "β Worker app created with queue-based scaling"
echo " Scales to 0 when queue is empty"
echo " Scales up when queue length > 10 messages"
Step 5: Implement Blue-Green Deployments
Create New Revision (v2)
# Update application code (v2)
cat <<EOF > server.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
app.get('/api/hello', (req, res) => {
const name = req.query.name || 'World';
res.json({
message: \`Hello, \${name}! Welcome to v2!\`,
version: '2.0.0', // Updated version
hostname: require('os').hostname(),
features: ['New UI', 'Better performance', 'Bug fixes'] // New field
});
});
app.get('/', (req, res) => {
res.json({
version: '2.0.0',
endpoints: [
{ path: '/', method: 'GET', description: 'List all routes' },
{ path: '/health', method: 'GET', description: 'Health check' },
{ path: '/api/hello', method: 'GET', description: 'Hello API (v2)' }
]
});
});
app.listen(port, () => {
console.log(\`Server v2 running on port \${port}\`);
});
EOF
# Build and push v2
docker build -t $ACR_LOGIN_SERVER/demo-api:v2 .
docker push $ACR_LOGIN_SERVER/demo-api:v2
echo "β Version 2 image pushed"
Deploy v2 with Traffic Splitting
echo "Deploying v2 with 20% traffic..."
# Update container app with new revision
az containerapp update \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--image "$ACR_LOGIN_SERVER/demo-api:v2"
# Get revision names
REVISION_V1=$(az containerapp revision list \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--query "[?contains(name, 'v1')].name" \
--output tsv | head -n 1)
REVISION_V2=$(az containerapp revision list \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--query "[0].name" \
--output tsv)
echo " Revision v1: $REVISION_V1"
echo " Revision v2: $REVISION_V2"
# Split traffic: 80% to v1, 20% to v2
az containerapp ingress traffic set \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--revision-weight "$REVISION_V1=80" "$REVISION_V2=20"
echo "β Traffic split: 80% v1, 20% v2"
# Test both versions
echo ""
echo "Testing traffic distribution..."
for i in {1..10}; do
curl -s "https://$APP_URL/api/hello" | jq -r '.version'
done
Promote v2 to 100%
echo "Promoting v2 to 100% traffic..."
az containerapp ingress traffic set \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--revision-weight "$REVISION_V2=100"
echo "β v2 is now serving 100% of traffic"
# Deactivate old revision
az containerapp revision deactivate \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--revision $REVISION_V1
echo "β v1 revision deactivated"
# List active revisions
az containerapp revision list \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--query "[?properties.active].{Name:name,Active:properties.active,Traffic:properties.trafficWeight,Created:properties.createdTime}" \
--output table
Step 6: Integrate Dapr for Microservices
Enable Dapr on Container Apps Environment
echo "Enabling Dapr..."
az containerapp env dapr-component set \
--name $ENVIRONMENT_NAME \
--resource-group $RESOURCE_GROUP \
--dapr-component-name statestore \
--yaml dapr-statestore.yaml
# Create Dapr state store configuration
cat <<EOF > dapr-statestore.yaml
componentType: state.azure.blobstorage
version: v1
metadata:
- name: accountName
value: "$STORAGE_ACCOUNT"
- name: accountKey
secretRef: storage-key
- name: containerName
value: "dapr-state"
secrets:
- name: storage-key
value: "$STORAGE_CONNECTION_STRING"
scopes:
- $CONTAINER_APP_NAME
EOF
echo "β Dapr state store configured"
Deploy Dapr-Enabled Container App
# Create Dapr-enabled app
DAPR_APP_NAME="api-dapr-demo"
az containerapp create \
--name $DAPR_APP_NAME \
--resource-group $RESOURCE_GROUP \
--environment $ENVIRONMENT_NAME \
--image "$ACR_LOGIN_SERVER/demo-api:v2" \
--registry-server $ACR_LOGIN_SERVER \
--registry-username $ACR_USERNAME \
--registry-password $ACR_PASSWORD \
--target-port 3000 \
--ingress external \
--min-replicas 1 \
--max-replicas 5 \
--enable-dapr \
--dapr-app-id demo-api \
--dapr-app-port 3000 \
--dapr-app-protocol http
echo "β Dapr-enabled container app deployed"
# Get Dapr components
az containerapp env dapr-component list \
--name $ENVIRONMENT_NAME \
--resource-group $RESOURCE_GROUP \
--output table
Service-to-Service Communication with Dapr
// Example: Calling another service via Dapr
// Add to server.js
app.get('/api/call-service', async (req, res) => {
const daprPort = process.env.DAPR_HTTP_PORT || 3500;
const serviceAppId = 'other-service';
const method = 'api/data';
try {
const response = await fetch(
`http://localhost:${daprPort}/v1.0/invoke/${serviceAppId}/method/${method}`
);
const data = await response.json();
res.json({
success: true,
data: data,
calledVia: 'Dapr service invocation'
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// State management with Dapr
app.post('/api/state/:key', async (req, res) => {
const daprPort = process.env.DAPR_HTTP_PORT || 3500;
const stateStore = 'statestore';
const key = req.params.key;
const value = req.body;
try {
await fetch(
`http://localhost:${daprPort}/v1.0/state/${stateStore}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([{ key: key, value: value }])
}
);
res.json({ success: true, message: 'State saved' });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
Step 7: Configure Custom Domain and TLS
Add Custom Domain
CUSTOM_DOMAIN="api.contoso.com"
echo "Adding custom domain: $CUSTOM_DOMAIN"
# Add domain (requires DNS verification)
az containerapp hostname add \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--hostname $CUSTOM_DOMAIN
echo "β Custom domain added"
echo "β Add CNAME record in DNS:"
echo " CNAME: api"
echo " Target: $APP_URL"
# Bind managed certificate (after DNS verification)
az containerapp hostname bind \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--hostname $CUSTOM_DOMAIN \
--environment $ENVIRONMENT_NAME \
--validation-method CNAME
echo "β TLS certificate bound automatically"
Step 8: Monitoring and Diagnostics
Configure Application Insights
# Create Application Insights
APP_INSIGHTS_NAME="ai-containerapps"
az monitor app-insights component create \
--app $APP_INSIGHTS_NAME \
--location $LOCATION \
--resource-group $RESOURCE_GROUP \
--workspace $LOG_ANALYTICS_WORKSPACE
# Get instrumentation key
INSTRUMENTATION_KEY=$(az monitor app-insights component show \
--app $APP_INSIGHTS_NAME \
--resource-group $RESOURCE_GROUP \
--query instrumentationKey \
--output tsv)
echo "β Application Insights created"
echo " Instrumentation Key: $INSTRUMENTATION_KEY"
# Update container app with App Insights
az containerapp update \
--name $CONTAINER_APP_NAME \
--resource-group $RESOURCE_GROUP \
--set-env-vars "APPINSIGHTS_INSTRUMENTATIONKEY=$INSTRUMENTATION_KEY"
echo "β App Insights integrated"
Query Logs with Kusto
-- Container app logs
ContainerAppConsoleLogs_CL
| where ContainerAppName_s == "api-demo"
| where TimeGenerated > ago(1h)
| project TimeGenerated, Log_s, Stream_s
| order by TimeGenerated desc
| take 100
-- HTTP requests
AppRequests
| where AppRoleName == "api-demo"
| where TimeGenerated > ago(1h)
| summarize
RequestCount = count(),
AvgDuration = avg(DurationMs),
P95Duration = percentile(DurationMs, 95),
FailureCount = countif(Success == false)
by bin(TimeGenerated, 5m)
| order by TimeGenerated desc
-- Scaling events
ContainerAppSystemLogs_CL
| where ContainerAppName_s == "api-demo"
| where Log_s contains "Scaling"
| project TimeGenerated, Log_s, Reason_s
| order by TimeGenerated desc
-- Replica count over time
ContainerAppSystemLogs_CL
| where ContainerAppName_s == "api-demo"
| summarize ReplicaCount = dcount(ReplicaName_s) by bin(TimeGenerated, 1m)
| render timechart
Best Practices Summary
DO:
- β Use managed identities for Azure service authentication
- β Store secrets in environment secrets (not in code)
- β Configure health probes for production workloads
- β Use traffic splitting for gradual rollouts
- β Enable Dapr for microservices patterns
- β Set appropriate CPU/memory limits
- β Configure auto-scaling based on workload patterns
- β Use ACR for private container images
- β Enable Application Insights for monitoring
- β Tag revisions for easy rollback
DON'T:
- β Deploy to production without testing revisions
- β Skip setting min replicas for critical apps (use β₯ 2)
- β Expose internal services publicly (use internal ingress)
- β Hard-code connection strings or credentials
- β Ignore scaling limits (test max load scenarios)
- β Deploy without health check endpoints
- β Use "latest" tag in production (use version tags)
- β Skip monitoring and alerting configuration
- β Deploy large container images (optimize Dockerfile)
- β Forget to clean up old revisions
Key Takeaways
- Serverless simplicity - No cluster management, automatic scaling, pay-per-use
- Container flexibility - Run any containerized app (Linux, Windows)
- Built-in Dapr - Microservices patterns without complexity
- Traffic splitting - Blue-green and canary deployments made easy
- KEDA scaling - Scale to zero for cost optimization
- Managed certificates - Automatic TLS with Let's Encrypt
- Environment isolation - Share infrastructure across apps securely
- Revision management - Instant rollback to previous versions
- VNet integration - Private networking for secure workloads
- Azure ecosystem - Seamless integration with Azure services
Additional Resources
- Azure Container Apps Documentation
- Dapr Documentation
- KEDA Scalers
- Container Apps Samples
- Bicep Templates for Container Apps
Next Steps
- Deploy multi-container apps: Add sidecars for logging, monitoring
- Implement Dapr pub/sub: Event-driven architectures
- Configure VNet integration: Private endpoints for security
- Set up CI/CD: GitHub Actions or Azure DevOps pipelines
- Add authentication: Azure AD or custom auth providers
- Implement rate limiting: Protect APIs from abuse
- Create dashboards: Power BI or Grafana for metrics
- Test disaster recovery: Multi-region deployments
Ready to deploy containers without Kubernetes complexity? Azure Container Apps delivers enterprise-grade container hosting with serverless simplicity!