Quick Navigation
Azure Container Registry (ACR) — Image LifecycleAzure Container Apps — Deployment and Revision ManagementAzure Kubernetes Service (AKS) — Manifest DeploymentsCosmos DB for NoSQL — SDK Operations and Vector SearchPostgreSQL pgvector — Vector Search and IndexingAzure Managed Redis — Caching and Vector SearchAzure Service Bus — Messaging PatternsAzure Event Grid — Event-Driven RoutingAzure Functions — Triggers and BindingsOpenTelemetry — Distributed Tracing and MetricsAzure Key Vault and App ConfigurationKQL — Log Analytics QueriesKey Service Comparisons and Exam Traps
Azure Container Registry (ACR) — Image Lifecycle
- az acr build --image myapp:v1 --registry myregistry --file Dockerfile .
- Build a container image and push it directly to ACR using a quick task (no local Docker required).
- az acr task create --name auto-build --registry myregistry --image myapp:{{.Run.ID}} --context https://github.com/org/repo.git --branch main --git-access-token <token>
- Create an ACR Task that automatically rebuilds the image on every GitHub commit to main.
- az acr task create --name base-update --registry myregistry --image myapp:latest --context . --base-image-trigger-enabled true
- Create an ACR Task with a base image trigger — rebuilds your image whenever the upstream base image is updated.
- docker tag myapp:v1 myregistry.azurecr.io/myapp:v1 && docker push myregistry.azurecr.io/myapp:v1
- Tag and push a local image to ACR using standard Docker CLI after az acr login.
- az acr repository list --name myregistry az acr repository show-tags --name myregistry --repository myapp
- List ACR repositories and view all tags for a specific image repository.
Azure Container Apps — Deployment and Revision Management
- az containerapp create \ --name myapp \ --resource-group myRG \ --environment myEnv \ --image myregistry.azurecr.io/myapp:v1 \ --target-port 8080 \ --ingress external \ --registry-server myregistry.azurecr.io
- Create a Container App with external HTTP ingress from an ACR image.
- az containerapp update --name myapp --resource-group myRG --image myregistry.azurecr.io/myapp:v2
- Update a Container App to a new image version — creates an immutable new revision automatically.
- az containerapp revision list --name myapp --resource-group myRG
- List all revisions of a Container App including active, inactive, and traffic weight assignments.
- az containerapp update --name myapp --resource-group myRG \ --scale-rule-name sb-rule \ --scale-rule-type azure-servicebus \ --scale-rule-metadata queueName=myqueue namespace=mynamespace messageCount=5 \ --scale-rule-auth trigger=connection-string-secret \ --min-replicas 0 --max-replicas 10
- Add a KEDA Service Bus scaling rule that scales to zero when idle and up based on queue depth.
- az containerapp update --name myapp --resource-group myRG \ --scale-rule-name http-rule \ --scale-rule-type http \ --scale-rule-metadata concurrentRequests=50 \ --min-replicas 0 --max-replicas 20
- Configure HTTP-based KEDA scaling — default minReplicas is 0 (scales to zero). Set --min-replicas 1 explicitly to keep at least one instance running and avoid cold starts.
- az containerapp env create --name myEnv --resource-group myRG --location eastus
- Create a Container Apps environment — the shared networking and logging boundary for all apps.
- # Enable multi-revision mode for blue/green or A/B deployments az containerapp revision set-mode --name myapp --resource-group myRG --mode multiple # Split traffic: 80% to stable revision, 20% to canary revision az containerapp ingress traffic set --name myapp --resource-group myRG \ --revision-weight myapp--stable=80 myapp--canary=20
- Enable multiple-revision mode and split traffic between revisions for blue/green or canary deployments.
Azure Kubernetes Service (AKS) — Manifest Deployments
- kubectl apply -f deployment.yaml kubectl apply -f service.yaml kubectl apply -f .
- Apply one or more Kubernetes manifest files to AKS — creates or updates resources to match desired state.
- # deployment.yaml skeleton apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: myregistry.azurecr.io/myapp:v1 ports: - containerPort: 8080 envFrom: - configMapRef: name: myapp-config
- AKS Deployment manifest skeleton with ACR image and ConfigMap environment variable injection.
- kubectl create configmap myapp-config --from-literal=APP_ENV=production --from-literal=LOG_LEVEL=info
- Create a ConfigMap for non-sensitive configuration — use ConfigMaps for feature flags and URLs, not secrets.
- kubectl create secret generic myapp-secret --from-literal=DB_PASSWORD=mysecretpass
- Create a Kubernetes Secret for sensitive values — base64-encoded at rest in etcd.
- # PersistentVolumeClaim for Azure Files (ReadWriteMany — shared across pods) apiVersion: v1 kind: PersistentVolumeClaim metadata: name: shared-pvc spec: accessModes: - ReadWriteMany storageClassName: azurefile resources: requests: storage: 10Gi
- PVC using Azure Files with ReadWriteMany — allows multiple pods to mount the same volume simultaneously.
- kubectl get pods kubectl logs <pod-name> kubectl describe pod <pod-name>
- Core AKS troubleshooting commands — check pod status, view logs, and inspect events/conditions.
- az aks get-credentials --resource-group myRG --name myAKSCluster
- Download AKS cluster credentials to local kubeconfig — required before running kubectl commands.
- # PersistentVolumeClaim for Azure Disk (ReadWriteOnce — single pod only) apiVersion: v1 kind: PersistentVolumeClaim metadata: name: disk-pvc spec: accessModes: - ReadWriteOnce storageClassName: managed-premium resources: requests: storage: 20Gi # Azure Disk = ReadWriteOnce (one pod); Azure Files = ReadWriteMany (many pods)
- PVC using Azure Disk with ReadWriteOnce — only one pod can mount at a time; use for high IOPS single-node workloads.
Cosmos DB for NoSQL — SDK Operations and Vector Search
- from azure.cosmos import CosmosClient client = CosmosClient(url=COSMOS_ENDPOINT, credential=COSMOS_KEY) db = client.get_database_client('mydb') container = db.get_container_client('mycontainer') # Upsert item container.upsert_item({'id': '1', 'partitionKey': 'pk1', 'value': 'data'}) # Query with partition key for item in container.query_items( query='SELECT * FROM c WHERE c.partitionKey = @pk', parameters=[{'name': '@pk', 'value': 'pk1'}]): print(item)
- Connect to Cosmos DB and perform upsert and parameterized query operations using the Python SDK.
- vector_embedding_policy = { 'vectorEmbeddings': [{ 'path': '/contentVector', 'dataType': 'float32', 'distanceFunction': 'cosine', 'dimensions': 1536 }] } indexing_policy = { 'includedPaths': [{'path': '/*'}], 'excludedPaths': [{'path': '/contentVector/*'}], 'vectorIndexes': [{'path': '/contentVector', 'type': 'quantizedFlat'}] } container = db.create_container_if_not_exists( id='mycontainer', partition_key=PartitionKey(path='/id'), indexing_policy=indexing_policy, vector_embedding_policy=vector_embedding_policy)
- Create a Cosmos DB container with vector embedding policy and vector index — must be set at creation time, cannot be modified later.
- SELECT TOP 10 c.title, VectorDistance(c.contentVector, @queryVector) AS score FROM c ORDER BY VectorDistance(c.contentVector, @queryVector)
- Cosmos DB vector similarity search using VectorDistance() — lower score means closer match (cosine distance).
- # Change Feed Processor pattern from azure.cosmos.aio import CosmosClient async def process_changes(changes): for item in changes: print(f'Changed item: {item["id"]}') processor = container.get_change_feed_processor( lease_container=lease_container, on_change=process_changes ) await processor.start()
- Change Feed Processor — asynchronously reads ordered changes from a Cosmos DB container for event-driven pipelines.
- # Consistency levels (from strongest to weakest): # Strong → Bounded Staleness → Session (default) → Consistent Prefix → Eventual # Session = most commonly used, per-client consistency, single RU cost
- Cosmos DB consistency levels ordered strongest to weakest — Session is the default and cheapest for most workloads.
PostgreSQL pgvector — Vector Search and Indexing
- CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE documents ( id BIGSERIAL PRIMARY KEY, content TEXT, embedding vector(1536) );
- Enable pgvector extension and create a table with a 1536-dimension vector column for storing embeddings.
- -- HNSW index (fast queries, high recall, more memory, slow to build) CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); -- IVFFlat index (fast to build, lower memory, needs reindexing after data changes) CREATE INDEX ON documents USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
- Create HNSW (production, best query speed) or IVFFlat (large datasets, fast build) vector indexes on pgvector columns.
- -- L2 (Euclidean) distance SELECT content FROM documents ORDER BY embedding <-> $1 LIMIT 5; -- Cosine distance SELECT content FROM documents ORDER BY embedding <=> $1 LIMIT 5; -- Inner product (negative value — negate for similarity) SELECT content FROM documents ORDER BY embedding <#> $1 LIMIT 5;
- Vector distance operators: <-> for L2/Euclidean, <=> for cosine, <#> for inner product (returns negative).
- -- RAG pattern: vector search with metadata filter SELECT content, category, embedding <=> $1 AS distance FROM documents WHERE category = 'finance' AND created_at > NOW() - INTERVAL '30 days' ORDER BY embedding <=> $1 LIMIT 10;
- Hybrid RAG query combining vector similarity search with SQL metadata filters for targeted retrieval.
- -- Connection pooling with psycopg2 (use pgBouncer in production) from psycopg2 import pool conn_pool = pool.ThreadedConnectionPool( minconn=2, maxconn=20, host=PG_HOST, database=PG_DB, user=PG_USER, password=PG_PASS )
- Connection pooling is essential for pgvector workloads — without it, concurrent requests exhaust the connection limit.
Azure Managed Redis — Caching and Vector Search
- import redis r = redis.Redis(host=REDIS_HOST, port=6380, ssl=True, password=REDIS_KEY) # Cache-aside pattern def get_cached(key: str): cached = r.get(key) if cached: return json.loads(cached) value = fetch_from_source(key) r.setex(key, 900, json.dumps(value)) # TTL = 15 minutes return value
- Cache-aside pattern with 15-minute TTL using setex — fetch from source only on cache miss.
- # Invalidate cache on model update r.delete(f'inference:{model_id}:{input_hash}') # Delete pattern — invalidate all keys for a model for key in r.scan_iter(f'inference:{model_id}:*'): r.delete(key)
- Manual cache invalidation on model update — delete specific keys or use scan_iter for pattern-based bulk invalidation.
- # Azure Managed Redis vector search requires Enterprise tier # Standard and Basic tiers do NOT support vector indexing # Configure vector index via RediSearch module in Enterprise tier
- Vector similarity search in Azure Managed Redis requires the Enterprise tier — Standard/Basic tiers do not support vector indexes.
- r.expire('session:user123', 3600) # Set TTL to 1 hour r.persist('cache:global:config') # Remove TTL (make permanent) r.ttl('session:user123') # Check remaining TTL in seconds
- Key expiration management — set, remove, or inspect TTL on any Redis key.
Azure Service Bus — Messaging Patterns
- from azure.servicebus import ServiceBusClient, ServiceBusMessage with ServiceBusClient.from_connection_string(conn_str) as client: with client.get_queue_sender('myqueue') as sender: sender.send_messages(ServiceBusMessage('Hello AI pipeline'))
- Send a message to a Service Bus queue using the Python SDK.
- with ServiceBusClient.from_connection_string(conn_str) as client: with client.get_queue_receiver('myqueue', max_wait_time=5) as receiver: for msg in receiver: try: process(msg) receiver.complete_message(msg) # remove from queue except: receiver.abandon_message(msg) # unlock, retry
- PeekLock mode — complete message on success, abandon on failure so it can be retried (up to max delivery count).
- # Dead-letter queue receiver — process messages exceeding MaxDeliveryCount with client.get_queue_receiver('myqueue', sub_queue='deadletter') as dlq: for msg in dlq: inspect_and_handle(msg) dlq.complete_message(msg)
- Process dead-letter queue (DLQ) messages — DLQ messages do NOT re-enter the main queue automatically.
- # Topics and subscriptions — fan-out messaging # Publisher sends to topic; each subscription gets its own copy # SQL filter example on subscription az servicebus topic subscription rule create \ --resource-group myRG --namespace-name myns \ --topic-name mytopic --subscription-name sub-prod \ --name prod-filter --filter-sql-expression "environment='production'"
- Create a SQL filter on a Service Bus topic subscription to route messages matching a property value.
- # Send to topic (fan-out — each subscription receives its own copy) with ServiceBusClient.from_connection_string(conn_str) as client: with client.get_topic_sender('mytopic') as sender: msg = ServiceBusMessage('AI job complete', application_properties={'environment': 'production'}) sender.send_messages(msg) # Receive from subscription with client.get_subscription_receiver('mytopic', 'sub-prod') as receiver: for msg in receiver: process(msg) receiver.complete_message(msg)
- Publish to a Service Bus topic and receive from a subscription — each subscription gets an independent copy of every message.
Azure Event Grid — Event-Driven Routing
- az eventgrid event-subscription create \ --name my-subscription \ --source-resource-id /subscriptions/{sub}/resourceGroups/myRG/providers/Microsoft.EventGrid/topics/mytopic \ --endpoint https://myfunc.azurewebsites.net/api/handler \ --endpoint-type webhook \ --subject-begins-with /models/production
- Create an Event Grid subscription with a subject prefix filter — only events whose subject starts with /models/production are delivered.
- # Publish a custom event to Event Grid topic import requests, json events = [{ 'id': '1', 'eventType': 'ModelTrainingCompleted', 'subject': '/models/production/v2', 'dataVersion': '1.0', 'data': {'accuracy': 0.95, 'environment': 'production'} }] requests.post(TOPIC_ENDPOINT, headers={'aeg-sas-key': TOPIC_KEY, 'Content-Type': 'application/json'}, data=json.dumps(events))
- Publish a custom event to an Event Grid topic — subscribers receive push-based notification immediately.
- # Event Grid dead-lettering goes to Azure Blob Storage (NOT Service Bus DLQ) az eventgrid event-subscription update \ --name my-subscription \ --source-resource-id <topic-resource-id> \ --deadletter-endpoint /subscriptions/{sub}/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/mysa/blobServices/default/containers/deadletter
- Configure Event Grid dead-lettering to a Blob Storage container — failed events after all retries go here, not to a Service Bus DLQ.
- # Service Bus = MESSAGES (commands, pulled by receiver, ordering guaranteed) # Event Grid = EVENTS (state change notifications, pushed to subscriber, no ordering guarantee)
- Key distinction: Service Bus is for commands/tasks requiring reliable processing; Event Grid is for reactive event notifications.
Azure Functions — Triggers and Bindings
- import azure.functions as func app = func.FunctionApp() @app.function_name('HttpProcessor') @app.route(route='process', methods=['POST']) @app.cosmos_db_output( arg_name='outputDoc', database_name='mydb', container_name='results', connection='CosmosDBConnection') def http_to_cosmos(req: func.HttpRequest, outputDoc: func.Out[func.Document]): data = req.get_json() outputDoc.set(func.Document.from_dict({'id': data['id'], 'result': data})) return func.HttpResponse('OK')
- HTTP trigger with Cosmos DB output binding — writes to Cosmos DB without any SDK code in the function body.
- @app.function_name('ServiceBusProcessor') @app.service_bus_queue_trigger( arg_name='msg', queue_name='myqueue', connection='ServiceBusConnection') def process_queue(msg: func.ServiceBusMessage): body = msg.get_body().decode('utf-8') process(body) # If function raises, message is ABANDONED (not deleted) and retried
- Service Bus queue trigger — uses PeekLock by default; exception causes message abandonment and retry up to MaxDeliveryCount.
- @app.function_name('TimerJob') @app.timer_trigger(arg_name='timer', schedule='0 0 * * * *') # every hour def hourly_task(timer: func.TimerRequest): run_scheduled_job()
- Timer trigger with cron expression — runs the function on a schedule (every hour in this example).
- # Trigger types and their use cases: # HttpTrigger → REST APIs, webhooks # ServiceBusTrigger → queue/topic message processing # EventGridTrigger → event-driven reactions # TimerTrigger → scheduled/batch jobs # CosmosDBTrigger → change feed processing
- Azure Functions trigger types and their primary use cases — trigger type determines what starts the function.
- @app.function_name('EventGridHandler') @app.event_grid_trigger(arg_name='event') def handle_event(event: func.EventGridEvent): data = event.get_json() subject = event.subject # e.g. '/models/production/v2' event_type = event.event_type # e.g. 'ModelTrainingCompleted' process_event(subject, event_type, data)
- Event Grid trigger receives pushed events directly — subject and event_type properties enable routing logic within the function.
OpenTelemetry — Distributed Tracing and Metrics
- from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter provider = TracerProvider() provider.add_span_processor( BatchSpanProcessor(AzureMonitorTraceExporter( connection_string=APPLICATIONINSIGHTS_CONNECTION_STRING )) ) trace.set_tracer_provider(provider) tracer = trace.get_tracer(__name__)
- Configure OpenTelemetry with Azure Monitor exporter — sends traces to Application Insights via OpenTelemetry (not TelemetryClient).
- tracer = trace.get_tracer('myapp') with tracer.start_as_current_span('inference-request') as span: span.set_attribute('model.name', 'gpt-4o') span.set_attribute('input.tokens', 512) result = run_inference(input_data) span.set_attribute('output.tokens', result.token_count)
- Create a custom span with attributes using trace.get_tracer() — Python OTel API; ActivitySource is the equivalent .NET API. Both replace TelemetryClient.
- from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider meter_provider = MeterProvider() metrics.set_meter_provider(meter_provider) meter = metrics.get_meter('myapp') # Counter (monotonically increasing) request_counter = meter.create_counter('inference.requests', unit='requests') request_counter.add(1, {'model': 'gpt-4o', 'status': 'success'}) # Histogram (distribution of values) latency = meter.create_histogram('inference.latency', unit='ms') latency.record(250, {'model': 'gpt-4o'})
- Create custom metrics with Meter instruments — Counter for counts, Histogram for latency distributions.
- # OpenTelemetry terminology: # Span = unit of work in a distributed trace (replaces AI SDK 'dependency'/'request') # Trace = collection of spans forming an end-to-end request # ActivitySource → tracer in .NET; trace.get_tracer() in Python # Meter → metrics instrument in both .NET and Python
- OpenTelemetry terminology — Span replaces Application Insights 'request/dependency'; Meter replaces TelemetryClient metrics.
Azure Key Vault and App Configuration
- from azure.identity import DefaultAzureCredential from azure.keyvault.secrets import SecretClient credential = DefaultAzureCredential() client = SecretClient(vault_url='https://myvault.vault.azure.net', credential=credential) # Retrieve secret (no credentials in code) secret = client.get_secret('db-connection-string') print(secret.value)
- Retrieve a Key Vault secret using managed identity via DefaultAzureCredential — no connection strings or passwords in code.
- # Assign Key Vault Secrets User role to a managed identity az role assignment create \ --role 'Key Vault Secrets User' \ --assignee <managed-identity-principal-id> \ --scope /subscriptions/{sub}/resourceGroups/myRG/providers/Microsoft.KeyVault/vaults/myvault
- Grant a managed identity RBAC access to Key Vault — identity must have Key Vault Secrets User role to read secrets.
- from azure.appconfiguration import AzureAppConfigurationClient client = AzureAppConfigurationClient.from_connection_string(APP_CONFIG_CONN_STR) # Get a setting setting = client.get_configuration_setting(key='FeatureFlags:DarkMode', label='production') print(setting.value) # Key Vault reference: App Configuration stores a reference, not the secret value itself
- Retrieve App Configuration settings by key and label — use App Configuration for feature flags and settings, Key Vault for secrets.
- # System-assigned identity: 1:1 with resource, deleted with resource az containerapp identity assign --name myapp --resource-group myRG --system-assigned # User-assigned identity: standalone, reusable across multiple resources az containerapp identity assign --name myapp --resource-group myRG --user-assigned <identity-resource-id>
- Assign system-assigned (tied to one resource) or user-assigned (shareable across resources) managed identity to a Container App.
- # Key Vault soft-delete is on by default and CANNOT be disabled on new vaults # Deleted secrets are recoverable for 7-90 days (default 90) # Purge protection prevents permanent deletion during retention period # Set rotation policy on a secret (auto-rotate every 30 days) az keyvault secret set-rotation-policy \ --vault-name myvault --name db-password \ --value @rotation-policy.json # rotation-policy.json: {"lifetimeActions": [{"trigger": {"timeAfterCreate": "P30D"}, "action": {"type": "Rotate"}}]}
- Key Vault soft-delete is on by default (cannot disable); set rotation policies to auto-rotate secrets on a schedule.
KQL — Log Analytics Queries
- // Find 500 errors on /api/inference in the last 24 hours grouped by hour requests | where timestamp > ago(24h) | where url contains '/api/inference' | where resultCode == '500' | summarize count() by bin(timestamp, 1h) | order by timestamp asc
- KQL query filtering requests by time, URL path, and status code then aggregating by hour using bin().
- // AKS Container Insights — pod logs for a specific namespace ContainerLog | where TimeGenerated > ago(1h) | where Namespace == 'production' | where ContainerName contains 'myapp' | project TimeGenerated, ContainerName, LogEntry | order by TimeGenerated desc
- Query AKS Container Insights logs by namespace and container name — requires Container Insights enabled on the cluster.
- // Case-sensitive vs case-insensitive string comparison // Case-sensitive (default): | where Name == 'Production' // Case-insensitive (use =~): | where Name =~ 'production'
- KQL string comparisons are case-sensitive by default — use =~ for case-insensitive matching.
- // Core KQL operators: // where → filter rows // project → select/rename columns // summarize count(), avg(), sum() by column // extend → add computed column // order by → sort results // join → join two tables // ago(1h) → relative time filter // bin(timestamp, 5m) → time bucketing
- Core KQL operators for log analysis — summarize with bin() is the standard pattern for time-series aggregation.
- // OpenTelemetry traces in Application Insights dependencies | where timestamp > ago(1h) | where name == 'inference-request' | summarize avg(duration), percentile(duration, 95) by bin(timestamp, 5m) | project timestamp, avg_duration=avg_duration, p95=percentile_duration_95
- Query OpenTelemetry span durations from Application Insights — spans appear in the 'dependencies' table.
Key Service Comparisons and Exam Traps
- # Container Apps vs AKS # Container Apps: serverless, KEDA built-in, no Kubernetes manifest knowledge needed # AKS: full Kubernetes, manifest files, ConfigMaps, Secrets, node management, GPU support # Choose AKS when: GPU AI workloads, full K8s control, complex orchestration
- Container Apps abstracts Kubernetes — use AKS when workloads need GPU, full cluster control, or complex networking.
- # Azure Disk vs Azure Files for AKS persistent storage # Azure Disk: ReadWriteOnce (RWO) — single pod only, high IOPS, block storage # Azure Files: ReadWriteMany (RWX) — multiple pods simultaneously, shared file storage # Wrong choice = mount failure in AKS
- Azure Disk is single-pod (RWO); Azure Files is multi-pod (RWX) — choosing wrong causes PVC mount failures.
- # Cosmos DB vector index is IMMUTABLE after container creation # Must set vector_embedding_policy and vectorIndexes at container creation # Cannot modify existing containers to add vector search # Vector paths must be in excludedPaths to avoid high RU charges on inserts
- Cosmos DB vector policy cannot be modified after creation — and vector paths must be excluded from standard indexing.
- # HNSW vs IVFFlat (pgvector) # HNSW: better query speed, higher recall, more memory, slower to build — use for production RAG # IVFFlat: fast to build, less memory, REQUIRES periodic reindexing after data changes # Key trap: IVFFlat needs reindexing; HNSW does NOT
- HNSW has faster queries but slower builds; IVFFlat requires reindexing after data changes — HNSW does not.
- # Key Vault vs App Configuration # Key Vault: secrets, keys, certificates — HSM-backed, audited, rotation policies # App Configuration: feature flags, environment settings, non-sensitive key-value pairs # App Configuration CAN reference Key Vault secrets (combined use) # Do NOT store non-sensitive settings in Key Vault
- Key Vault is for secrets only; App Configuration is for settings and feature flags — App Configuration can reference Key Vault.
- # Container Apps revisions are IMMUTABLE # Any change to: image, env vars, scaling rules, resource limits → creates new revision # You cannot edit an existing revision — only activate/deactivate them # ALL KEDA triggers (HTTP, Service Bus, custom) default to minReplicas=0 (scale to zero) # Set minReplicas=1 explicitly if you need at least one replica running at all times
- Container Apps revisions are immutable — every configuration or image change creates a new revision.