From 6cb41cf81db92283fce8745289069ad78cc77d59 Mon Sep 17 00:00:00 2001 From: Paul Yuknewicz Date: Wed, 1 Apr 2026 10:35:31 -0700 Subject: [PATCH 01/13] feat(develop): Container Apps templates + composition (Gap-2) 15 template files for Container Apps development scaffolding: - selection.md decision tree - 6 base templates (web-app, api, microservice, worker, job, functions-on-aca) - 6 recipe templates (Dapr, Cosmos, Service Bus, Redis, ACR, PostgreSQL) - Composition algorithm Closes #1610 Parent: #1608 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../services/container-apps/templates/api.md | 159 +++++++++++++++ .../templates/functions-on-aca.md | 172 ++++++++++++++++ .../services/container-apps/templates/job.md | 181 +++++++++++++++++ .../container-apps/templates/microservice.md | 154 +++++++++++++++ .../templates/recipes/README.md | 72 +++++++ .../templates/recipes/acr/README.md | 113 +++++++++++ .../templates/recipes/composition.md | 139 +++++++++++++ .../templates/recipes/cosmos/README.md | 104 ++++++++++ .../templates/recipes/dapr/README.md | 100 ++++++++++ .../templates/recipes/postgres/README.md | 138 +++++++++++++ .../templates/recipes/redis/README.md | 110 +++++++++++ .../templates/recipes/servicebus/README.md | 124 ++++++++++++ .../container-apps/templates/selection.md | 84 ++++++++ .../container-apps/templates/web-app.md | 147 ++++++++++++++ .../container-apps/templates/worker.md | 184 ++++++++++++++++++ 15 files changed, 1981 insertions(+) create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/api.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/functions-on-aca.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/job.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/microservice.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/README.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/acr/README.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/composition.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/cosmos/README.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/dapr/README.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/postgres/README.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/redis/README.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/servicebus/README.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/selection.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/web-app.md create mode 100644 plugin/skills/azure-prepare/references/services/container-apps/templates/worker.md diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/api.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/api.md new file mode 100644 index 000000000..917b92d8e --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/api.md @@ -0,0 +1,159 @@ +# API Template — REFERENCE ONLY + +REST and gRPC API services on Azure Container Apps with ingress configuration. + +## When to Use + +- REST API with OpenAPI/Swagger +- gRPC service +- API gateway backend +- Backend-for-frontend (BFF) pattern + +## Project Structure + +``` +project-root/ +├── azure.yaml +├── Dockerfile +├── src/ +│ └── (API code) +└── infra/ + ├── main.bicep + └── app/ + └── api.bicep +``` + +## azure.yaml + +```yaml +name: my-api +metadata: + template: container-apps-api +services: + api: + host: containerapp + project: . + language: +``` + +## Bicep — API Container App + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param envId string +param containerRegistryName string +param imageName string +param userAssignedIdentityId string +param isGrpc bool = false + +resource api 'Microsoft.App/containerApps@2024-03-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'api' }) + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${userAssignedIdentityId}': {} } + } + properties: { + managedEnvironmentId: envId + configuration: { + ingress: { + external: true + targetPort: 8080 + transport: isGrpc ? 'http2' : 'auto' + corsPolicy: { + allowedOrigins: ['*'] + allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'] + allowedHeaders: ['*'] + } + } + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userAssignedIdentityId + } + ] + } + template: { + containers: [ + { + name: 'api' + image: imageName + resources: { cpu: json('0.5'), memory: '1Gi' } + env: [ + { name: 'PORT', value: '8080' } + ] + } + ] + scale: { + minReplicas: 1 + maxReplicas: 20 + rules: [ + { + name: 'http-scale' + http: { metadata: { concurrentRequests: '50' } } + } + ] + } + } + } +} + +output fqdn string = api.properties.configuration.ingress.fqdn +output name string = api.name +``` + +## REST vs gRPC + +| Setting | REST | gRPC | +|---------|------|------| +| `transport` | `auto` | `http2` | +| `targetPort` | 8080 | 8080 | +| Ingress | External or internal | External or internal | + +> ⚠️ **gRPC requires `transport: 'http2'`** — without it, gRPC calls fail. + +## Internal API (No External Ingress) + +For backend APIs only consumed by other Container Apps: + +```bicep +ingress: { + external: false // internal only + targetPort: 8080 + transport: 'auto' +} +``` + +Internal APIs are reachable at `https://.internal.`. + +## API with Authentication + +Use Easy Auth or integrate with Microsoft Entra ID: + +```bicep +configuration: { + ingress: { + external: true + targetPort: 8080 + } +} +``` + +> 💡 **Tip:** For API authentication, consider using the built-in authentication +> feature of Container Apps (Easy Auth) or validating JWT tokens in your application code. + +## CORS Configuration + +Restrict `allowedOrigins` for production: + +```bicep +corsPolicy: { + allowedOrigins: ['https://myapp.example.com'] + allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'] + allowedHeaders: ['Authorization', 'Content-Type'] + maxAge: 3600 +} +``` diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/functions-on-aca.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/functions-on-aca.md new file mode 100644 index 000000000..09c257b63 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/functions-on-aca.md @@ -0,0 +1,172 @@ +# Functions on Container Apps — REFERENCE ONLY + +Azure Functions hosted on Container Apps for event-driven triggers and bindings +with Container Apps scaling and networking. + +## When to Use + +- Event-driven processing requiring Functions triggers/bindings +- Need KEDA-based scaling with Functions programming model +- Want Container Apps networking (VNet, private endpoints) with Functions +- Migrating from Functions Consumption/Premium to Container Apps + +## Why Functions on Container Apps? + +| Feature | Functions (Flex) | Functions on ACA | +|---------|-----------------|------------------| +| Programming model | Functions v4 | Functions v4 | +| Triggers/bindings | ✅ Full support | ✅ Full support | +| Scaling | Flex Consumption | KEDA (Container Apps) | +| Networking | VNet integration | Container Apps VNet | +| Container support | Managed | Full Dockerfile control | +| Dapr integration | ❌ | ✅ | +| Side-cars | ❌ | ✅ | + +## Project Structure + +``` +project-root/ +├── azure.yaml +├── Dockerfile +├── host.json +├── src/ +│ └── (Functions code) +└── infra/ + ├── main.bicep + └── app/ + └── functions-app.bicep +``` + +## Dockerfile + +```dockerfile +# Example: Node.js Functions on ACA +FROM mcr.microsoft.com/azure-functions/node:4-node20 + +ENV AzureWebJobsScriptRoot=/home/site/wwwroot +COPY . /home/site/wwwroot +RUN cd /home/site/wwwroot && npm install --production +``` + +## azure.yaml + +```yaml +name: my-functions-aca +services: + api: + host: containerapp + project: . + language: js +``` + +## Bicep — Functions on Container Apps + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param envId string +param containerRegistryName string +param imageName string +param userAssignedIdentityId string +param storageAccountName string + +resource funcApp 'Microsoft.App/containerApps@2024-03-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'api' }) + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${userAssignedIdentityId}': {} } + } + properties: { + managedEnvironmentId: envId + configuration: { + ingress: { + external: true + targetPort: 80 + transport: 'auto' + } + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userAssignedIdentityId + } + ] + } + template: { + containers: [ + { + name: 'functions' + image: imageName + resources: { cpu: json('0.5'), memory: '1Gi' } + env: [ + { + name: 'AzureWebJobsStorage__accountName' + value: storageAccountName + } + { + name: 'AzureWebJobsStorage__credential' + value: 'managedidentity' + } + { + name: 'FUNCTIONS_EXTENSION_VERSION' + value: '~4' + } + ] + } + ] + scale: { + minReplicas: 0 + maxReplicas: 30 + } + } + } +} +``` + +## Supported Triggers + +All Functions triggers work on Container Apps: + +| Trigger | KEDA Scaler | Notes | +|---------|-------------|-------| +| HTTP | `http` | Built-in HTTP scaling | +| Timer | `cron` | Cron-based scheduling | +| Service Bus | `azure-servicebus` | Queue/topic scaling | +| Event Hubs | `azure-eventhub` | Partition-based scaling | +| Cosmos DB | `azure-cosmosdb` | Change feed scaling | +| Blob Storage | `azure-blob` | Blob count scaling | +| Storage Queue | `azure-queue` | Queue length scaling | + +## KEDA Scale Rules for Triggers + +```bicep +scale: { + minReplicas: 0 + maxReplicas: 30 + rules: [ + { + name: 'servicebus-scale' + custom: { + type: 'azure-servicebus' + metadata: { + queueName: 'myqueue' + namespace: 'my-sb-namespace' + messageCount: '5' + } + } + } + ] +} +``` + +## Key Differences from Standard Functions + +1. **You manage the Dockerfile** — base image must be `mcr.microsoft.com/azure-functions/` +2. **Scaling is KEDA-based** — configure scale rules explicitly +3. **Storage is still required** — Functions runtime needs `AzureWebJobsStorage` +4. **No Flex Consumption billing** — billed as Container Apps + +> ⚠️ **Always use the official Functions base images** from MCR. +> Custom base images may break the Functions runtime. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/job.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/job.md new file mode 100644 index 000000000..c8c71dd19 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/job.md @@ -0,0 +1,181 @@ +# Container Apps Job Template — REFERENCE ONLY + +Scheduled, event-triggered, and manual jobs on Azure Container Apps. + +## When to Use + +- Scheduled tasks (cron-based) +- Event-driven batch processing +- One-shot manual execution +- ETL pipelines, data imports, cleanup tasks + +## Job Types + +| Type | Trigger | Example | +|------|---------|---------| +| **Scheduled** | Cron expression | Nightly data sync, hourly report | +| **Event** | KEDA scaler (queue, event hub) | Process uploads, handle messages | +| **Manual** | API call / CLI | Ad-hoc migration, one-time import | + +## Project Structure + +``` +project-root/ +├── azure.yaml +├── Dockerfile +├── src/ +│ └── (job code) +└── infra/ + ├── main.bicep + └── app/ + └── job.bicep +``` + +## azure.yaml + +```yaml +name: my-job +metadata: + template: container-apps-job +services: + job: + host: containerapp + project: . +``` + +## Bicep — Scheduled Job + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param envId string +param containerRegistryName string +param imageName string +param userAssignedIdentityId string +param cronExpression string = '0 0 * * *' + +resource job 'Microsoft.App/jobs@2024-03-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'job' }) + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${userAssignedIdentityId}': {} } + } + properties: { + environmentId: envId + configuration: { + triggerType: 'Schedule' + replicaTimeout: 1800 + replicaRetryLimit: 1 + scheduleTriggerConfig: { + cronExpression: cronExpression + parallelism: 1 + replicaCompletionCount: 1 + } + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userAssignedIdentityId + } + ] + } + template: { + containers: [ + { + name: 'job' + image: imageName + resources: { cpu: json('0.5'), memory: '1Gi' } + } + ] + } + } +} + +output name string = job.name +``` + +## Bicep — Event-Triggered Job + +```bicep +resource eventJob 'Microsoft.App/jobs@2024-03-01' = { + name: '${name}-event' + location: location + tags: tags + properties: { + environmentId: envId + configuration: { + triggerType: 'Event' + replicaTimeout: 600 + replicaRetryLimit: 2 + eventTriggerConfig: { + parallelism: 1 + replicaCompletionCount: 1 + scale: { + minExecutions: 0 + maxExecutions: 10 + rules: [ + { + name: 'queue-trigger' + type: 'azure-servicebus' + metadata: { + namespace: '' + queueName: '' + messageCount: '1' + } + } + ] + } + } + } + template: { + containers: [ + { + name: 'job' + image: imageName + resources: { cpu: json('1'), memory: '2Gi' } + } + ] + } + } +} +``` + +## Bicep — Manual Job + +```bicep +configuration: { + triggerType: 'Manual' + replicaTimeout: 3600 + replicaRetryLimit: 0 +} +``` + +Start manually: + +```bash +az containerapp job start -n -g +``` + +## Common Cron Expressions + +| Schedule | Expression | +|----------|-----------| +| Every hour | `0 * * * *` | +| Daily at midnight UTC | `0 0 * * *` | +| Every 15 minutes | `*/15 * * * *` | +| Weekdays at 9 AM UTC | `0 9 * * 1-5` | +| First day of month | `0 0 1 * *` | + +## Key Configuration + +| Setting | Description | Default | +|---------|-------------|---------| +| `replicaTimeout` | Max seconds per execution | 1800 | +| `replicaRetryLimit` | Retry count on failure | 1 | +| `parallelism` | Concurrent replicas | 1 | +| `replicaCompletionCount` | Required successful replicas | 1 | + +> ⚠️ **Jobs exit after completion.** Ensure your container exits with code 0 on success +> and non-zero on failure. Container Apps tracks execution history. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/microservice.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/microservice.md new file mode 100644 index 000000000..81d650118 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/microservice.md @@ -0,0 +1,154 @@ +# Microservice Template — REFERENCE ONLY + +Multi-service architecture on Azure Container Apps with service discovery. + +## When to Use + +- Multiple independent services communicating via HTTP/gRPC +- Mono-repo or multi-repo microservice architecture +- Services with independent scaling requirements +- Dapr-enabled service mesh + +## Project Structure + +``` +project-root/ +├── azure.yaml +├── src/ +│ ├── frontend/ +│ │ └── Dockerfile +│ ├── api-gateway/ +│ │ └── Dockerfile +│ └── worker-service/ +│ └── Dockerfile +└── infra/ + ├── main.bicep + └── app/ + ├── frontend.bicep + ├── api-gateway.bicep + └── worker-service.bicep +``` + +## azure.yaml + +```yaml +name: my-microservices +metadata: + template: container-apps-microservices +services: + frontend: + host: containerapp + project: ./src/frontend + api-gateway: + host: containerapp + project: ./src/api-gateway + worker-service: + host: containerapp + project: ./src/worker-service +``` + +## Bicep — Multi-Service Environment + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} + +// Shared Container Apps Environment +resource env 'Microsoft.App/managedEnvironments@2024-03-01' = { + name: '${name}-env' + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logAnalytics.properties.customerId + sharedKey: logAnalytics.listKeys().primarySharedKey + } + } + } +} +``` + +## Service Discovery + +Container Apps in the same environment discover each other by name: + +``` +https://.internal. +``` + +### Internal Communication Pattern + +```bicep +// Frontend (external ingress) +ingress: { + external: true + targetPort: 3000 +} + +// API Gateway (internal ingress) +ingress: { + external: false + targetPort: 8080 +} + +// Worker (no ingress — processes messages only) +// Omit ingress block entirely +``` + +### Environment Variables for Service URLs + +Pass internal URLs via environment variables: + +```bicep +env: [ + { + name: 'API_GATEWAY_URL' + value: 'https://${apiGateway.properties.configuration.ingress.fqdn}' + } +] +``` + +## Scaling Per Service + +Each service scales independently: + +| Service | Min | Max | Scale Rule | +|---------|-----|-----|------------| +| Frontend | 1 | 10 | HTTP concurrency | +| API Gateway | 2 | 20 | HTTP concurrency | +| Worker | 0 | 30 | Queue depth (KEDA) | + +## With Dapr + +For microservices using Dapr, apply the [Dapr recipe](recipes/dapr/README.md) to enable: +- Service-to-service invocation +- State management +- Pub/sub messaging +- Distributed tracing + +```bicep +configuration: { + dapr: { + enabled: true + appId: 'api-gateway' + appPort: 8080 + appProtocol: 'http' + } +} +``` + +## Deployment Order + +Deploy services with dependencies in correct order: + +```bash +azd provision --no-prompt +sleep 60 # RBAC propagation +azd deploy --no-prompt +``` + +> 💡 **Tip:** `azd deploy` deploys all services defined in `azure.yaml`. +> Individual services: `azd deploy --service api-gateway`. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/README.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/README.md new file mode 100644 index 000000000..02858c77c --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/README.md @@ -0,0 +1,72 @@ +# Container Apps Template Recipes — REFERENCE ONLY + +Composable IaC + configuration modules that extend base Container Apps templates +to support specific Azure service integrations. + +## Architecture + +``` +Base Template (web-app, api, worker, job, etc.) + │ + ├── Dockerfile + app code + ├── IaC (Container Apps Environment, ACR, UAMI, RBAC) + └── AZD config (azure.yaml) + + + Recipe (per integration) + │ + ├── IaC module (new resource + RBAC + networking) + ├── Environment variables + └── Scaling rules (KEDA, if applicable) + │ + = Complete deployable project → `azd up` +``` + +## Available Recipes + +| Recipe | IaC Delta | Scaling Rules | Status | +|--------|-----------|---------------|--------| +| [dapr](dapr/README.md) | ✅ Dapr components | ❌ | ✅ Available | +| [cosmos](cosmos/README.md) | ✅ Cosmos account + DB + RBAC | ❌ | ✅ Available | +| [servicebus](servicebus/README.md) | ✅ SB namespace + queue + RBAC | ✅ KEDA azure-servicebus | ✅ Available | +| [redis](redis/README.md) | ✅ Redis cache + RBAC | ❌ | ✅ Available | +| [acr](acr/README.md) | ✅ ACR + build task | ❌ | ✅ Available | +| [postgres](postgres/README.md) | ✅ PostgreSQL Flexible + RBAC | ❌ | ✅ Available | + +## How It Works + +### Step 1: Select Base Template + +Choose from [selection.md](../selection.md) based on workload type. + +### Step 2: Apply Recipe(s) + +Read each recipe's README for: +- **IaC modules** to copy into `infra/` +- **RBAC roles** with exact GUIDs +- **Environment variables** for the container app +- **Scaling rules** (KEDA) for event-driven scenarios + +### Step 3: Wire Into Base + +**Bicep:** Add `module` reference in `main.bicep` +**Terraform:** Copy `.tf` files, merge environment variables + +### Step 4: Deploy + +```bash +azd env set AZURE_LOCATION eastus2 +azd provision --no-prompt +sleep 60 +azd deploy --no-prompt +``` + +## Design Principles + +| Principle | Why | +|-----------|-----| +| **Never synthesize base IaC** | Always use proven templates | +| **Never modify base; only extend** | Recipes are additive — no risk of breaking core | +| **Recipes own their RBAC** | Exact role GUIDs, no LLM guessing | +| **Stack-agnostic** | Container Apps runs any container — recipes work with any language | +| **Same algorithm for Bicep & Terraform** | Only IaC files differ, not composition logic | +| **UAMI everywhere** | Managed identity for all service connections | diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/acr/README.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/acr/README.md new file mode 100644 index 000000000..53b895fcc --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/acr/README.md @@ -0,0 +1,113 @@ +# ACR Recipe — REFERENCE ONLY + +Azure Container Registry build and push workflow for Container Apps. + +## When to Use + +- Building container images in Azure (no local Docker needed) +- CI/CD pipeline for Container Apps +- Private container registry with managed identity pull + +## Bicep — ACR Module + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param principalId string + +resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { + name: name + location: location + tags: tags + sku: { name: 'Basic' } + properties: { + adminUserEnabled: false + } +} + +// RBAC — AcrPull for Container App +resource acrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(acr.id, principalId, '7f951dda-4ed3-4680-a7ca-43fe172d538d') + scope: acr + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '7f951dda-4ed3-4680-a7ca-43fe172d538d' + ) + principalId: principalId + principalType: 'ServicePrincipal' + } +} + +// RBAC — AcrPush for build agent / deployer +resource acrPush 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(acr.id, principalId, '8311e382-0749-4cb8-b61a-304f252e45ec') + scope: acr + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '8311e382-0749-4cb8-b61a-304f252e45ec' + ) + principalId: principalId + principalType: 'ServicePrincipal' + } +} + +output loginServer string = acr.properties.loginServer +output name string = acr.name +``` + +## Container App Registry Configuration + +```bicep +configuration: { + registries: [ + { + server: acr.outputs.loginServer + identity: userAssignedIdentityId + } + ] +} +``` + +## ACR Build (Cloud Build) + +Build images in Azure without local Docker: + +```bash +az acr build \ + --registry \ + --image myapp:latest \ + --file Dockerfile . +``` + +## AZD + ACR Workflow + +With `azd`, image build and push is handled automatically: + +```yaml +# azure.yaml +services: + web: + host: containerapp + project: . + docker: + path: Dockerfile +``` + +`azd deploy` automatically: +1. Builds the image via ACR Tasks +2. Pushes to the linked ACR +3. Updates the Container App with the new image + +## RBAC Roles + +| Role | GUID | Access | +|------|------|--------| +| AcrPull | `7f951dda-4ed3-4680-a7ca-43fe172d538d` | Pull images | +| AcrPush | `8311e382-0749-4cb8-b61a-304f252e45ec` | Push + pull images | +| AcrDelete | `c2f4ef07-c644-48eb-af81-4b1b4947fb11` | Delete images | + +> ⚠️ **Never enable admin user** (`adminUserEnabled: false`). +> Use managed identity for image pull. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/composition.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/composition.md new file mode 100644 index 000000000..0d175734d --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/composition.md @@ -0,0 +1,139 @@ +# Composition Algorithm — REFERENCE ONLY + +Step-by-step algorithm for composing a Container Apps base template with integration recipes. + +> **This is the authoritative process. Follow it exactly.** + +## Algorithm + +``` +INPUT: + - base: web-app | api | microservice | worker | job | functions-on-aca + - recipes: dapr | cosmos | servicebus | redis | acr | postgres (zero or more) + - iac: bicep | terraform + +OUTPUT: + - Complete project directory ready for `azd up` +``` + +### Step 1: Select Base Template + +Choose the base template from [selection.md](../selection.md) based on the workload type. +Copy the base template structure into the project directory. + +```bash +ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev" +azd init -e "$ENV_NAME" --no-prompt +``` + +### Step 2: Check if Recipes Needed + +``` +IF no integrations detected: + → DONE. Base template is complete. + +IF recipes detected: + → Continue to Step 3 for each recipe. +``` + +### Step 3: Add IaC Module (per recipe) + +**Bicep:** +1. Copy recipe Bicep module into `infra/app/` +2. Add module reference in `infra/main.bicep`: + ```bicep + module cosmos './app/cosmos.bicep' = { + name: 'cosmos' + scope: rg + params: { + name: name + location: location + tags: tags + containerAppPrincipalId: app.outputs.principalId + } + } + ``` + +**Terraform:** +1. Copy recipe `.tf` file into `infra/` +2. Merge recipe app settings into container app environment variables + +### Step 4: Add Environment Variables + +Read the recipe's `README.md` for required env vars. Add them to the container app config. + +> **CRITICAL: User Assigned Managed Identity (UAMI)** +> +> Use managed identity for all service connections. Never use connection strings or keys. +> +> ```bicep +> env: [ +> { name: 'COSMOS_ENDPOINT', value: cosmos.outputs.endpoint } +> { name: 'AZURE_CLIENT_ID', value: uami.outputs.clientId } +> ] +> ``` + +### Step 5: Add RBAC Role Assignments + +Each recipe defines required RBAC roles. Use exact role definition GUIDs from recipe docs. + +```bicep +resource cosmosRbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(cosmos.id, uami.id, cosmosDataContributor) + scope: cosmos + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '00000000-0000-0000-0000-000000000002' // from recipe + ) + principalId: uami.outputs.principalId + principalType: 'ServicePrincipal' + } +} +``` + +### Step 6: Add Scaling Rules (if applicable) + +For workers and event-driven apps, add KEDA scaling rules from the recipe: + +```bicep +scale: { + minReplicas: 0 + maxReplicas: 30 + rules: [ + // From recipe scaling configuration + ] +} +``` + +### Step 7: Validate and Deploy + +```bash +azd env set AZURE_LOCATION eastus2 +azd provision --no-prompt +sleep 60 # Wait for RBAC propagation +azd deploy --no-prompt +``` + +## Multiple Recipes + +Recipes are additive. Apply each recipe independently: + +``` +Base (web-app) + + cosmos recipe → adds Cosmos module + RBAC + env vars + + redis recipe → adds Redis module + RBAC + env vars + + acr recipe → adds ACR build pipeline + = Complete project +``` + +> ⚠️ **Each recipe is independent.** No recipe should modify another recipe's resources. + +## Critical Rules + +1. **Never synthesize IaC from scratch** — always extend base template +2. **Never modify base IaC files** — only ADD recipe modules alongside them +3. **Always use recipe RBAC role GUIDs** — never let the LLM guess role IDs +4. **Always use UAMI** — never use connection strings or access keys +5. **Always use `--no-prompt`** with azd commands +6. **Always wait for RBAC propagation** — use two-phase deploy diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/cosmos/README.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/cosmos/README.md new file mode 100644 index 000000000..c84f812b9 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/cosmos/README.md @@ -0,0 +1,104 @@ +# Cosmos DB Recipe — REFERENCE ONLY + +Azure Cosmos DB integration for Container Apps. + +## When to Use + +- NoSQL document database +- Global distribution requirements +- Low-latency reads/writes +- Change feed processing + +## Bicep — Cosmos DB Module + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param principalId string + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = { + name: name + location: location + tags: tags + kind: 'GlobalDocumentDB' + properties: { + databaseAccountOfferType: 'Standard' + disableLocalAuthentication: true + locations: [ + { locationName: location, failoverPriority: 0 } + ] + capabilities: [ + { name: 'EnableServerless' } + ] + } +} + +resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' = { + parent: cosmos + name: 'appdb' + properties: { + resource: { id: 'appdb' } + } +} + +resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { + parent: database + name: 'items' + properties: { + resource: { + id: 'items' + partitionKey: { paths: ['/partitionKey'], kind: 'Hash' } + } + } +} + +// RBAC — Cosmos DB Built-in Data Contributor +resource rbac 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15' = { + parent: cosmos + name: guid(cosmos.id, principalId, 'data-contributor') + properties: { + roleDefinitionId: '${cosmos.id}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002' + principalId: principalId + scope: cosmos.id + } +} + +output endpoint string = cosmos.properties.documentEndpoint +output databaseName string = database.name +output containerName string = container.name +``` + +## Environment Variables + +```bicep +env: [ + { name: 'COSMOS_ENDPOINT', value: cosmos.outputs.endpoint } + { name: 'COSMOS_DATABASE', value: cosmos.outputs.databaseName } + { name: 'COSMOS_CONTAINER', value: cosmos.outputs.containerName } + { name: 'AZURE_CLIENT_ID', value: uami.outputs.clientId } +] +``` + +## RBAC Roles + +| Role | GUID | Access | +|------|------|--------| +| Cosmos DB Built-in Data Contributor | `00000000-0000-0000-0000-000000000002` | Read + write data | +| Cosmos DB Built-in Data Reader | `00000000-0000-0000-0000-000000000001` | Read-only data | + +## SDK Connection (Node.js Example) + +```javascript +const { CosmosClient } = require("@azure/cosmos"); +const { DefaultAzureCredential } = require("@azure/identity"); + +const client = new CosmosClient({ + endpoint: process.env.COSMOS_ENDPOINT, + aadCredentials: new DefaultAzureCredential({ + managedIdentityClientId: process.env.AZURE_CLIENT_ID, + }), +}); +``` + +> ⚠️ **Always set `disableLocalAuthentication: true`** — use RBAC only, never keys. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/dapr/README.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/dapr/README.md new file mode 100644 index 000000000..fdc8c5731 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/dapr/README.md @@ -0,0 +1,100 @@ +# Dapr Integration Recipe — REFERENCE ONLY + +Dapr (Distributed Application Runtime) integration for Container Apps +providing service invocation, state management, and pub/sub messaging. + +## When to Use + +- Service-to-service communication (HTTP/gRPC invocation) +- Distributed state management +- Pub/sub messaging between microservices +- Distributed tracing across services + +## Capabilities + +| Component | Description | Use Case | +|-----------|-------------|----------| +| Service Invocation | Call other services by app ID | Microservice communication | +| State Store | Key/value state management | Session state, caches | +| Pub/Sub | Publish and subscribe to topics | Event-driven messaging | +| Bindings | Input/output bindings to external systems | Trigger from / push to services | + +## Bicep — Enable Dapr on Container App + +```bicep +configuration: { + dapr: { + enabled: true + appId: 'my-service' + appPort: 8080 + appProtocol: 'http' // or 'grpc' + } +} +``` + +## Bicep — Dapr State Store Component (Cosmos DB) + +```bicep +resource stateStore 'Microsoft.App/managedEnvironments/daprComponents@2024-03-01' = { + parent: env + name: 'statestore' + properties: { + componentType: 'state.azure.cosmosdb' + version: 'v1' + metadata: [ + { name: 'url', value: cosmosEndpoint } + { name: 'database', value: 'daprdb' } + { name: 'collection', value: 'state' } + { name: 'azureClientId', value: uamiClientId } + ] + scopes: ['my-service'] + } +} +``` + +## Bicep — Dapr Pub/Sub Component (Service Bus) + +```bicep +resource pubsub 'Microsoft.App/managedEnvironments/daprComponents@2024-03-01' = { + parent: env + name: 'pubsub' + properties: { + componentType: 'pubsub.azure.servicebus.topics' + version: 'v1' + metadata: [ + { + name: 'namespaceName' + value: '${serviceBusNamespace}.servicebus.windows.net' + } + { name: 'azureClientId', value: uamiClientId } + ] + scopes: ['publisher-service', 'subscriber-service'] + } +} +``` + +## Service Invocation Example + +```python +# Call another service via Dapr sidecar +import requests + +DAPR_PORT = 3500 +response = requests.get( + f"http://localhost:{DAPR_PORT}/v1.0/invoke/order-service/method/orders" +) +``` + +## Required RBAC Roles + +| Service | Role | GUID | +|---------|------|------| +| Cosmos DB (state) | Cosmos DB Built-in Data Contributor | `00000000-0000-0000-0000-000000000002` | +| Service Bus (pub/sub) | Azure Service Bus Data Owner | `090c5cfd-751d-490a-894a-3ce6f1109419` | + +## Environment Variables + +No additional env vars needed — Dapr sidecar handles connections via component metadata. + +> 💡 **Tip:** Scope Dapr components to specific app IDs using `scopes` to enforce +> least-privilege access between services. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/postgres/README.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/postgres/README.md new file mode 100644 index 000000000..6f40c4d54 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/postgres/README.md @@ -0,0 +1,138 @@ +# PostgreSQL Recipe — REFERENCE ONLY + +Azure Database for PostgreSQL Flexible Server integration for Container Apps. + +## When to Use + +- Relational database for CRUD applications +- PostgreSQL-compatible workloads +- Applications requiring SQL queries, joins, transactions +- Django, Rails, Spring Data, Prisma backends + +## Bicep — PostgreSQL Module + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param principalId string +param principalName string +param databaseName string = 'appdb' + +resource postgres 'Microsoft.DBforPostgreSQL/flexibleServers@2023-12-01-preview' = { + name: name + location: location + tags: tags + sku: { + name: 'Standard_B1ms' + tier: 'Burstable' + } + properties: { + version: '16' + storage: { storageSizeGB: 32 } + authConfig: { + activeDirectoryAuth: 'Enabled' + passwordAuth: 'Disabled' + } + highAvailability: { mode: 'Disabled' } + } +} + +resource database 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-12-01-preview' = { + parent: postgres + name: databaseName + properties: { charset: 'UTF8', collation: 'en_US.utf8' } +} + +// Entra ID administrator +resource admin 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2023-12-01-preview' = { + parent: postgres + name: principalId + properties: { + principalType: 'ServicePrincipal' + principalName: principalName + tenantId: tenant().tenantId + } +} + +output fqdn string = postgres.properties.fullyQualifiedDomainName +output databaseName string = database.name +``` + +## Environment Variables + +```bicep +env: [ + { name: 'PGHOST', value: postgres.outputs.fqdn } + { name: 'PGDATABASE', value: postgres.outputs.databaseName } + { name: 'PGPORT', value: '5432' } + { name: 'PGSSLMODE', value: 'require' } + { name: 'AZURE_CLIENT_ID', value: uami.outputs.clientId } +] +``` + +## Authentication + +Use Entra ID (passwordless) authentication: + +```python +# Python example +from azure.identity import DefaultAzureCredential +import psycopg2 +import os + +credential = DefaultAzureCredential( + managed_identity_client_id=os.environ["AZURE_CLIENT_ID"] +) +token = credential.get_token("https://ossrdbms-aad.database.windows.net/.default") + +conn = psycopg2.connect( + host=os.environ["PGHOST"], + database=os.environ["PGDATABASE"], + user=os.environ["AZURE_CLIENT_ID"], + password=token.token, + sslmode="require", +) +``` + +## Node.js Connection + +```javascript +const { DefaultAzureCredential } = require("@azure/identity"); +const { Client } = require("pg"); + +const credential = new DefaultAzureCredential({ + managedIdentityClientId: process.env.AZURE_CLIENT_ID, +}); +const token = await credential.getToken( + "https://ossrdbms-aad.database.windows.net/.default" +); + +const client = new Client({ + host: process.env.PGHOST, + database: process.env.PGDATABASE, + user: process.env.AZURE_CLIENT_ID, + password: token.token, + ssl: { rejectUnauthorized: true }, + port: 5432, +}); +``` + +## Firewall + +For Container Apps with VNet integration, use private endpoints or service endpoints. +Without VNet, allow Azure services: + +```bicep +resource firewallRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2023-12-01-preview' = { + parent: postgres + name: 'AllowAzureServices' + properties: { + startIpAddress: '0.0.0.0' + endIpAddress: '0.0.0.0' + } +} +``` + +> ⚠️ **Always disable password auth** — set `passwordAuth: 'Disabled'` +> and use Entra ID authentication only. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/redis/README.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/redis/README.md new file mode 100644 index 000000000..7ed974e0c --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/redis/README.md @@ -0,0 +1,110 @@ +# Redis Recipe — REFERENCE ONLY + +Azure Managed Redis (or Azure Cache for Redis) integration for Container Apps. + +## When to Use + +- Application caching (session, output, data) +- Distributed state store +- Rate limiting +- Dapr state store backend + +## Bicep — Redis Module + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param principalId string + +resource redis 'Microsoft.Cache/redis@2024-03-01' = { + name: name + location: location + tags: tags + properties: { + sku: { + name: 'Basic' + family: 'C' + capacity: 0 + } + enableNonSslPort: false + minimumTlsVersion: '1.2' + redisConfiguration: { + 'aad-enabled': 'true' + } + } +} + +// RBAC — Redis Cache Contributor +resource rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(redis.id, principalId, 'e0f68234-74aa-48ed-b826-c38b57376e17') + scope: redis + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'e0f68234-74aa-48ed-b826-c38b57376e17' + ) + principalId: principalId + principalType: 'ServicePrincipal' + } +} + +output hostName string = redis.properties.hostName +output sslPort int = redis.properties.sslPort +``` + +## Environment Variables + +```bicep +env: [ + { name: 'REDIS_HOSTNAME', value: redis.outputs.hostName } + { name: 'REDIS_PORT', value: string(redis.outputs.sslPort) } + { name: 'AZURE_CLIENT_ID', value: uami.outputs.clientId } +] +``` + +## RBAC Roles + +| Role | GUID | Access | +|------|------|--------| +| Redis Cache Contributor | `e0f68234-74aa-48ed-b826-c38b57376e17` | Manage cache + data | +| Redis Cache Data Access | Custom role | Data plane operations | + +## SDK Connection (Node.js Example) + +```javascript +const { createClient } = require("redis"); +const { DefaultAzureCredential } = require("@azure/identity"); + +const credential = new DefaultAzureCredential({ + managedIdentityClientId: process.env.AZURE_CLIENT_ID, +}); + +const client = createClient({ + url: `rediss://${process.env.REDIS_HOSTNAME}:${process.env.REDIS_PORT}`, + credential, +}); +``` + +## Dapr State Store + +Redis can also be used as a Dapr state store component: + +```bicep +resource stateStore 'Microsoft.App/managedEnvironments/daprComponents@2024-03-01' = { + parent: env + name: 'statestore' + properties: { + componentType: 'state.redis' + version: 'v1' + metadata: [ + { name: 'redisHost', value: '${redis.outputs.hostName}:${redis.outputs.sslPort}' } + { name: 'enableTLS', value: 'true' } + { name: 'azureClientId', value: uamiClientId } + ] + } +} +``` + +> ⚠️ **Always enable Entra ID authentication** via `aad-enabled: true` +> and disable non-SSL port. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/servicebus/README.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/servicebus/README.md new file mode 100644 index 000000000..351e3d72f --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/recipes/servicebus/README.md @@ -0,0 +1,124 @@ +# Service Bus Recipe — REFERENCE ONLY + +Azure Service Bus integration with KEDA scaling for Container Apps. + +## When to Use + +- Reliable message queuing between services +- Pub/sub with topics and subscriptions +- Worker scaling based on queue depth +- Ordered message processing + +## Bicep — Service Bus Module + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param principalId string +param queueName string = 'tasks' + +resource sb 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' = { + name: name + location: location + tags: tags + sku: { name: 'Standard', tier: 'Standard' } + properties: { + disableLocalAuth: true + } +} + +resource queue 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = { + parent: sb + name: queueName + properties: { + maxDeliveryCount: 10 + lockDuration: 'PT1M' + deadLetteringOnMessageExpiration: true + } +} + +// RBAC — Azure Service Bus Data Owner +resource rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(sb.id, principalId, '090c5cfd-751d-490a-894a-3ce6f1109419') + scope: sb + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '090c5cfd-751d-490a-894a-3ce6f1109419' + ) + principalId: principalId + principalType: 'ServicePrincipal' + } +} + +output namespace string = sb.name +output fqdn string = '${sb.name}.servicebus.windows.net' +output queueName string = queue.name +``` + +## Environment Variables + +```bicep +env: [ + { name: 'SERVICEBUS_FQDN', value: sb.outputs.fqdn } + { name: 'SERVICEBUS_QUEUE', value: sb.outputs.queueName } + { name: 'AZURE_CLIENT_ID', value: uami.outputs.clientId } +] +``` + +## KEDA Scaling Rule + +Scale workers based on queue message count: + +```bicep +scale: { + minReplicas: 0 + maxReplicas: 30 + rules: [ + { + name: 'servicebus-scale' + custom: { + type: 'azure-servicebus' + metadata: { + namespace: sb.outputs.namespace + queueName: sb.outputs.queueName + messageCount: '5' + } + auth: [ + { + secretRef: 'sb-identity' + triggerParameter: 'connection' + } + ] + } + } + ] +} +``` + +## RBAC Roles + +| Role | GUID | Access | +|------|------|--------| +| Azure Service Bus Data Owner | `090c5cfd-751d-490a-894a-3ce6f1109419` | Full access | +| Azure Service Bus Data Sender | `69a216fc-b8fb-44d8-bc22-1f3c2cd27a39` | Send only | +| Azure Service Bus Data Receiver | `4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0` | Receive only | + +## SDK Connection (Python Example) + +```python +from azure.servicebus import ServiceBusClient +from azure.identity import DefaultAzureCredential +import os + +credential = DefaultAzureCredential( + managed_identity_client_id=os.environ["AZURE_CLIENT_ID"] +) +client = ServiceBusClient( + fully_qualified_namespace=os.environ["SERVICEBUS_FQDN"], + credential=credential, +) +``` + +> ⚠️ **Always set `disableLocalAuth: true`** — use RBAC only, never SAS keys. diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/selection.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/selection.md new file mode 100644 index 000000000..358ebaad7 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/selection.md @@ -0,0 +1,84 @@ +# Template Selection Decision Tree — REFERENCE ONLY + +**CRITICAL**: Check indicators IN ORDER before defaulting to web app. + +**Architecture**: All deployments start from a [base template](web-app.md) per stack. +Integrations are applied as [composable recipes](recipes/README.md) on top of the base. +See [composition.md](recipes/composition.md) for the merge algorithm. + +Container Apps hosts **any** containerised app — any language, any framework, any SDK. +Event-driven processing with triggers/bindings uses [Functions on Container Apps](functions-on-aca.md). + +``` +1. Is this event-driven with Functions triggers/bindings? + Indicators: BlobTrigger, ServiceBusTrigger, EventHubTrigger, + TimerTrigger, CosmosDBTrigger, DurableOrchestration, + host.json, @app.service_bus_queue, @app.schedule + └─► YES → Functions on Container Apps (see functions-on-aca.md) + +2. Is this a Container Apps Job (not a long-running service)? + Indicators: scheduled task, cron job, one-shot batch, + event-triggered processing, manual job + └─► YES → Job template (see job.md) + +3. Is this a microservices architecture? + Indicators: multiple services, service discovery, Dapr, + docker-compose with 3+ services, mono-repo with services/ + └─► YES → Microservice template (see microservice.md) + +4. Is this a background worker / queue processor? + Indicators: queue consumer, long-running task, KEDA scaling on queue depth, + no HTTP ingress needed, worker process + └─► YES → Worker template (see worker.md) + +5. Is this a REST or gRPC API? + Indicators: REST API, OpenAPI/Swagger, gRPC, API gateway, + /api/ routes, Express/FastAPI/ASP.NET controllers + └─► YES → API template (see api.md) + +6. Does it use Dapr? + Indicators: dapr.io/enabled annotation, Dapr SDK imports, + state store, pub/sub, service invocation + └─► YES → Use appropriate base + dapr recipe (recipes/dapr/) + +7. Does it need a database? + Indicators: Cosmos DB, PostgreSQL, Redis, SQL + └─► YES → Use appropriate base + database recipe + +8. Does it use messaging? + Indicators: Service Bus, Event Hubs, Storage Queues + └─► YES → Use appropriate base + messaging recipe + +9. DEFAULT → Web app template (see web-app.md) +``` + +## Recipe Index + +| Integration | Recipe | Description | +|-------------|--------|-------------| +| Dapr | [recipes/dapr/](recipes/dapr/README.md) | Service invocation, state, pub/sub | +| Cosmos DB | [recipes/cosmos/](recipes/cosmos/README.md) | NoSQL database | +| Service Bus | [recipes/servicebus/](recipes/servicebus/README.md) | Messaging with KEDA scaling | +| Redis | [recipes/redis/](recipes/redis/README.md) | Cache / state store | +| ACR | [recipes/acr/](recipes/acr/README.md) | Container registry build + push | +| PostgreSQL | [recipes/postgres/](recipes/postgres/README.md) | PostgreSQL Flexible Server | + +## Base Templates + +| Template | Use Case | File | +|----------|----------|------| +| Web app | General-purpose serverless web app | [web-app.md](web-app.md) | +| API | REST / gRPC API services | [api.md](api.md) | +| Microservice | Multi-service architecture | [microservice.md](microservice.md) | +| Worker | Background processing | [worker.md](worker.md) | +| Job | Scheduled / event / manual jobs | [job.md](job.md) | +| Functions on ACA | Event-driven triggers/bindings | [functions-on-aca.md](functions-on-aca.md) | + +## Critical Rules + +1. **Container Apps is stack-agnostic** — any language, any framework, any container image +2. **Use UAMI (User Assigned Managed Identity)** for all service connections — never connection strings +3. **Always use `--no-prompt`** with azd commands +4. **Never synthesize Bicep/Terraform from scratch** — use AZD templates or proven modules +5. **Use KEDA scaling rules** for event-driven workloads (queue depth, HTTP concurrency, cron) +6. **Disable local auth** on all backing services (Cosmos, Service Bus, etc.) diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/web-app.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/web-app.md new file mode 100644 index 000000000..55ef9f401 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/web-app.md @@ -0,0 +1,147 @@ +# Web App Template — REFERENCE ONLY + +High-scale serverless web app on Azure Container Apps. +Supports any language/framework: Node.js, Python, .NET, Java, Go, Rust, etc. + +## When to Use + +- General-purpose web application with HTTP ingress +- Frontend + backend in a single container +- Any framework: Express, FastAPI, ASP.NET, Spring Boot, Gin, etc. + +## Project Structure + +``` +project-root/ +├── azure.yaml +├── Dockerfile +├── src/ +│ └── (application code) +└── infra/ + ├── main.bicep # or *.tf + ├── main.parameters.json + └── app/ + └── container-app.bicep +``` + +## azure.yaml + +```yaml +name: my-web-app +metadata: + template: container-apps-web-app +services: + web: + host: containerapp + project: . + language: +``` + +## Bicep — Container App Module + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param containerRegistryName string +param imageName string +param envId string +param userAssignedIdentityId string + +resource app 'Microsoft.App/containerApps@2024-03-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'web' }) + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${userAssignedIdentityId}': {} } + } + properties: { + managedEnvironmentId: envId + configuration: { + ingress: { + external: true + targetPort: 3000 + transport: 'auto' + } + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userAssignedIdentityId + } + ] + } + template: { + containers: [ + { + name: 'main' + image: imageName + resources: { cpu: json('0.5'), memory: '1Gi' } + } + ] + scale: { + minReplicas: 0 + maxReplicas: 10 + rules: [ + { + name: 'http-scale' + http: { metadata: { concurrentRequests: '100' } } + } + ] + } + } + } +} + +output fqdn string = app.properties.configuration.ingress.fqdn +output name string = app.name +output principalId string = app.identity.userAssignedIdentities[userAssignedIdentityId].principalId +``` + +## Deployment + +```bash +ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev" +azd init -e "$ENV_NAME" --no-prompt +azd env set AZURE_LOCATION eastus2 +azd up --no-prompt +``` + +## Scaling + +| Rule | Trigger | Default | +|------|---------|---------| +| HTTP | Concurrent requests | 100 per instance | +| Min replicas | Scale to zero | 0 | +| Max replicas | Burst limit | 10 | + +## Language-Specific `targetPort` + +| Framework | Default Port | +|-----------|-------------| +| Node.js (Express) | 3000 | +| Python (FastAPI/Gunicorn) | 8000 | +| .NET (Kestrel) | 8080 | +| Java (Spring Boot) | 8080 | +| Go (net/http) | 8080 | + +> ⚠️ **Set `targetPort` to match your app's listen port.** Mismatched ports cause 502 errors. + +## Health Probes + +Always configure liveness and readiness probes. See [health-probes.md](../health-probes.md). + +```bicep +probes: [ + { + type: 'liveness' + httpGet: { path: '/healthz', port: 3000 } + periodSeconds: 10 + } + { + type: 'readiness' + httpGet: { path: '/ready', port: 3000 } + periodSeconds: 5 + } +] +``` diff --git a/plugin/skills/azure-prepare/references/services/container-apps/templates/worker.md b/plugin/skills/azure-prepare/references/services/container-apps/templates/worker.md new file mode 100644 index 000000000..8e08cc509 --- /dev/null +++ b/plugin/skills/azure-prepare/references/services/container-apps/templates/worker.md @@ -0,0 +1,184 @@ +# Worker Template — REFERENCE ONLY + +Background processing and long-running tasks on Azure Container Apps. + +## When to Use + +- Queue consumer (Service Bus, Storage Queue, Event Hubs) +- Long-running background processing +- No HTTP ingress required +- Event-driven scaling with KEDA + +## Project Structure + +``` +project-root/ +├── azure.yaml +├── Dockerfile +├── src/ +│ └── (worker code) +└── infra/ + ├── main.bicep + └── app/ + └── worker.bicep +``` + +## azure.yaml + +```yaml +name: my-worker +metadata: + template: container-apps-worker +services: + worker: + host: containerapp + project: . + language: +``` + +## Bicep — Worker Container App + +```bicep +param name string +param location string = resourceGroup().location +param tags object = {} +param envId string +param containerRegistryName string +param imageName string +param userAssignedIdentityId string +param serviceBusNamespace string = '' +param queueName string = '' + +resource worker 'Microsoft.App/containerApps@2024-03-01' = { + name: name + location: location + tags: union(tags, { 'azd-service-name': 'worker' }) + identity: { + type: 'UserAssigned' + userAssignedIdentities: { '${userAssignedIdentityId}': {} } + } + properties: { + managedEnvironmentId: envId + configuration: { + // No ingress — worker has no HTTP endpoint + registries: [ + { + server: '${containerRegistryName}.azurecr.io' + identity: userAssignedIdentityId + } + ] + } + template: { + containers: [ + { + name: 'worker' + image: imageName + resources: { cpu: json('0.5'), memory: '1Gi' } + env: [ + { + name: 'SERVICEBUS_NAMESPACE' + value: '${serviceBusNamespace}.servicebus.windows.net' + } + { name: 'QUEUE_NAME', value: queueName } + { + name: 'AZURE_CLIENT_ID' + value: '' // Set to UAMI client ID + } + ] + } + ] + scale: { + minReplicas: 0 + maxReplicas: 30 + rules: [ + { + name: 'queue-scale' + custom: { + type: 'azure-servicebus' + metadata: { + namespace: serviceBusNamespace + queueName: queueName + messageCount: '5' + } + auth: [ + { + secretRef: 'sb-connection' + triggerParameter: 'connection' + } + ] + } + } + ] + } + } + } +} + +output name string = worker.name +``` + +## Scaling Patterns + +### Service Bus Queue Scaling (KEDA) + +```bicep +rules: [ + { + name: 'queue-scale' + custom: { + type: 'azure-servicebus' + metadata: { + namespace: '' + queueName: '' + messageCount: '5' // scale up when > 5 messages + } + } + } +] +``` + +### Event Hubs Scaling (KEDA) + +```bicep +rules: [ + { + name: 'eventhub-scale' + custom: { + type: 'azure-eventhub' + metadata: { + consumerGroup: '$Default' + unprocessedEventThreshold: '64' + } + } + } +] +``` + +### Storage Queue Scaling (KEDA) + +```bicep +rules: [ + { + name: 'storage-queue-scale' + custom: { + type: 'azure-queue' + metadata: { + queueName: '' + queueLength: '5' + } + } + } +] +``` + +## Key Differences from Web App + +| Aspect | Web App | Worker | +|--------|---------|--------| +| Ingress | External HTTP | None | +| Scale trigger | HTTP concurrency | Queue depth / events | +| Min replicas | 0–1 | 0 (scale to zero) | +| Health probes | HTTP liveness/readiness | TCP or none | + +> ⚠️ **Workers with no ingress cannot use HTTP health probes.** +> Use TCP probes or omit probes and rely on container restart policy. From 1452d81f940133a5b8a4a941ec1ce2f2811b50c5 Mon Sep 17 00:00:00 2001 From: Simon J Date: Fri, 24 Apr 2026 09:35:48 -0500 Subject: [PATCH 02/13] fix(develop): 17 corrections + orphan fix for PR #1636 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(develop): apply 17 corrections to ACA develop templates Critical fixes (6): - Cosmos DB: disableLocalAuth property name (ARM API) - Cosmos DB: sqlRoleAssignments for data-plane RBAC - Redis: Redis Cache Data Owner role + correct GUID - Redis: add useEntraID to Dapr state store metadata - Event job: add identity block and registries - Functions: add AzureWebJobsStorage__clientId Major fixes (10): - Worker/ServiceBus/Functions: KEDA identity-based auth - Worker: wire uamiClientId param to AZURE_CLIENT_ID - Worker: add missing EventHub/Queue scaler fields - Redis: fix Node.js SDK to use token-based auth - Composition: fix param name principalId mismatch - Composition: Cosmos RBAC resource type correction - Composition: reword contradictory rule #2 - Microservice: declare logAnalytics params - API: add CORS production warning CI fix: - SKILL.md version bump 1.1.1 -> 1.1.2 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(develop): link ACA templates into skill reference chain Add Templates & Recipes section to container-apps/README.md linking to selection.md, composition.md, and recipes/README.md — resolves orphaned files CI check. Also add ACA template loading guidance in analyze.md and research.md for parity with existing Functions template references. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- plugin/skills/azure-prepare/SKILL.md | 2 +- .../azure-prepare/references/analyze.md | 8 +++++- .../azure-prepare/references/research.md | 4 ++- .../services/container-apps/README.md | 6 +++++ .../services/container-apps/templates/api.md | 1 + .../templates/functions-on-aca.md | 5 ++++ .../services/container-apps/templates/job.md | 10 ++++++++ .../container-apps/templates/microservice.md | 7 ++++-- .../templates/recipes/composition.md | 17 ++++++------- .../templates/recipes/cosmos/README.md | 4 +-- .../templates/recipes/redis/README.md | 25 ++++++++++++------- .../templates/recipes/servicebus/README.md | 9 +++---- .../container-apps/templates/worker.md | 15 +++++------ 13 files changed, 74 insertions(+), 39 deletions(-) diff --git a/plugin/skills/azure-prepare/SKILL.md b/plugin/skills/azure-prepare/SKILL.md index 7ee4e0ba1..7cc968c57 100644 --- a/plugin/skills/azure-prepare/SKILL.md +++ b/plugin/skills/azure-prepare/SKILL.md @@ -4,7 +4,7 @@ description: "Prepare Azure apps for deployment (infra Bicep/Terraform, azure.ya license: MIT metadata: author: Microsoft - version: "1.1.1" + version: "1.1.2" --- # Azure Prepare diff --git a/plugin/skills/azure-prepare/references/analyze.md b/plugin/skills/azure-prepare/references/analyze.md index e29b74f12..14ff85fb8 100644 --- a/plugin/skills/azure-prepare/references/analyze.md +++ b/plugin/skills/azure-prepare/references/analyze.md @@ -97,4 +97,10 @@ Converting an existing application to run on Azure. > ⚠️ **Critical**: The Functions `bicep.md` and `terraform.md` files are **REFERENCE DOCUMENTATION**, not templates to copy. Hand-writing infrastructure from these patterns results in missing RBAC, incorrect managed identity configuration, and security vulnerabilities. -For other compute targets (Container Apps, App Service, Static Web Apps), load their respective README files in `services/` for guidance. +For **Container Apps**, load the composition rules the same way: + +1. Load `services/container-apps/templates/selection.md` — decision tree for base template + recipe +2. Load `services/container-apps/templates/recipes/composition.md` — the exact algorithm to follow +3. Use `azd init -t