Skip to main content

Theoretical Foundation: Provision a container by using Azure Container Apps


1. Initial Intuition​

In previous modules, we saw two extremes of the container spectrum in Azure: ACI (simple execution, no management, ideal for one-off tasks) and AKS (complete orchestration with Kubernetes, ideal for complex production architectures). Azure Container Apps (ACA) occupies the space between them.

Think of it this way: ACI is like renting a hotel room for one night. AKS is like managing a complete condominium, you have full control but need to take care of everything. Azure Container Apps is like living in a co-living service: you have your independent space, don't need to worry about the building infrastructure, can automatically receive more guests when needed, and only pay for what you use.

ACA is built on top of Kubernetes and KEDA (Kubernetes Event-Driven Autoscaling) internally, but you never need to interact with these systems directly. Azure manages the entire underlying Kubernetes cluster. You simply define: "my application is this container, it should scale from 0 to 10 replicas based on the number of HTTP requests per second".


2. Context​

ACA was launched by Microsoft as a response to the growth of microservices and event-driven architectures that need sophisticated automatic scaling (including scale-to-zero) without the operational complexity of Kubernetes.

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

ACA is built on open source technologies: Kubernetes for orchestration, KEDA for event-based autoscaling, Dapr for microservice communication, and Envoy as a service proxy. You benefit from all of this without needing to configure anything manually.


3. Concept Construction​

3.1 Resource Hierarchy: Environment, App, Revision​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Container Apps Environment: the logical container that groups multiple Container Apps. All apps within an environment share the same VNet, the same Log Analytics workspace, and can communicate with each other via internal DNS. An environment corresponds to a Kubernetes cluster managed by Azure.

Container App: the application itself. Defines the container image, environment variables, scaling, ingress and other configurations. A Container App can have multiple Revisions active simultaneously.

Revision: an immutable version of a Container App. Each time you update a configuration that affects the container (image, environment variables, CPU/memory), a new revision is created. You can distribute traffic between revisions for canary deployments or blue/green.

Replica: a running instance of a revision. The autoscaler increases or decreases the number of replicas based on configured rules.

3.2 Revision Modes​

Revision behavior depends on the Revision Mode:

ModeBehaviorUse Case
Single (default)Only one active revision at a time. New revision automatically replaces the old one.Simple applications without A/B testing needs
MultipleMultiple active revisions simultaneously. Traffic can be split between them.Canary deployments, A/B testing, blue/green

3.3 Ingress: Container App Access​

Ingress controls how the Container App is accessible:

TypeDescriptionGenerated URL
External (enabled)Accessible from internet via HTTPShttps://my-api.<hash>.brazilsouth.azurecontainerapps.io
Internal (enabled)Accessible only within the environmenthttps://my-api (short name)
DisabledNo network access (jobs, processors)No URL

ACA automatically manages:

  • TLS certificate (HTTPS enabled by default for external ingress)
  • Load balancing between replicas
  • Custom domains (with your own certificate or free managed certificate)

3.4 Autoscaling with KEDA​

ACA autoscaling is based on scalers (scaling triggers). The most important for AZ-104:

Scaler TypeWhat it monitorsExample
HTTPConcurrent HTTP requestsScale when > 10 concurrent req per replica
CPUCPU usageScale when CPU > 70%
MemoryMemory usageScale when memory > 80%
Azure Service BusMessages in queue1 replica per message in queue
Azure Storage QueueMessages in queue1 replica per 5 messages
CustomAny metric via KEDAAny source supported by KEDA

Scale-to-zero: when there's no traffic or messages in queue, the number of replicas can reach zero. The container doesn't consume CPU or memory, and you don't pay for compute. When a new request arrives, ACA automatically starts a replica (with cold start latency, usually seconds).

The scale range is defined by minReplicas and maxReplicas:

scale:
minReplicas: 0 # scale-to-zero active
maxReplicas: 10 # maximum of 10 replicas
rules:
- name: http-rule
http:
metadata:
concurrentRequests: "10" # 1 replica for every 10 concurrent req

3.5 Jobs vs. Apps​

Besides Container Apps (long-running services), ACA supports Container Apps Jobs: executions that terminate after completing a task, similar to ACI with restartPolicy: Never.

TypeDurationTriggerBilling
Container AppContinuousTraffic / messagesPer vCPU/s and GB/s used
Container App JobFinite (terminates)Manual, Scheduled (cron), Event-drivenPer execution

4. Structural View​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

5. Practical Operation​

Deployment Lifecycle​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Important and Non-Obvious Behaviors​

Revision names are auto-generated: each new revision receives a name in the format <app-name>--<random-suffix>. You can specify a custom suffix: --revision-suffix v2-release.

Scale-to-zero has cold start: when there are 0 replicas and a request arrives, ACA needs to start a new replica before responding. This takes seconds. For latency-sensitive applications, configure minReplicas: 1 to always have at least one replica ready, at the cost of continuous billing.

Dapr is optional but integrated: ACA has native integration with Dapr (Distributed Application Runtime) for microservice patterns like pub/sub, service invocation and state management. You enable Dapr per app with a flag, without manual installation.

Environments have network isolation: each environment can be created in a VNet. Apps within the same environment communicate via internal DNS without going to the internet. Apps in different environments need communication via public URL or VNet peering.


6. Implementation Methods​

6.1 Azure Portal​

When to use: initial exploratory creation, traffic management between revisions, metrics visualization.

Path: Create a resource > Containers > Container App

The wizard automatically creates a Container Apps Environment if one doesn't exist, or allows selecting an existing one.

6.2 Azure CLI​

Create Container Apps Environment:

# Create Log Analytics workspace (required for environment)
az monitor log-analytics workspace create \
--resource-group rg-containers \
--workspace-name law-aca \
--location brazilsouth

LAW_ID=$(az monitor log-analytics workspace show \
--resource-group rg-containers \
--workspace-name law-aca \
--query customerId -o tsv)

LAW_KEY=$(az monitor log-analytics workspace get-shared-keys \
--resource-group rg-containers \
--workspace-name law-aca \
--query primarySharedKey -o tsv)

# Create the environment
az containerapp env create \
--name env-production \
--resource-group rg-containers \
--location brazilsouth \
--logs-workspace-id $LAW_ID \
--logs-workspace-key $LAW_KEY

Create basic Container App:

az containerapp create \
--name my-api \
--resource-group rg-containers \
--environment env-production \
--image myacr.azurecr.io/api:v1.0 \
--cpu 0.5 \
--memory 1.0Gi \
--min-replicas 1 \
--max-replicas 5 \
--target-port 8080 \
--ingress external \
--registry-server myacr.azurecr.io \
--registry-identity system \
--env-vars ENVIRONMENT=production DB_HOST=my-db.postgres.database.azure.com

Update to new image version:

az containerapp update \
--name my-api \
--resource-group rg-containers \
--image myacr.azurecr.io/api:v2.0 \
--revision-suffix v2-release

Manage traffic between revisions (Multiple mode):

# First, enable Multiple mode
az containerapp revision set-mode \
--name my-api \
--resource-group rg-containers \
--mode multiple

# List available revisions
az containerapp revision list \
--name my-api \
--resource-group rg-containers \
--output table

# Split traffic: 90% v1, 10% v2
az containerapp ingress traffic set \
--name my-api \
--resource-group rg-containers \
--revision-weight \
my-api--v1-release=90 \
my-api--v2-release=10

# After validation: 100% to v2
az containerapp ingress traffic set \
--name my-api \
--resource-group rg-containers \
--revision-weight my-api--v2-release=100

Configure HTTP-based autoscaling:

az containerapp update \
--name my-api \
--resource-group rg-containers \
--min-replicas 0 \
--max-replicas 10 \
--scale-rule-name http-scaler \
--scale-rule-type http \
--scale-rule-http-concurrency 20

Configure Azure Service Bus-based autoscaling:

az containerapp update \
--name processor \
--resource-group rg-containers \
--min-replicas 0 \
--max-replicas 15 \
--scale-rule-name sb-scaler \
--scale-rule-type azure-servicebus \
--scale-rule-metadata \
queueName=processing-queue \
namespace=my-servicebus \
messageCount=5 \
--scale-rule-auth \
connectionString=secretRef:sb-connection-string

Use secrets for credentials:

# Create secret
az containerapp secret set \
--name my-api \
--resource-group rg-containers \
--secrets db-password=MySecurePassword123

# Reference secret as environment variable
az containerapp update \
--name my-api \
--resource-group rg-containers \
--set-env-vars "DB_PASSWORD=secretref:db-password"

Create Container App Job (scheduled single execution):

az containerapp job create \
--name job-daily-report \
--resource-group rg-containers \
--environment env-production \
--trigger-type Schedule \
--cron-expression "0 8 * * *" \
--image myacr.azurecr.io/report:latest \
--cpu 1 \
--memory 2.0Gi

6.3 Bicep​

resource environment 'Microsoft.App/managedEnvironments@2023-05-01' = {
name: 'env-production'
location: location
properties: {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalyticsWorkspaceId
sharedKey: logAnalyticsSharedKey
}
}
}
}

resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
name: 'my-api'
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
managedEnvironmentId: environment.id
configuration: {
ingress: {
external: true
targetPort: 8080
transport: 'http'
}
registries: [
{
server: 'myacr.azurecr.io'
identity: 'system'
}
]
secrets: [
{
name: 'db-password'
value: dbPassword
}
]
}
template: {
containers: [
{
name: 'api-container'
image: 'myacr.azurecr.io/api:v1.0'
resources: {
cpu: json('0.5')
memory: '1.0Gi'
}
env: [
{
name: 'ENVIRONMENT'
value: 'production'
}
{
name: 'DB_PASSWORD'
secretRef: 'db-password'
}
]
}
]
scale: {
minReplicas: 1
maxReplicas: 10
rules: [
{
name: 'http-scaler'
http: {
metadata: {
concurrentRequests: '20'
}
}
}
]
}
}
}
}

7. Control and Security​

ACR Authentication via Managed Identity​

ACA supports System-assigned and User-assigned Managed Identities. For pulling images from ACR, use the system identity:

# Create app with system-assigned identity
az containerapp create \
--name my-api \
--registry-server myacr.azurecr.io \
--registry-identity system \
...

# Assign AcrPull to the identity (needed after creating the app)
APP_IDENTITY=$(az containerapp show \
--name my-api \
--resource-group rg-containers \
--query identity.principalId -o tsv)

ACR_ID=$(az acr show --name myacr --query id -o tsv)

az role assignment create \
--assignee $APP_IDENTITY \
--role AcrPull \
--scope $ACR_ID

Built-in Authentication (Easy Auth)​

ACA has integration with Easy Auth (codeless authentication): you can protect a Container App by requiring users to authenticate with Entra ID, GitHub, Google or other providers before accessing the application, without any changes to the container code.

az containerapp auth microsoft update \
--name my-api \
--resource-group rg-containers \
--client-id <entra-app-client-id> \
--client-secret <client-secret> \
--issuer https://login.microsoftonline.com/<tenant-id>/v2.0 \
--yes

Network Isolation​

Environments can be created in a VNet for network isolation. Two types:

TypeExternal accessUse cases
External EnvironmentPublic IP for external ingressApplications accessible from internet
Internal EnvironmentOnly via VNet (no public IP)Internal applications, internal service communication

8. Decision Making​

ACA vs. other container services​

SituationBest choiceReason
REST API with variable traffic, scale-to-zeroACANative scale-to-zero, pay-per-use billing
Microservices with event-driven communicationACA with KEDAQueue/topic-based autoscaling
Easy canary deployment or A/B testingACA (Revision traffic)Native traffic splitting between revisions
Scheduled batch job (cron)ACA Job or ACIACA Job better integrated; ACI simpler
Granular Kubernetes control (custom controllers, CRDs)AKSACA doesn't expose Kubernetes directly
Web application with staging slotsApp ServiceMore mature deployment slots
Simple and fast container without scalingACILower overhead, simpler

When to configure minReplicas: 0 vs. 1?​

SituationminReplicasReason
API with critical latency1Avoids cold start; always has replica ready
Job/processor with eventual execution0Zero cost when idle
Development environment0Minimal cost, acceptable cold start
Production with latency SLA1 or moreGuarantee of immediate availability

9. Best Practices​

Use Revisions for safe deployments: instead of updating directly to 100% traffic, enable Multiple mode and perform canary deployments: 5% traffic to the new version, monitor for a few minutes, expand to 50%, then 100%. If something goes wrong, redirect 100% back to the previous version immediately.

Centralize secrets and never put them in common environment variables: use secretref for all sensitive information. Even better, use Managed Identity + Key Vault to fetch secrets at runtime, completely eliminating the need to pass secrets to the Container App.

Configure health probes to accelerate autoscaling: define startupProbe, readinessProbe and livenessProbe so that ACA knows exactly when a new replica is ready to receive traffic during scale-out and when a replica has problems during normal operation.

Choose the appropriate environment size: environments have workload profiles. The Consumption profile is pay-per-use and ideal for most cases. Dedicated profiles (D4, D8, D16, etc.) offer reserved capacity for workloads with high and predictable CPU/memory requirements.


10. Common Errors​

Forgetting to assign AcrPull after creating the app with system identity

The container app is created with --registry-identity system, but image pull fails. The cause is that the Managed Identity needs time to be provisioned and the AcrPull assignment needs to be done explicitly after creation. The app stays in an error state with an image not found message.

Using Single mode and losing rollback revisions

In Single mode (default), when a new revision is created, the old one is automatically deactivated. If the new version has a bug, there's no active old revision to redirect traffic to immediately. Switch to Multiple mode in production to keep the previous revision active until confirming the new one works.

Configuring scale-to-zero without understanding cold start impact

A production API with a 200ms latency SLA is configured with minReplicas: 0. When there's no traffic for a few minutes and a new request arrives, the cold start can take 5-15 seconds, violating the SLA. For latency-sensitive APIs, use minReplicas: 1.

Confusing revision with deployment

A new revision is created whenever the container configuration changes (image, variables, CPU/memory). Changes to ingress, scaling, or secrets don't create a new revision. Many administrators expect to create a new revision when changing a scaling rule, which doesn't happen.

Storing state in container filesystem expecting persistence

The container filesystem is ephemeral in ACA (as in any container). Data written to /tmp or any local directory is lost when the replica is restarted or replaced by another. Use Azure Storage, Azure SQL, or other external services for persistent state.


11. Operation and Maintenance​

Check Status and Logs​

# Container App and revisions status
az containerapp show \
--name minha-api \
--resource-group rg-containers \
--query "{Estado:provisioningState, URL:properties.configuration.ingress.fqdn, Replicas:properties.template.scale}" \
--output table

# Real-time logs (streaming)
az containerapp logs show \
--name minha-api \
--resource-group rg-containers \
--follow

# Logs from a specific revision
az containerapp logs show \
--name minha-api \
--resource-group rg-containers \
--revision minha-api--v2-release

# List revisions and traffic distribution
az containerapp revision list \
--name minha-api \
--resource-group rg-containers \
--query "[].{Nome:name, Ativa:properties.active, Trafego:properties.trafficWeight, Replicas:properties.replicas}" \
--output table

Monitoring with Log Analytics​

With the environment integrated to Log Analytics, KQL queries are the main monitoring tool:

// Container logs
ContainerAppConsoleLogs_CL
| where ContainerAppName_s == "minha-api"
| where TimeGenerated > ago(1h)
| project TimeGenerated, Log_s, RevisionName_s
| order by TimeGenerated desc

// Request count by revision
ContainerAppSystemLogs_CL
| where Reason_s == "Scaling"
| where TimeGenerated > ago(24h)
| summarize count() by RevisionName_s, bin(TimeGenerated, 5m)
| render timechart

Important Limits​

ItemLimit
Container Apps per environment20 (default, adjustable)
Revisions per Container App100 (older ones are automatically cleaned)
CPU per replica2 vCPU (Consumption profile)
Memory per replica4 Gi (Consumption profile)
Replicas per revision300
Ingress: maximum request size128 KB (headers)

12. Integration and Automation​

Event-Driven Pattern with Service Bus​

The most powerful use case of ACA is processing queue messages with automatic scaling:

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

GitHub Actions for Continuous Deploy​

# .github/workflows/deploy.yml
name: Deploy to Azure Container Apps

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Build and push to ACR
run: |
az acr build \
--registry minhacr \
--image api:${{ github.sha }} \
.

- name: Deploy to Container Apps
run: |
az containerapp update \
--name minha-api \
--resource-group rg-containers \
--image minhacr.azurecr.io/api:${{ github.sha }} \
--revision-suffix ${{ github.run_number }}

13. Final Summary​

Essential points:

  • ACA is a PaaS for containers with automatic management of underlying Kubernetes, KEDA, and Envoy. You don't manage the cluster.
  • The hierarchy is: Environment (shared cluster) > Container App (application) > Revision (immutable version) > Replica (running instance).
  • Scale-to-zero is native: with minReplicas: 0, replicas are removed when there's no traffic and created automatically when demand arrives.
  • Revisions allow native canary deployments and A/B testing with percentage traffic splitting.

Critical differences:

  • ACA vs. ACI: ACA has automatic scaling, revisions, traffic splitting and lifecycle management; ACI is simple execution without these functionalities.
  • ACA vs. AKS: ACA completely abstracts Kubernetes; AKS gives direct access to the cluster. ACA is simpler; AKS is more flexible and controllable.
  • Revision Mode Single vs. Multiple: Single replaces revisions automatically (simpler); Multiple keeps revisions active in parallel (necessary for canary/blue-green).
  • minReplicas 0 vs. 1: zero has cold start but no cost when idle; 1 eliminates cold start with cost of at least one replica always active.

What needs to be remembered:

  • A new revision is created when the container configuration changes (image, env vars, resources), not when scaling or ingress rules change.
  • For ACR image pulls: use --registry-identity system and assign AcrPull to the Managed Identity created after deployment.
  • Environments are regional and have associated VNet. Apps within the same environment communicate via internal DNS.
  • For event-driven workloads with Service Bus or Storage Queue, ACA with KEDA is the most efficient approach in the Azure ecosystem.
  • The az containerapp logs show --follow command is the equivalent of kubectl logs -f to follow logs in real time.