Comprehensive AZ-204 quick reference: App Service, Functions, containers, Cosmos DB, Blob Storage, Entra ID auth, Key Vault/App Configuration, API Management policies, Event Grid/Hubs, Service Bus/Queues, and Application Insights. Includes code, tables, charts, and diagrams.
Use this for last‑mile review. Pair it with the Syllabus for coverage, the Study Plan for structure, and Practice for speed.
AZ-204 is less about memorizing product lists and more about choosing the right service + configuration for a scenario.
| Domain | Weight | Visual (relative) |
|---|---|---|
| Develop Azure compute solutions | 25–30% | ██████████ |
| Develop for Azure storage | 15–20% | ██████ |
| Implement Azure security | 15–20% | ██████ |
| Monitor and troubleshoot Azure solutions | 5–10% | ███ |
| Connect/consume Azure + third-party services | 20–25% | ████████ |
Always look for the hidden requirement: private vs public, ordering, idempotency, throughput, RTO/RPO, least privilege, cost controls.
| Need | Best fit | Why |
|---|---|---|
| Managed web app/API with easy deployment | App Service | Built-in scaling, TLS/custom domains, slots |
| Event-driven code, bursty traffic | Functions | Triggers/bindings, scale-to-zero (Consumption) |
| Containerized microservices with revisions + KEDA | Container Apps | Managed env, ingress, traffic splitting |
| “Run a container now” without orchestration | Container Instances | Simple container group execution |
flowchart TD
Q{"Do you need triggers\nand bindings?"} -->|Yes| F["Azure Functions"]
Q -->|No| C{"Do you need\na container image?"}
C -->|No| AS["App Service"]
C -->|Yes| O{"Need revisions\n+ autoscaling rules?"}
O -->|Yes| CA["Container Apps"]
O -->|No| ACI["Container Instances"]
1# Create ACR
2az acr create -g RG -n myregistry --sku Basic
3
4# Login and push
5az acr login -n myregistry
6docker build -t myregistry.azurecr.io/myapi:1.0.0 .
7docker push myregistry.azurecr.io/myapi:1.0.0
| Option | Best when | Notes |
|---|---|---|
| Entra ID (AAD) | humans/devs | Role assignments; best default |
| Managed identity | Azure-hosted workloads | Great for pull access (no secrets) |
| Scoped tokens | limited automation | Narrow scope + expiry |
| Admin user | last resort | Shared credential; avoid for prod |
1FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
2WORKDIR /src
3COPY . .
4RUN dotnet publish -c Release -o /out
5
6FROM mcr.microsoft.com/dotnet/aspnet:8.0
7WORKDIR /app
8COPY --from=build /out .
9ENV ASPNETCORE_URLS=http://+:8080
10EXPOSE 8080
11ENTRYPOINT ["dotnet","MyApi.dll"]
1# Example: build on source context (conceptual)
2az acr build -r myregistry -t myapi:<build-id> .
| Feature | Container Apps | ACI |
|---|---|---|
| Revision-based deploys | ✅ | ✖ |
| Traffic splitting | ✅ | ✖ |
| Event-based scaling (KEDA) | ✅ | ✖ |
| “Just run a container” simplicity | ⚠ (more setup) | ✅ |
Container Apps: key knobs to remember
1# Minimal Container Apps flow (conceptual)
2az containerapp env create -g RG -n ca-env --location eastus
3az containerapp create -g RG -n myapi --environment ca-env \\
4 --image myregistry.azurecr.io/myapi:1.0.0 --ingress external --target-port 8080
flowchart LR
DEV["Dev pushes image"] --> ACR["ACR"]
ACR --> CA["Container Apps"]
CA --> REV{"Revisions"}
REV -->|blue| R1["rev-1"]
REV -->|green| R2["rev-2"]
R1 --> TR["Traffic split"]
R2 --> TR
Common gotcha: container apps and private registries require correct auth (managed identity or registry credentials) and correct image reference.
| Method | When it’s best | Notes |
|---|---|---|
| Zip deploy / run-from-package | Fast + simple | Great for CI/CD artifacts |
| GitHub Actions / Azure DevOps | Repeatable | Build → test → deploy pipelines |
| Container from ACR | Containerized web apps | Control image tag/version |
1# Plan + web app
2az appservice plan create -g RG -n plan-az204 --sku P1v3 --is-linux
3az webapp create -g RG -p plan-az204 -n mywebapp --runtime \"DOTNETCORE:8.0\"
4
5# App settings (configuration) + connection strings
6az webapp config appsettings set -g RG -n mywebapp --settings ASPNETCORE_ENVIRONMENT=prod
7az webapp config connection-string set -g RG -n mywebapp -t SQLAzure --settings Db=\"<conn-string>\"
8
9# Zip deploy (one option)
10az webapp deploy -g RG -n mywebapp --src-path ./artifact.zip --type zip
11
12# Slots (safe releases)
13az webapp deployment slot create -g RG -n mywebapp --slot staging
14az webapp deployment slot swap -g RG -n mywebapp --slot staging --target-slot production
1name: build-and-deploy
2on: [push]
3jobs:
4 deploy:
5 runs-on: ubuntu-latest
6 steps:
7 - uses: actions/checkout@v4
8 - uses: azure/login@v2
9 with:
10 creds: ${{ secrets.AZURE_CREDENTIALS }}
11 - uses: azure/webapps-deploy@v3
12 with:
13 app-name: mywebapp
14 package: ./artifact.zip
| Item | Best home | Why |
|---|---|---|
| Feature flags / non-secret settings | App Configuration | Centralized, environment-aware |
| Secrets (API keys, passwords) | Key Vault | Rotation + auditing |
| App runtime env vars | App settings | Standard platform config |
| Database connection strings | Connection strings | Separate slot settings + tooling support |
flowchart LR
U["Users"] --> P["Prod slot"]
S["Staging slot"] --> SW["Swap"]
SW --> P
Slot rules of thumb
| Pattern | Typical trigger | Typical output |
|---|---|---|
| HTTP API | HTTP trigger | Storage/Cosmos/Service Bus |
| Background processing | Queue/Service Bus trigger | Blob/Cosmos/Service Bus |
| Event routing | Event Grid trigger | Service Bus / storage |
| Scheduled job | Timer trigger | Blob/Cosmos |
| Plan | Best when | Notes |
|---|---|---|
| Consumption | bursty workloads | scale-to-zero; watch cold start |
| Premium | low-latency + scale | reduced cold start; more features |
| Dedicated (App Service plan) | steady load | predictable capacity; you manage scaling |
1{
2 "version": "2.0",
3 "retry": {
4 "strategy": "fixedDelay",
5 "maxRetryCount": 5,
6 "delayInterval": "00:00:10"
7 }
8}
Key exam idea: many triggers are at-least-once. Design processing to be idempotent.
1using Azure.Messaging.ServiceBus;
2using Microsoft.Azure.Functions.Worker;
3using Microsoft.Extensions.Logging;
4
5public class ProcessOrders
6{
7 private readonly ILogger _logger;
8 public ProcessOrders(ILoggerFactory loggerFactory) => _logger = loggerFactory.CreateLogger<ProcessOrders>();
9
10 [Function("ProcessOrders")]
11 public async Task Run(
12 [ServiceBusTrigger("orders", Connection = "ServiceBusConnection")]
13 ServiceBusReceivedMessage message,
14 ServiceBusMessageActions messageActions)
15 {
16 try
17 {
18 var body = message.Body.ToString();
19 _logger.LogInformation("Order received: {Body}", body);
20
21 // Do work...
22
23 await messageActions.CompleteMessageAsync(message);
24 }
25 catch (Exception ex)
26 {
27 _logger.LogError(ex, "Failed processing message");
28 await messageActions.AbandonMessageAsync(message);
29 }
30 }
31}
Function gotchas
flowchart LR
START["Starter (HTTP/Timer)"] --> ORCH["Orchestrator"]
ORCH --> A1["Activity: step 1"]
ORCH --> A2["Activity: step 2"]
A1 --> ORCH
A2 --> ORCH
ORCH --> DONE["Complete"]
| Level | Strength | Typical use |
|---|---|---|
| Strong | Highest | Single region, strict correctness |
| Bounded staleness | High | Controlled lag |
| Session | Medium | Most apps (user session correctness) |
| Consistent prefix | Lower | Ordered but potentially stale |
| Eventual | Lowest | Highest availability/lowest latency |
1var read = await container.ReadItemAsync<MyItem>(id, new PartitionKey(pk));
2var etag = read.ETag;
3
4item.Version = item.Version + 1;
5await container.ReplaceItemAsync(item, id, new PartitionKey(pk), new ItemRequestOptions
6{
7 IfMatchEtag = etag
8});
1var client = new CosmosClient(endpoint, credential);
2var container = client.GetContainer("db", "items");
3
4var query = new QueryDefinition("SELECT * FROM c WHERE c.type = @t")
5 .WithParameter("@t", "invoice");
6
7using var iterator = container.GetItemQueryIterator<dynamic>(query);
8while (iterator.HasMoreResults)
9{
10 foreach (var item in await iterator.ReadNextAsync())
11 {
12 Console.WriteLine(item);
13 }
14}
flowchart LR
CDB["Cosmos DB container"] --> CF["Change Feed"]
CF --> FN["Function / Worker"]
FN --> OUT["Downstream system"]
Change feed gotcha: you need leases/checkpointing so processors can scale safely.
1var processor = container
2 .GetChangeFeedProcessorBuilder<MyItem>(
3 processorName: "proc",
4 onChangesDelegate: async (changes, cancellationToken) =>
5 {
6 foreach (var doc in changes)
7 {
8 // react to changes
9 }
10 })
11 .WithInstanceName("worker-1")
12 .WithLeaseContainer(leaseContainer)
13 .Build();
14
15await processor.StartAsync();
1using Azure.Storage.Blobs;
2using Azure.Storage.Blobs.Models;
3
4var container = new BlobContainerClient(connectionString, "docs");
5await container.CreateIfNotExistsAsync();
6
7var blob = container.GetBlobClient("reports/2025-q4.pdf");
8await blob.UploadAsync(fileStream, new BlobHttpHeaders { ContentType = "application/pdf" });
9
10await blob.SetMetadataAsync(new Dictionary<string, string>
11{
12 ["owner"] = "finance",
13 ["classification"] = "internal"
14});
| Feature | Why it matters | Typical scenario |
|---|---|---|
| Soft delete | recover from deletes | “accidental deletion” |
| Versioning | recover previous versions | “rollback changes” / ransomware |
| Immutability | WORM retention | compliance retention |
| Access tiers | cost optimization | logs/archives |
1{
2 "rules": [
3 {
4 "name": "tier-old-logs",
5 "enabled": true,
6 "type": "Lifecycle",
7 "definition": {
8 "filters": { "blobTypes": ["blockBlob"], "prefixMatch": ["logs/"] },
9 "actions": {
10 "baseBlob": {
11 "tierToCool": { "daysAfterModificationGreaterThan": 30 },
12 "tierToArchive": { "daysAfterModificationGreaterThan": 180 },
13 "delete": { "daysAfterModificationGreaterThan": 365 }
14 }
15 }
16 }
17 }
18 ]
19}
| SAS type | Best when | Notes |
|---|---|---|
| Service SAS | Scoped to a resource | Most common “least privilege” SAS |
| Account SAS | Broad | Easy to over-permission |
| User delegation SAS | Best security | Signed by Entra ID; no account key exposure |
Common gotcha: SAS failures are often clock skew, expired token, or missing permissions on the exact resource path.
1using Azure.Storage.Sas;
2
3var builder = new BlobSasBuilder
4{
5 BlobContainerName = "docs",
6 BlobName = "reports/2025-q4.pdf",
7 Resource = "b",
8 ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(15)
9};
10builder.SetPermissions(BlobSasPermissions.Read);
11
12var sasUri = blob.GenerateSasUri(builder);
13Console.WriteLine(sasUri);
| Scenario | Flow | Why |
|---|---|---|
| Web app signing in a user | Authorization code | Standard OIDC pattern |
| Service-to-service (daemon) | Client credentials | No user context |
| CLI/device sign-in | Device code | Works without browser redirect |
1using Microsoft.Identity.Client;
2
3var app = ConfidentialClientApplicationBuilder
4 .Create(clientId)
5 .WithClientSecret(clientSecret)
6 .WithAuthority($"https://login.microsoftonline.com/{tenantId}")
7 .Build();
8
9var result = await app.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
10 .ExecuteAsync();
11
12Console.WriteLine(result.AccessToken);
| Permission type | Used when | Example |
|---|---|---|
| Delegated | user is signed in | User.Read to call /me |
| Application | daemon/no user | .default for app-only permissions |
1using System.Net.Http.Headers;
2
3using var http = new HttpClient();
4http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
5
6var json = await http.GetStringAsync("https://graph.microsoft.com/v1.0/me");
7Console.WriteLine(json);
1using Azure.Identity;
2using Azure.Security.KeyVault.Secrets;
3
4var vaultUri = new Uri("https://myvault.vault.azure.net/");
5var client = new SecretClient(vaultUri, new DefaultAzureCredential());
6KeyVaultSecret secret = await client.GetSecretAsync("DbPassword");
sequenceDiagram
participant App as "App Service / Function"
participant MSI as "Managed Identity"
participant AAD as "Entra ID"
participant KV as "Key Vault"
App->>MSI: Request token for Key Vault
MSI->>AAD: Get access token
AAD-->>MSI: Access token
MSI-->>App: Access token
App->>KV: GetSecret (Bearer token)
KV-->>App: Secret value
| Store | Best for | Why |
|---|---|---|
| Key Vault | secrets/keys/certs | rotation + auditing + access controls |
| App Configuration | app settings + feature flags | centralized config + refresh |
Rate limiting
1<rate-limit-by-key calls="10" renewal-period="60" counter-key="@(context.Subscription.Key)" />
Validate JWT
1<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
2 <openid-config url="https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration" />
3 <required-claims>
4 <claim name="aud">
5 <value>{api-app-id-uri}</value>
6 </claim>
7 </required-claims>
8</validate-jwt>
CORS
1<cors allow-credentials="false">
2 <allowed-origins>
3 <origin>https://example.com</origin>
4 </allowed-origins>
5 <allowed-methods>
6 <method>GET</method>
7 <method>POST</method>
8 </allowed-methods>
9</cors>
Rewrite URL (common “legacy backend” fix)
1<rewrite-uri template="/v2/{uri}" />
Set backend (multi-backend routing)
1<set-backend-service base-url="https://mybackend.azurewebsites.net" />
Response caching
1<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" />
2<cache-store duration="60" />
Policy scopes: global → product → API → operation (more specific overrides earlier scopes).
flowchart LR
C["Client"] --> APIM["API Management"]
APIM --> POL["Policies (auth, throttling, transform)"]
POL --> BE["Backend API"]
Policy order matters: inbound runs before backend; outbound after backend response.
| Service | Best for | Key traits |
|---|---|---|
| Event Grid | event routing | push, fan-out, filtering |
| Event Hubs | streaming ingestion | partitions, high throughput |
| Service Bus | enterprise messaging | DLQ, sessions, transactions |
| Storage Queues | simple queues | basic, cheap, fewer features |
| Feature | Event Grid | Event Hubs | Service Bus | Storage Queues |
|---|---|---|---|---|
| Primary job | route events | ingest streams | reliable messaging | simple queue |
| Push vs pull | push | pull | pull | pull |
| Ordering | ✖ | within partition | sessions/FIFO patterns | best-effort |
| Dead-letter queue | dead-lettering | consumer design | built-in DLQ | custom pattern |
| Transactions | ✖ | ✖ | ✅ | ✖ |
| Typical workload | app/resource events | telemetry/logs | commands/work items | lightweight jobs |
flowchart LR
SRC["Source system"] --> EG["Event Grid"]
EG --> FN["Function handler"]
FN --> SB["Service Bus queue"]
SB --> WK["Worker"]
1using Azure.Messaging.EventHubs;
2using Azure.Messaging.EventHubs.Producer;
3
4await using var producer = new EventHubProducerClient(connectionString, "telemetry");
5using EventDataBatch batch = await producer.CreateBatchAsync();
6batch.TryAdd(new EventData(System.Text.Encoding.UTF8.GetBytes("{\"ok\":true}")));
7await producer.SendAsync(batch);
1using Azure.Messaging.ServiceBus;
2
3await using var client = new ServiceBusClient(connectionString);
4ServiceBusSender sender = client.CreateSender("orders");
5await sender.SendMessageAsync(new ServiceBusMessage("hello") { CorrelationId = "c-123" });
| Mode | Behavior | When to use |
|---|---|---|
| Peek-lock | process then settle | Most reliable (handles failures) |
| Receive-and-delete | delete immediately | Only when loss is acceptable |
1using Azure.Storage.Queues;
2
3var queue = new QueueClient(connectionString, "jobs");
4await queue.CreateIfNotExistsAsync();
5
6await queue.SendMessageAsync("do-work");
7var msg = await queue.ReceiveMessageAsync(visibilityTimeout: TimeSpan.FromMinutes(2));
8
9// Do work...
10await queue.DeleteMessageAsync(msg.Value.MessageId, msg.Value.PopReceipt);
Poison message rule: if the same message fails repeatedly, move it to a poison/dead-letter queue and alert.
requests
| where success == false
| summarize count() by resultCode, operation_Name
| order by count_ desc
dependencies
| where duration > 2s
| summarize avg(duration), count() by target
| order by avg_duration desc
exceptions
| summarize count() by type, outerMessage
| order by count_ desc
Correlation tip: join on operation_Id to stitch requests → dependencies → exceptions for a single transaction.
Alerting defaults
| Symptom | Likely cause | First fix to try |
|---|---|---|
| 401 from API | wrong audience/issuer | verify Entra app IDs + aud claim |
| 403 from Key Vault | missing RBAC/access policy | grant least-privilege + verify identity |
| SAS “authentication failed” | expired token / clock skew | regenerate SAS; ensure UTC + expiry |
| Cosmos 429 | RU throttling | increase RU/autoscale; optimize partition/query |
| Service Bus “lock lost” | processing too slow | renew lock / shorten processing / increase concurrency |
| Event Grid delivery failures | endpoint down / auth mismatch | fix handler auth; configure dead-letter |
| App Service works in staging but not prod | slot settings swapped | mark correct settings as slot settings |