Installation
gh skills-hub install azure-prepare Don't have the extension? Run gh extension install samueltauil/skills-hub first.
Download and extract to your repository:
.github/skills/azure-prepare/ Extract the ZIP to .github/skills/ in your repo. The folder name must match azure-prepare for Copilot to auto-discover it.
Skill Files (164)
SKILL.md 13.2 KB
---
name: azure-prepare
description: "Prepare Azure apps for deployment (infra Bicep/Terraform, azure.yaml, Dockerfiles). Use for create/modernize or create+deploy; not cross-cloud migration (use azure-cloud-migrate). DO NOT USE FOR: copilot-sdk apps (use azure-hosted-copilot-sdk). WHEN: \"create app\", \"build web app\", \"create API\", \"create serverless HTTP API\", \"create frontend\", \"create back end\", \"build a service\", \"modernize application\", \"update application\", \"add authentication\", \"add caching\", \"host on Azure\", \"create and deploy\", \"deploy to Azure\", \"deploy to Azure using Terraform\", \"deploy to Azure App Service\", \"deploy to Azure App Service using Terraform\", \"deploy to Azure Container Apps\", \"deploy to Azure Container Apps using Terraform\", \"generate Terraform\", \"generate Bicep\", \"function app\", \"timer trigger\", \"service bus trigger\", \"event-driven function\", \"containerized Node.js app\", \"social media app\", \"static portfolio website\", \"todo list with frontend and API\", \"prepare my Azure application to use Key Vault\", \"managed identity\"."
license: MIT
metadata:
author: Microsoft
version: "1.2.12"
---
# Azure Prepare
> **AUTHORITATIVE GUIDANCE ā MANDATORY COMPLIANCE**
>
> This document is the **official, canonical source** for preparing applications for Azure deployment. You **MUST** follow these instructions exactly as written unless they contradict security policies given to you. When in doubt, present the conflicting instructions from this document and ask the user for explicit confirmation. Do not improvise, infer, or substitute steps.
---
## Triggers
Activate this skill when user wants to:
- Create a new application
- Add services or components to an existing app
- Make updates or changes to existing application
- Modernize or migrate an application
- Set up Azure infrastructure
- Deploy to Azure or host on Azure
- Create and deploy to Azure (including Terraform-based deployment requests)
## Rules
1. **Plan first ā MANDATORY** ā You MUST physically write an initial `.azure/deployment-plan.md` **skeleton in the workspace root directory** (not the session-state folder) **as your very first action** ā before any code generation or execution begins. Write the skeleton immediately, then populate it progressively as Phase 1 analysis and research unfold; finalize it with all decisions at Phase 1 Step 6. This file must exist on disk throughout. azure-validate and azure-deploy depend on it and will fail without it. Do not skip or defer this step.
2. **Get approval** ā Present plan to user before execution
3. **Research before generating** ā Load references and invoke related skills
4. **Update plan progressively** ā Mark steps complete as you go
5. **Validate before deploy** ā Invoke azure-validate before azure-deploy
6. **Confirm Azure context** ā Use `ask_user` for subscription and location per [Azure Context](references/azure-context.md)
7. ā **Destructive actions require `ask_user`** ā [Global Rules](references/global-rules.md)
8. ā **NEVER delete user project or workspace directories** ā When adding features to an existing project, MODIFY existing files. `azd init -t <template>` is for NEW projects only; do NOT run `azd init -t` in an existing workspace. Plain `azd init` (without a template argument) may be used in existing workspaces when appropriate. File deletions within a project (e.g., removing build artifacts or temp files) are permitted when appropriate, but NEVER delete the user's project or workspace directory itself. See [Global Rules](references/global-rules.md).
9. **Scope: preparation only** ā This skill generates infrastructure code and configuration files. Deployment execution (`azd up`, `azd deploy`, `terraform apply`) is handled by the **azure-deploy** skill, which provides built-in error recovery and deployment verification.
10. ā **SQL Server Bicep: NEVER generate `administratorLogin` or `administratorLoginPassword`** ā not in direct properties, not in conditional/ternary branches, not anywhere in the file. Always use Entra-only authentication (`azureADOnlyAuthentication: true`) unconditionally. See [references/services/sql-database/bicep.md](references/services/sql-database/bicep.md).
11. **Remove stale template IaC after conversion** ā If you converted Bicep templates from the selected `azd` template into Terraform templates, remove the Bicep templates that were introduced by that `azd` template and are now fully replaced by Terraform equivalents. Do not remove user-authored Bicep files. Only remove those template-provided Bicep files after the Terraform IaC is complete and Terraform has been selected as the deployment path. Before handing off to azure-validate skill, keep only the IaC templates required by the chosen deployment path.
---
## ā PLAN-FIRST WORKFLOW ā MANDATORY
> **YOU MUST CREATE A PLAN BEFORE DOING ANY WORK**
>
> 1. **STOP** ā Do not generate any code, infrastructure, or configuration yet
> 2. **CREATE SKELETON** - Write an initial `.azure/deployment-plan.md` skeleton to disk **immediately** (before any code generation or execution begins), then populate it progressively as Phase 1 steps 1-5 reveal details; finalize it at Step 6
> 3. **CONFIRM** ā Present the completed plan to the user and get approval
> 4. **EXECUTE** ā Only after approval, execute the plan step by step
>
> The `.azure/deployment-plan.md` file is the **source of truth** for this workflow and for azure-validate and azure-deploy skills. Without it, those skills will fail.
>
> ā ļø **CRITICAL: `.azure/deployment-plan.md` must be WRITTEN TO DISK inside the workspace root** (e.g., `/tmp/my-project/.azure/deployment-plan.md`), not in the session-state folder. Use a file-write tool to create this file. This is the deployment plan artifact read by azure-validate and azure-deploy. **You MUST create this file ā do not proceed without it.**
> ā ļø **CRITICAL: You must create the file with the name `.azure/deployment-plan.md` as is**. You must not use other names such as `.azure/plan.md`.
>
> ā **Critical:** Skipping the plan file creation will cause azure-validate and azure-deploy to fail. This requirement has no exceptions.
---
## ā STEP 0: Specialized Technology Check ā MANDATORY FIRST ACTION
**BEFORE starting Phase 1**, check if the user's prompt OR workspace codebase matches a specialized technology that has a dedicated skill with tested templates. If matched, **invoke that skill FIRST** ā then resume azure-prepare for validation and deployment.
### Check 1: Prompt keywords
| Prompt keywords | Invoke FIRST |
|----------------|-------------|
| Lambda, AWS Lambda, migrate AWS, migrate GCP, Lambda to Functions, migrate from AWS, migrate from GCP | **azure-cloud-migrate** |
| copilot SDK, copilot app, copilot-powered, @github/copilot-sdk, CopilotClient | **azure-hosted-copilot-sdk** |
| Azure Functions, function app, serverless function, timer trigger, HTTP trigger, func new | Stay in **azure-prepare** ā prefer Azure Functions templates in Step 4 |
| APIM, API Management, API gateway, deploy APIM | Stay in **azure-prepare** ā see [APIM Deployment Guide](references/apim.md) |
| AI gateway, AI gateway policy, AI gateway backend, AI gateway configuration | **azure-aigateway** |
| workflow, orchestration, multi-step, pipeline, fan-out/fan-in, saga, long-running process, durable, order processing | Stay in **azure-prepare** ā select **durable** recipe in Step 4. **MUST** load [durable.md](references/services/functions/durable.md), [DTS reference](references/services/durable-task-scheduler/README.md), and [DTS Bicep patterns](references/services/durable-task-scheduler/bicep.md). |
### Check 2: Codebase markers (even if prompt is generic like "deploy to Azure")
| Codebase marker | Where | Invoke FIRST |
|----------------|-------|-------------|
| `@github/copilot-sdk` in dependencies | `package.json` | **azure-hosted-copilot-sdk** |
| `copilot-sdk` in name or dependencies | `package.json` | **azure-hosted-copilot-sdk** |
| `CopilotClient` import | `.ts`/`.js` source files | **azure-hosted-copilot-sdk** |
| `createSession` + `sendAndWait` calls | `.ts`/`.js` source files | **azure-hosted-copilot-sdk** |
> ā ļø Check the user's **prompt text** ā not just existing code. Critical for greenfield projects with no codebase to scan. See [full routing table](references/specialized-routing.md).
After the specialized skill completes, **resume azure-prepare** at Phase 1 Step 4 (Select Recipe) for remaining infrastructure, validation, and deployment.
---
## Phase 1: Planning (BLOCKING ā Complete Before Any Execution)
Create `.azure/deployment-plan.md` by completing these steps. Do NOT generate any artifacts until the plan is approved.
| # | Action | Reference |
|---|--------|-----------|
| 0 | **ā Check Prompt AND Codebase for Specialized Tech** ā If user mentions copilot SDK, Azure Functions, etc., OR codebase contains `@github/copilot-sdk`, invoke that skill first | [specialized-routing.md](references/specialized-routing.md) |
| 1 | **Analyze Workspace** ā Determine mode: NEW, MODIFY, or MODERNIZE | [analyze.md](references/analyze.md) |
| 2 | **Gather Requirements** ā Classification, scale, budget | [requirements.md](references/requirements.md) |
| 3 | **Scan Codebase** ā Identify components, technologies, dependencies | [scan.md](references/scan.md) |
| 4 | **Select Recipe** ā Choose AZD (default), AZCLI, Bicep, or Terraform | [recipe-selection.md](references/recipe-selection.md) |
| 5 | **Plan Architecture** ā Select stack + map components to Azure services | [architecture.md](references/architecture.md) |
| 6 | **Finalize Plan (MANDATORY)** - Use a file-write tool to finalize `.azure/deployment-plan.md` with all decisions from steps 1-5. Update the skeleton written at the start of Phase 1 with the complete content. The file must be fully populated before you present the plan to the user. | [plan-template.md](references/plan-template.md) |
| 7 | **Present Plan** ā Show plan to user and ask for approval | `.azure/deployment-plan.md` |
| 8 | **Destructive actions require `ask_user`** | [Global Rules](references/global-rules.md) |
---
> **ā STOP HERE** ā Do NOT proceed to Phase 2 until the user approves the plan.
---
## Phase 2: Execution (Only After Plan Approval)
Execute the approved plan. Update `.azure/deployment-plan.md` status after each step.
| # | Action | Reference |
|---|--------|-----------|
| 1 | **Research Components** ā Load service references + invoke related skills | [research.md](references/research.md) |
| 2 | **Confirm Azure Context** ā Detect and confirm subscription + location and check the resource provisioning limit | [Azure Context](references/azure-context.md) |
| 3 | **Generate Artifacts** ā Create infrastructure and configuration files | [generate.md](references/generate.md) |
| 4 | **Harden Security** ā Apply security best practices | [security.md](references/security.md) |
| 5 | **Functional Verification** ā Verify the app works (UI + backend), locally if possible | [functional-verification.md](references/functional-verification.md) |
| 6 | **ā Update Plan (MANDATORY before hand-off)** ā Use the `edit` tool to change the Status in `.azure/deployment-plan.md` to `Ready for Validation`. You **MUST** complete this edit **BEFORE** invoking azure-validate. Do NOT skip this step. | `.azure/deployment-plan.md` |
| 7 | **ā MANDATORY Hand Off** ā Invoke **azure-validate** skill. Your preparation work is done. Do NOT run `azd up`, `azd deploy`, or any deployment command directly ā all deployment execution is handled by azure-deploy after azure-validate completes. **PREREQUISITE:** Step 6 must be completed first ā `.azure/deployment-plan.md` status must say `Ready for Validation`. | ā |
---
## Outputs
| Artifact | Location |
|----------|----------|
| **Plan** | `.azure/deployment-plan.md` |
| Infrastructure | `./infra/` |
| AZD Config | `azure.yaml` (AZD only) |
| Dockerfiles | `src/<component>/Dockerfile` |
---
## SDK Quick References
- **Azure Developer CLI**: [azd](references/sdk/azd-deployment.md)
- **Azure Identity**: [Python](references/sdk/azure-identity-py.md) | [.NET](references/sdk/azure-identity-dotnet.md) | [TypeScript](references/sdk/azure-identity-ts.md) | [Java](references/sdk/azure-identity-java.md)
- **App Configuration**: [Python](references/sdk/azure-appconfiguration-py.md) | [TypeScript](references/sdk/azure-appconfiguration-ts.md) | [Java](references/sdk/azure-appconfiguration-java.md)
---
## Next
> **ā MANDATORY NEXT STEP ā DO NOT SKIP**
>
> After completing preparation, you **MUST** invoke **azure-validate** before any deployment attempt. Do NOT skip validation. Do NOT go directly to azure-deploy. Do NOT run `azd up` or any deployment command directly. The workflow is:
>
> `azure-prepare` ā `azure-validate` ā `azure-deploy`
>
> **ā BEFORE invoking azure-validate**, you MUST use the `edit` tool to update `.azure/deployment-plan.md` status to `Ready for Validation`. If the plan status has not been updated, the validation will fail.
>
> This applies to ALL deployment scenarios including containerized apps, Container Apps, App Service, Azure Functions, static sites, and any other Azure target. No exceptions.
>
> Skipping validation leads to deployment failures. Be patient and follow the complete workflow for the highest success outcome.
**ā Update plan status to `Ready for Validation`, then invoke azure-validate**
analyze.md 4.3 KB
# Analyze Workspace
## ā MANDATORY FIRST ā Specialized Technology Delegation
**STOP. Before choosing a mode, check the user's prompt for specialized technology keywords.**
If matched, invoke the corresponding skill **immediately** ā it has tested templates and correct SDK usage.
> ā ļø **Re-entry guard**: If azure-prepare was invoked as a **resume** from a specialized skill (e.g., azure-hosted-copilot-sdk Step 4), **skip this check** and go directly to Step 4.
| User prompt mentions | Action |
|---------------------|--------|
| copilot SDK, copilot app, copilot-powered, copilot-sdk-service, @github/copilot-sdk, CopilotClient, sendAndWait | **Invoke azure-hosted-copilot-sdk skill NOW** ā then resume azure-prepare at Step 4 |
| Azure Functions, function app, serverless function, timer trigger, func new | Stay in **azure-prepare**. When selecting compute, **prefer Azure Functions** templates and best practices, then continue from Step 4. |
> ā ļø Check the user's **prompt text** ā not just existing code. This is critical for greenfield projects with no codebase. See [full routing table](specialized-routing.md).
If no match, continue below.
---
## Three Modes ā Always Choose One
> **ā IMPORTANT**: Always go through one of these three paths. Having `azure.yaml` does NOT mean you skip to validate ā the user may want to modify or extend the app.
| Mode | When to Use |
|------|-------------|
| **NEW** | Empty workspace, or user wants to create a new app |
| **MODIFY** | Existing Azure app, user wants to add features/components |
| **MODERNIZE** | Existing non-Azure app, user wants to migrate to Azure |
## Decision Tree
```
What does the user want to do?
ā
āāā Create new application ā Mode: NEW
ā
āāā Add/change features to existing app
ā āāā Has azure.yaml/infra? ā Mode: MODIFY
ā āāā No Azure config? ā Mode: MODERNIZE (add Azure support first)
ā
āāā Migrate/modernize for Azure
āāā Cross-cloud migration (AWS/GCP/Lambda)? ā **Invoke azure-cloud-migrate skill** (do NOT continue in azure-prepare)
āāā On-prem or generic modernization ā Mode: MODERNIZE
```
## Mode: NEW
Creating a new Azure application from scratch.
**Actions:**
1. Confirm project type with user
2. Gather requirements ā [requirements.md](requirements.md)
3. Select technology stack
4. Update plan
## Mode: MODIFY
Adding components/services to an existing Azure application.
**Actions:**
1. Scan existing codebase ā [scan.md](scan.md)
2. Identify existing Azure configuration
3. Gather requirements for new components
4. Update plan
## Mode: MODERNIZE
Converting an existing application to run on Azure.
**Actions:**
1. Full codebase scan ā [scan.md](scan.md)
2. Analyze existing infrastructure (Docker, CI/CD, etc.)
3. Gather requirements ā [requirements.md](requirements.md)
4. Map existing components to Azure services
5. Update plan
## Detection Signals
| Signal | Indicates |
|--------|-----------|
| `azure.yaml` exists | AZD project (MODIFY mode likely) |
| `infra/*.bicep` exists | Bicep IaC |
| `infra/*.tf` exists | Terraform IaC |
| `Dockerfile` exists | Containerized app |
| No Azure files | NEW or MODERNIZE mode |
---
## ā MANDATORY for Azure Functions: Load Composition Rules BEFORE Execution
**If the target compute is Azure Functions**, you MUST load the composition algorithm before generating ANY infrastructure:
1. Load `services/functions/templates/selection.md` ā decision tree for base template + recipe
2. Load `services/functions/templates/recipes/composition.md` ā the exact algorithm to follow
3. Use `functions_template_get` MCP tool to list and fetch templates and write `functionFiles[]` + `projectFiles[]` directly ā NEVER hand-write Bicep/Terraform and use `azd init -t <template>`/`func init`/`func new` as fallback when composing multiple recipes and required templates are not found
> ā ļø **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.
apim.md 5.7 KB
# APIM Deployment Guide
Deploy Azure API Management (APIM) as part of your Azure infrastructure.
> **For AI Gateway configuration** (policies, backends, semantic caching), use the **azure-aigateway** skill after deployment.
---
## When to Deploy APIM
| Scenario | APIM Tier | Notes |
|----------|-----------|-------|
| AI Gateway for model governance | Standard v2 or Premium v2 | Semantic caching requires v2 SKUs |
| API consolidation | Standard v2 | Single entry point for microservices |
| MCP tool hosting | Standard v2 | Rate limiting and auth for AI tools |
| Development / Testing | Developer | Not for production |
| High-volume production | Premium v2 | Multi-region, higher limits |
---
## Quick Deploy (Azure CLI)
### 1. Create APIM Instance
```bash
az apim create \
--name <apim-name> \
--resource-group <rg> \
--location <location> \
--publisher-name "<your-org>" \
--publisher-email "<admin@org.com>" \
--sku-name "StandardV2" \
--sku-capacity 1
# ā APIM provisioning takes 30-45 minutes for Standard/Premium tiers
```
### 2. Enable Managed Identity
```bash
az apim update --name <apim-name> --resource-group <rg> \
--set identity.type=SystemAssigned
```
### 3. Get Gateway URL
```bash
az apim show --name <apim-name> --resource-group <rg> \
--query "gatewayUrl" -o tsv
```
---
## Bicep Template
```bicep
@description('Name of the API Management instance')
param apimName string
@description('Location for the APIM instance')
param location string = resourceGroup().location
@description('Publisher organization name')
param publisherName string
@description('Publisher email address')
param publisherEmail string
@description('SKU name (StandardV2 recommended for AI Gateway)')
@allowed(['Developer', 'StandardV2', 'PremiumV2'])
param skuName string = 'StandardV2'
@description('Number of scale units')
param skuCapacity int = 1
resource apim 'Microsoft.ApiManagement/service@2023-09-01-preview' = {
name: apimName
location: location
sku: {
name: skuName
capacity: skuCapacity
}
identity: {
type: 'SystemAssigned'
}
properties: {
publisherName: publisherName
publisherEmail: publisherEmail
}
}
output apimId string = apim.id
output gatewayUrl string = apim.properties.gatewayUrl
output principalId string = apim.identity.principalId
```
### With Azure OpenAI Backend (AI Gateway Pattern)
```bicep
@description('Name of the Azure OpenAI resource')
param aoaiName string
@description('Resource group of the Azure OpenAI resource')
param aoaiResourceGroup string = resourceGroup().name
// Reference existing Azure OpenAI
resource aoai 'Microsoft.CognitiveServices/accounts@2024-04-01-preview' existing = {
name: aoaiName
scope: resourceGroup(aoaiResourceGroup)
}
// APIM Backend pointing to Azure OpenAI
resource openaiBackend 'Microsoft.ApiManagement/service/backends@2023-09-01-preview' = {
parent: apim
name: 'openai-backend'
properties: {
protocol: 'http'
url: '${aoai.properties.endpoint}openai'
tls: {
validateCertificateChain: true
validateCertificateName: true
}
}
}
// Grant APIM access to Azure OpenAI
resource cognitiveServicesUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(apim.id, aoai.id, 'Cognitive Services User')
scope: aoai
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')
principalId: apim.identity.principalId
principalType: 'ServicePrincipal'
}
}
```
---
## Terraform Module
```hcl
resource "azurerm_api_management" "apim" {
name = var.apim_name
location = var.location
resource_group_name = var.resource_group_name
publisher_name = var.publisher_name
publisher_email = var.publisher_email
sku_name = "${var.sku_name}_${var.sku_capacity}"
identity {
type = "SystemAssigned"
}
}
output "gateway_url" {
value = azurerm_api_management.apim.gateway_url
}
output "principal_id" {
value = azurerm_api_management.apim.identity[0].principal_id
}
```
---
## Post-Deployment Steps
After APIM is deployed:
1. **Configure AI backends** ā Use **azure-aigateway** skill
2. **Import APIs** ā `az apim api import` or portal
3. **Apply policies** ā Invoke **azure-aigateway** skill for AI governance policies
4. **Enable monitoring** ā Connect Application Insights
5. **Secure endpoints** ā Configure subscriptions and RBAC
---
## SKU Selection Guide
| Feature | Developer | Standard v2 | Premium v2 |
|---------|-----------|-------------|------------|
| GenAI policies | ā
| ā
| ā
|
| Semantic caching | ā | ā
| ā
|
| VNet integration | ā | ā
| ā
|
| Multi-region | ā | ā | ā
|
| SLA | None | 99.95% | 99.99% |
| Scale units | 1 | 1-10 | 1-12 per region |
| Provisioning time | ~30 min | ~30 min | ~45 min |
> **Recommendation**: Use **Standard v2** for most AI Gateway scenarios. Use **Premium v2** only for multi-region or high-compliance requirements.
---
## Naming Conventions
| Resource | Pattern | Example |
|----------|---------|---------|
| APIM Instance | `apim-<app>-<env>` | `apim-myapp-prod` |
| API | `<api-name>-api` | `openai-api` |
| Backend | `<service>-backend` | `openai-backend` |
| Product | `<tier>-product` | `premium-product` |
| Subscription | `<consumer>-sub` | `frontend-sub` |
---
## References
- [APIM v2 Overview](https://learn.microsoft.com/azure/api-management/v2-service-tiers-overview)
- [APIM Bicep Reference](https://learn.microsoft.com/azure/templates/microsoft.apimanagement/service)
- [APIM Terraform](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/api_management)
- [GenAI Gateway Capabilities](https://learn.microsoft.com/azure/api-management/genai-gateway-capabilities)
architecture.md 6.3 KB
# Architecture Planning
Select hosting stack and map components to Azure services.
## Stack Selection
| Stack | Best For | Azure Services |
| --------------- | ------------------------------------------------------ | --------------------------------- |
| **Containers** | Docker experience, complex dependencies, microservices | Container Apps, AKS, ACR |
| **Serverless** | Event-driven, variable traffic, cost optimization | Functions, Logic Apps, Event Grid |
| **App Service** | Traditional web apps, PaaS preference | App Service, Static Web Apps |
### Decision Factors
| Factor | Containers | Serverless | App Service |
| ------------------------ | :--------: | :--------------------------: | :---------: |
| Docker experience | āā | | |
| Event-driven | ā | āā | |
| Variable traffic | | āā | ā |
| Complex dependencies | āā | | ā |
| Long-running processes | āā | ā (Durable Functions) | ā |
| Workflow / orchestration | | āā (Durable Functions + DTS) | |
| Minimal ops overhead | | āā | ā |
### Container Hosting: Container Apps vs AKS
| Factor | Container Apps | AKS |
| ------------------------- | :-------------------------: | :---------------------------------: |
| **Scale to zero** | āā | |
| **Kubernetes API access** | | āā |
| **Custom operators/CRDs** | | āā |
| **Service mesh** | Dapr (built-in) | Istio |
| **Networking/dataplane** | Managed platform defaults | Azure CNI powered by Cilium |
| **GPU workloads** | | āā |
| **Best for** | Microservices, event-driven | Full K8s control, complex workloads |
#### When to Use Container Apps
- Microservices without Kubernetes complexity
- Event-driven workloads (KEDA built-in)
- Need scale-to-zero for cost optimization
- Teams without Kubernetes expertise
#### When to Use AKS
- Need Kubernetes API/kubectl access
- Require custom operators or CRDs
- Service mesh requirements (Istio, Linkerd)
- GPU/ML workloads
- Complex networking or multi-tenant architectures
> **AKS Planning:** For AKS SKU selection (Automatic vs Standard), networking, identity, scaling, and security configuration, invoke the **azure-kubernetes** skill.
## Service Mapping
### Hosting
| Component Type | Primary Service | Alternatives |
| ------------------------ | ----------------- | ------------------------------------------------ |
| SPA Frontend | Static Web Apps | Blob + CDN |
| SSR Web App | Container Apps | App Service, AKS |
| REST/GraphQL API | Container Apps | App Service, Functions, AKS |
| Background Worker | Container Apps | Functions, AKS |
| Scheduled Task | Functions (Timer) | Container Apps Jobs, Kubernetes CronJob (on AKS) |
| Event Processor | Functions | Container Apps, AKS + KEDA |
| Microservices (full K8s) | AKS | Container Apps |
| GPU/ML Workloads | AKS | Azure ML |
### Data
| Need | Primary | Reference | Alternatives |
| ---------- | ------------ | ----------------------------------------------- | ----------------- |
| Relational | Azure SQL | [SQL Database](services/sql-database/README.md) | PostgreSQL, MySQL |
| Document | Cosmos DB | [Cosmos DB](services/cosmos-db/README.md) | MongoDB |
| Cache | Redis Cache | | |
| Files | Blob Storage | [Storage](services/storage/README.md) | Files Storage |
| Search | AI Search | | |
### Integration
| Need | Service |
| ------------- | ----------- |
| Message Queue | Service Bus |
| Pub/Sub | Event Grid |
| Streaming | Event Hubs |
### Workflow & Orchestration
| Need | Service | Notes |
| ----------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Multi-step workflow / orchestration | **Durable Functions + Durable Task Scheduler** | DTS is the **required** managed backend for Durable Functions. Do NOT use Azure Storage or MSSQL backends. See [durable.md](services/functions/durable.md). |
| Low-code / visual workflow | Logic Apps | For integration-heavy, low-code scenarios |
### Supporting (Always Include)
| Service | Purpose |
| -------------------- | ----------------------- |
| Log Analytics | Centralized logging |
| Application Insights | Monitoring, APM |
| Key Vault | Secrets management |
| Managed Identity | Service-to-service auth |
---
## Document Architecture
Record selections in `.azure/deployment-plan.md` with rationale for each choice.
aspire.md 18.0 KB
# .NET Aspire Projects
> ā **CRITICAL - READ THIS FIRST**
>
> For .NET Aspire projects, **NEVER manually create azure.yaml or infra/ files.**
> Always use `azd init --from-code` which auto-detects the AppHost and generates everything correctly.
>
> **Failure to follow this causes:** "Could not find a part of the path 'infra\main.bicep'" error.
Guidance for preparing .NET Aspire applications for Azure deployment.
**š For detailed AZD workflow:** See [recipes/azd/aspire.md](recipes/azd/aspire.md)
## What is .NET Aspire?
.NET Aspire is an opinionated, cloud-ready stack for building observable, production-ready distributed applications. Aspire projects use an AppHost orchestrator to define and configure the application's components, services, and dependencies.
## Detection
A .NET Aspire project is identified by:
| Indicator | Description |
|-----------|-------------|
| `*.AppHost.csproj` | AppHost orchestrator project file |
| `Aspire.Hosting` package | Core Aspire hosting package reference |
| `Aspire.Hosting.AppHost` | Alternative Aspire hosting package |
**Example project structure:**
```
orleans-voting/
āāā OrleansVoting.sln
āāā OrleansVoting.AppHost/
ā āāā OrleansVoting.AppHost.csproj ā AppHost indicator
āāā OrleansVoting.Web/
āāā OrleansVoting.Api/
āāā OrleansVoting.Grains/
```
## Azure Preparation Workflow
### Step 1: Detection
When scanning the codebase (per [scan.md](scan.md)), detect Aspire by:
```bash
# Check for AppHost project
find . -name "*.AppHost.csproj"
# Or check for Aspire.Hosting package reference
grep -r "Aspire.Hosting" . --include="*.csproj"
```
### ā Step 1a: Pre-Check for Custom/Non-Deployable Resources (MANDATORY)
**Before running `azd init --from-code`, scan the AppHost source code to understand whether the app may contain local-only custom resources.**
```bash
# Find the AppHost project and scan only its source directory
APPHOST_PROJECT=$(find . -name "*.AppHost.csproj" | head -1)
APPHOST_DIR=$(dirname "$APPHOST_PROJECT")
grep -r "ExcludeFromManifest" "$APPHOST_DIR" --include="*.cs" | head -20
```
**PowerShell:**
```powershell
# Find the AppHost project and scan only its source directory
$appHostProject = Get-ChildItem -Recurse -Filter "*.AppHost.csproj" | Select-Object -First 1
$appHostDir = $appHostProject.DirectoryName
Get-ChildItem -Path $appHostDir -Recurse -Filter "*.cs" | Select-String "ExcludeFromManifest" | Select-Object -First 20
```
This scan is informational. `.ExcludeFromManifest()` can appear alongside deployable resources, so a positive match does **not** immediately block deployment. What matters is the final `azure.yaml` output after `azd init --from-code` completes:
- If `azd init` **fails** with `unsupported resource type` ā see Step 2 error guidance below.
- If `azd init` **succeeds** but `azure.yaml` has an empty or missing `services` section ā see Step 4a below.
> š” **Why scan early:** Knowing that `.ExcludeFromManifest()` is present gives useful context when azd errors or generates an empty manifest ā it confirms the app intentionally targets local development rather than Azure deployment.
### Step 2: Initialize with azd
**CRITICAL: For Aspire projects, use `azd init --from-code -e <environment-name>` instead of creating azure.yaml manually.**
**ā ļø ALWAYS include the `-e <environment-name>` flag:** Without it, `azd init` will fail in non-interactive environments (agents, CI/CD) with the error: `no default response for prompt 'Enter a unique environment name:'`
The `--from-code` flag:
- Auto-detects the AppHost orchestrator
- Reads the Aspire service definitions
- Generates appropriate `azure.yaml` and infrastructure
- Works in non-interactive/CI environments when combined with `-e` flag
```bash
# Non-interactive initialization for Aspire projects (REQUIRED for agents)
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init --from-code -e "$ENV_NAME"
```
**Why both flags are required:**
- `--from-code`: Tells azd to detect the AppHost automatically (no "How do you want to initialize?" prompt)
- `-e <name>`: Provides environment name upfront (no "Enter environment name:" prompt)
- Together, they enable fully non-interactive operation essential for automation, agents, and CI/CD pipelines
**ā If `azd init --from-code` fails with "unsupported resource type":**
This error means the AppHost contains custom Aspire resource types that azd cannot process for Azure deployment:
1. ā **Do NOT attempt to fix this error by modifying source code** ā do not add `.ExcludeFromManifest()` calls or otherwise patch the AppHost
2. ā **Do NOT proceed with deployment** ā the application is designed for local development only
3. ā
Record a blocker: "AppHost contains custom Aspire resource types (`unsupported resource type`) that cannot be deployed to Azure"
4. ā
Inform the user: this application uses custom Aspire resource authoring patterns intended for local tooling, not cloud deployment
> ā ļø **Why modifying source code is forbidden:** Adding `.ExcludeFromManifest()` may suppress the error and allow `azd init` to succeed, but the deployment outcome will not reflect the application's actual intent. The custom resources are deliberately designed to be local-only.
### Step 3: Configure Subscription and Location
> **ā CRITICAL**: After `azd init --from-code` completes, you **MUST** immediately set the user-confirmed subscription and location.
>
> **DO NOT** skip this step or delay it until validation. The `azd init` command creates an environment but does NOT inherit the Azure CLI's subscription. If you skip this step, azd will use its own default subscription, which may differ from the user's confirmed choice.
**Set the subscription and location immediately after initialization:**
```bash
# Set the user-confirmed subscription ID
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
# Set the location
azd env set AZURE_LOCATION <location>
```
**Verify the configuration:**
```bash
azd env get-values
```
Confirm that `AZURE_SUBSCRIPTION_ID` and `AZURE_LOCATION` match the user's confirmed choices from [Azure Context](azure-context.md).
### Step 4: What azd Generates
`azd init --from-code` creates:
| Artifact | Location | Description |
|----------|----------|-------------|
| `azure.yaml` | Project root | Service definitions from AppHost |
| `infra/` | Project root | Bicep templates for Azure resources |
| `.azure/` | Project root | Environment configuration |
### ā Step 4a: Validate Generated Output
**MANDATORY: After `azd init --from-code` completes, verify the generated `azure.yaml` contains deployable services.**
```bash
# Check if azure.yaml has a non-empty services section
cat azure.yaml
```
**If the `services` section is empty or missing:** The AppHost has no deployable resources. This happens when all resources use `.ExcludeFromManifest()` (e.g., custom resource demonstrations, local-only tooling). In this case:
1. ā **Do NOT proceed with deployment** ā there is nothing to deploy
2. ā
Keep the plan status in a valid state (for example, leave it as **Planning**) and record a blocker in the plan body with the reason: "Application contains only custom/demo Aspire resources with no Azure-deployable services"
3. ā
Inform the user that this application is designed for local development and cannot be meaningfully deployed to Azure
4. ā Do NOT manually create Bicep, Dockerfiles, or azure.yaml to work around this ā the absence of services is the correct result
**Example generated azure.yaml:**
```yaml
name: orleans-voting
# metadata section is auto-generated by azd init --from-code
services:
web:
project: ./OrleansVoting.Web
language: dotnet
host: containerapp
api:
project: ./OrleansVoting.Api
language: dotnet
host: containerapp
```
### ā Step 4b: Fix Azure Functions Secret Storage (MANDATORY for Aspire + Functions)
**MANDATORY: After `azd init --from-code` succeeds, check if the AppHost contains Azure Functions and fix secret storage configuration.**
This step **MUST** run BEFORE `azd up` or `azd provision`. Skipping it causes a runtime failure: `Secret initialization from Blob storage failed`.
**1. Detect Azure Functions in the AppHost:**
```bash
APPHOST_DIR=$(dirname "$(find . -name '*.AppHost.csproj' | head -1)")
grep -n "AddAzureFunctionsProject" "$APPHOST_DIR"/*.cs
```
**PowerShell:**
```powershell
$appHostDir = (Get-ChildItem -Recurse -Filter "*.AppHost.csproj" | Select-Object -First 1).DirectoryName
Get-ChildItem -Path $appHostDir -Filter "*.cs" | Select-String "AddAzureFunctionsProject"
```
**If `AddAzureFunctionsProject` is NOT found ā skip this step.**
**2. Check if `AzureWebJobsSecretStorageType` is already configured:**
```bash
grep -n "AzureWebJobsSecretStorageType" "$APPHOST_DIR"/*.cs
```
**PowerShell:**
```powershell
Get-ChildItem -Path $appHostDir -Filter "*.cs" | Select-String "AzureWebJobsSecretStorageType"
```
**If already present ā skip this step.**
**3. Add the environment variable to the Functions builder chain:**
Use the `edit` tool to add `.WithEnvironment("AzureWebJobsSecretStorageType", "Files")` to the `AddAzureFunctionsProject` builder chain in the AppHost source file.
**Before:**
```csharp
var functions = builder.AddAzureFunctionsProject<Projects.MyFunctions>("functions")
.WithHostStorage(storage)
.WithReference(queues);
```
**After:**
```csharp
var functions = builder.AddAzureFunctionsProject<Projects.MyFunctions>("functions")
.WithHostStorage(storage)
.WithEnvironment("AzureWebJobsSecretStorageType", "Files")
.WithReference(queues);
```
> š” **Tip:** Place `.WithEnvironment(...)` immediately after `.WithHostStorage(...)` for clarity.
> ā ļø **Why this is required:** When Aspire uses `WithHostStorage(storage)`, it configures identity-based storage URIs (e.g., `AzureWebJobsStorage__blobServiceUri`). Azure Functions' secret/key manager does **not** support these identity-based URIs ā it requires either a connection string or file-based storage. Setting `AzureWebJobsSecretStorageType=Files` switches to file-system key storage, bypassing the incompatible blob dependency.
See [aspire-functions-secrets reference](services/functions/aspire-containerapps.md) for additional details.
## Flags Reference
### azd init for Aspire
| Flag | Required | Description |
|------|----------|-------------|
| `--from-code` | ā
Yes | Auto-detect AppHost, no interactive prompts |
| `-e <name>` | ā
Yes | Environment name (required for non-interactive) |
| `--no-prompt` | Optional | Skip additional confirmations |
**Complete initialization sequence:**
```bash
# 1. Initialize the environment
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init --from-code -e "$ENV_NAME"
# 2. IMMEDIATELY set the user-confirmed subscription
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
# 3. Set the location
azd env set AZURE_LOCATION <location>
# 4. Verify configuration
azd env get-values
```
## Common Aspire Samples
| Sample | Repository | Notes |
|--------|------------|-------|
| orleans-voting | [dotnet/aspire-samples](https://github.com/dotnet/aspire-samples/tree/main/samples/orleans-voting) | Orleans cluster with voting app |
| AspireYarp | [dotnet/aspire-samples](https://github.com/dotnet/aspire-samples/tree/main/samples/AspireYarp) | YARP reverse proxy |
| AspireWithDapr | [dotnet/aspire-samples](https://github.com/dotnet/aspire-samples/tree/main/samples/AspireWithDapr) | Dapr integration |
| eShop | [dotnet/eShop](https://github.com/dotnet/eShop) | Reference microservices app |
## Troubleshooting
### Error: "no default response for prompt 'Enter a unique environment name:'"
**Cause:** Missing `-e` flag when running `azd init --from-code` in non-interactive environment
**Solution:** Always include the `-e <environment-name>` flag
```bash
# ā Wrong - fails in non-interactive environments (agents, CI/CD)
azd init --from-code
# ā
Correct - provides environment name upfront
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init --from-code -e "$ENV_NAME"
```
**Important:** This error typically occurs when:
- Running in an agent or automation context
- No TTY is available for interactive prompts
- The `-e` flag was omitted
### Error: "no default response for prompt 'How do you want to initialize your app?'"
**Cause:** Missing `--from-code` flag
**Solution:** Add `--from-code` to the `azd init` command
```bash
# ā Wrong - requires interactive prompt
azd init -e "my-env"
# ā
Correct - auto-detects AppHost
azd init --from-code -e "my-env"
```
### No AppHost detected
**Symptoms:** `azd init --from-code` doesn't find the AppHost
**Solutions:**
1. Verify AppHost project exists: `find . -name "*.AppHost.csproj"`
2. Check project builds: `dotnet build`
3. Ensure Aspire.Hosting package is referenced in AppHost project
### Error: "unsupported resource type" during manifest generation
**Symptoms:** `azd init --from-code` fails with output like:
```
error: unsupported resource type: <custom-resource-type>
```
or the manifest generation step errors on child resources (e.g., ClockHand, or other custom resource types defined in the AppHost).
**Cause:** The AppHost contains custom Aspire resource types that azd cannot convert to Azure deployable resources. These custom types are typically:
- Demonstration resources showing developers how to build Aspire extensions for local tooling
- Resources that wrap local services without Azure equivalents
- Custom child resources (e.g., subcomponents of a custom Aspire integration)
**Resolution:**
1. ā **Do NOT attempt to fix this error by modifying source code** ā do not add `.ExcludeFromManifest()` calls or otherwise patch the AppHost
2. ā **Do NOT proceed with deployment** ā this is a deployment blocker, not a recoverable error
3. ā
Record a blocker in the deployment plan: "AppHost contains custom Aspire resource types not supported for Azure deployment (unsupported resource type)"
4. ā
Inform the user that this application is designed for local development and cannot be meaningfully deployed to Azure
> ā ļø **Why this is a hard stop:** Custom resource types that produce "unsupported resource type" errors are intentionally not deployable. Adding `.ExcludeFromManifest()` to suppress the error may allow `azd init` to succeed, but the resulting deployment would not represent the application's actual functionality.
### Azure Functions: Secret initialization from Blob storage failed
**Symptoms:** Azure Functions app fails at startup with error:
```
System.InvalidOperationException: Secret initialization from Blob storage failed due to missing both
an Azure Storage connection string and a SAS connection uri.
```
**Cause:** When using `AddAzureFunctionsProject` with `WithHostStorage(storage)`, Aspire configures identity-based storage access (managed identity). However, Azure Functions' internal secret management does not support identity-based URIs and requires file-based secret storage for Container Apps deployments.
**Solution:** Add `AzureWebJobsSecretStorageType=Files` environment variable to the Functions resource in the AppHost **before running `azd up`**:
```csharp
var functions = builder.AddAzureFunctionsProject<Projects.ImageGallery_Functions>("functions")
.WithReference(queues)
.WithReference(blobs)
.WaitFor(storage)
.WithRoleAssignments(storage, ...)
.WithHostStorage(storage)
.WithEnvironment("AzureWebJobsSecretStorageType", "Files") // Required for Container Apps
.WithUrlForEndpoint("http", u => u.DisplayText = "Functions App");
```
> š” **Why this is required:**
> - `WithHostStorage(storage)` sets identity-based URIs like `AzureWebJobsStorage__blobServiceUri`
> - This is correct and secure for runtime storage operations
> - However, Functions' secret/key management doesn't support these URIs
> - File-based secrets are mandatory for Container Apps deployments
> ā ļø **Important:** This is required when:
> - Using `AddAzureFunctionsProject` in Aspire
> - Using `WithHostStorage()` with identity-based storage
> - Deploying to Azure Container Apps (the default for Aspire Functions)
**Generated Infrastructure Note:**
If you need to modify the generated Container Apps infrastructure directly, ensure the Functions container app has this environment variable:
```bicep
resource functionsContainerApp 'Microsoft.App/containerApps@2024-03-01' = {
properties: {
template: {
containers: [
{
env: [
{
name: 'AzureWebJobsSecretStorageType'
value: 'Files'
}
// ... other environment variables
]
}
]
}
}
}
```
### Error: azd uses wrong subscription despite user confirmation
**Symptoms:** `azd provision --preview` shows a different subscription than the one the user confirmed
**Cause:** The `AZURE_SUBSCRIPTION_ID` was not set immediately after `azd init --from-code`. The Azure CLI and azd can have different default subscriptions.
**Solution:** Always set the subscription immediately after initialization:
```bash
# After azd init --from-code completes:
azd env set AZURE_SUBSCRIPTION_ID <user-confirmed-subscription-id>
azd env set AZURE_LOCATION <location>
# Verify before proceeding:
azd env get-values
```
**Prevention:** Follow the complete initialization sequence in the [Flags Reference](#azd-init-for-aspire) section above.
## References
- [.NET Aspire Documentation](https://learn.microsoft.com/en-us/dotnet/aspire/)
- [Azure Developer CLI (azd)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/)
- [Aspire Samples Repository](https://github.com/dotnet/aspire-samples)
- [azd + Aspire Integration](https://learn.microsoft.com/en-us/dotnet/aspire/deployment/azure/aca-deployment-azd-in-depth)
## Next Steps
After `azd init --from-code`:
1. Review generated `azure.yaml` and `infra/` files (if present)
2. Set AZURE_SUBSCRIPTION_ID and AZURE_LOCATION with `azd env set`
3. Customize infrastructure as needed
4. Proceed to **azure-validate** skill
5. Deploy with **azure-deploy** skill
> ā ļø **Important for Container Apps:** If using Aspire with Container Apps, azure-validate will check and help set up required environment variables after provisioning.
auth-best-practices.md 6.0 KB
# Azure Authentication Best Practices
> Source: [Microsoft ā Passwordless connections for Azure services](https://learn.microsoft.com/azure/developer/intro/passwordless-overview) and [Azure Identity client libraries](https://learn.microsoft.com/dotnet/azure/sdk/authentication/).
## Golden Rule
Use **managed identities** and **Azure RBAC** in production. Reserve `DefaultAzureCredential` for **local development only**.
## Authentication by Environment
| Environment | Recommended Credential | Why |
|---|---|---|
| **Production (Azure-hosted)** | `ManagedIdentityCredential` (system- or user-assigned) | No secrets to manage; auto-rotated by Azure |
| **Production (on-premises)** | `ClientCertificateCredential` or `WorkloadIdentityCredential` | Deterministic; no fallback chain overhead |
| **CI/CD pipelines** | `AzurePipelinesCredential` / `WorkloadIdentityCredential` | Scoped to pipeline identity |
| **Local development** | `DefaultAzureCredential` | Chains CLI, PowerShell, and VS Code credentials for convenience |
## Why Not `DefaultAzureCredential` in Production?
1. **Unpredictable fallback chain** ā walks through multiple credential types, adding latency and making failures harder to diagnose.
2. **Broad surface area** ā checks environment variables, CLI tokens, and other sources that should not exist in production.
3. **Non-deterministic** ā which credential actually authenticates depends on the environment, making behavior inconsistent across deployments.
4. **Performance** ā each failed credential attempt adds network round-trips before falling back to the next.
## Production Patterns
### .NET
```csharp
using Azure.Identity;
var credential = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"
? new DefaultAzureCredential() // local dev ā uses CLI/VS credentials
: new ManagedIdentityCredential(); // production ā deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredential("<client-id>")
```
### TypeScript / JavaScript
```typescript
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
const credential = process.env.NODE_ENV === "development"
? new DefaultAzureCredential() // local dev ā uses CLI/VS credentials
: new ManagedIdentityCredential(); // production ā deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredential("<client-id>")
```
### Python
```python
import os
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
credential = (
DefaultAzureCredential() # local dev ā uses CLI/VS credentials
if os.getenv("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"
else ManagedIdentityCredential() # production ā deterministic, no fallback chain
)
# For user-assigned identity: ManagedIdentityCredential(client_id="<client-id>")
```
### Java
```java
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.identity.ManagedIdentityCredentialBuilder;
var credential = "Development".equals(System.getenv("AZURE_FUNCTIONS_ENVIRONMENT"))
? new DefaultAzureCredentialBuilder().build() // local dev ā uses CLI/VS credentials
: new ManagedIdentityCredentialBuilder().build(); // production ā deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredentialBuilder().clientId("<client-id>").build()
```
## Local Development Setup
`DefaultAzureCredential` is ideal for local dev because it automatically picks up credentials from developer tools:
1. **Azure CLI** ā `az login`
2. **Azure Developer CLI** ā `azd auth login`
3. **Azure PowerShell** ā `Connect-AzAccount`
4. **Visual Studio / VS Code** ā sign in via Azure extension
```typescript
import { DefaultAzureCredential } from "@azure/identity";
// Local development only ā uses CLI/PowerShell/VS Code credentials
const credential = new DefaultAzureCredential();
```
## Environment-Aware Pattern
Detect the runtime environment and select the appropriate credential. The key principle: use `DefaultAzureCredential` only when running locally, and a specific credential in production.
> **Tip:** Azure Functions sets `AZURE_FUNCTIONS_ENVIRONMENT` to `"Development"` when running locally. For App Service or containers, use any environment variable you control (e.g. `NODE_ENV`, `ASPNETCORE_ENVIRONMENT`).
```typescript
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
function getCredential() {
if (process.env.NODE_ENV === "development") {
return new DefaultAzureCredential(); // picks up az login / VS Code creds
}
return process.env.AZURE_CLIENT_ID
? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID) // user-assigned
: new ManagedIdentityCredential(); // system-assigned
}
```
## Security Checklist
- [ ] Use managed identity for all Azure-hosted apps
- [ ] Never hardcode credentials, connection strings, or keys
- [ ] Apply least-privilege RBAC roles at the narrowest scope
- [ ] Use `ManagedIdentityCredential` (not `DefaultAzureCredential`) in production
- [ ] Store any required secrets in Azure Key Vault
- [ ] Rotate secrets and certificates on a schedule
- [ ] Enable Microsoft Defender for Cloud on production resources
## Further Reading
- [Passwordless connections overview](https://learn.microsoft.com/azure/developer/intro/passwordless-overview)
- [Managed identities overview](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview)
- [Azure RBAC overview](https://learn.microsoft.com/azure/role-based-access-control/overview)
- [.NET authentication guide](https://learn.microsoft.com/dotnet/azure/sdk/authentication/)
- [Python identity library](https://learn.microsoft.com/python/api/overview/azure/identity-readme)
- [JavaScript identity library](https://learn.microsoft.com/javascript/api/overview/azure/identity-readme)
- [Java identity library](https://learn.microsoft.com/java/api/overview/azure/identity-readme)
azure-context.md 5.9 KB
# Azure Context (Subscription & Location)
Detect and confirm Azure subscription and location before generating artifacts. Run region capacity check for customer selected location
---
## Step 1: Check for Existing AZD Environment
If the project already uses AZD, check for an existing environment with values already set:
```bash
azd env list
```
**If an environment is selected** (marked with `*`), check its values:
```bash
azd env get-values
```
If `AZURE_SUBSCRIPTION_ID` and `AZURE_LOCATION` are already set, use `ask_user` to confirm reuse:
```
Question: "I found an existing AZD environment with these settings. Would you like to continue with them?"
Environment: {env-name}
Subscription: {subscription-name} ({subscription-id})
Location: {location}
Choices: [
"Yes, use these settings (Recommended)",
"No, let me choose different settings"
]
```
If user confirms ā skip to **Record in Plan**. Otherwise ā continue to Step 2.
---
## Step 2: Detect Defaults
Check for user-configured defaults:
```bash
azd config get defaults
```
Returns JSON with any configured defaults:
```json
{
"subscription": "25fd0362-aa79-488b-b37b-d6e892009fdf",
"location": "eastus2"
}
```
Use these as **recommended** values if present.
If no defaults, fall back to az CLI:
```bash
az account show --query "{name:name, id:id}" -o json
```
## Step 3: Confirm Subscription with User
Use `ask_user` with the **actual subscription name and ID**:
ā
**Correct:**
```
Question: "Which Azure subscription would you like to deploy to?"
Choices: [
"Use current: jongdevdiv (25fd0362-aa79-488b-b37b-d6e892009fdf) (Recommended)",
"Let me specify a different subscription"
]
```
ā **Wrong** (never do this):
```
Choices: [
"Use default subscription", // ā Does not show actual name
"Let me specify"
]
```
If user wants a different subscription:
```bash
az account list --output table
```
---
## Step 4: Confirm Location with User
1. Consult [Region Availability](region-availability.md) for services with limited availability
2. Present only regions that support ALL selected services
3. Use `ask_user`:
4. After customer selected region, do provisioning limit check, consult [Resource Limits and Quotas](resources-limits-quotas.md). For this also invoke azure-quotas
```
Question: "Which Azure region would you like to deploy to?"
Based on your architecture ({list services}), these regions support all services:
Choices: [
"eastus2 (Recommended)",
"westus2",
"westeurope"
]
```
ā ļø Do NOT include regions that don't support all services ā deployment will fail.
---
## Step 5: Check Resource Provisioning Limits
1. **List resource types and quantities** that will be deployed from the planned architecture (e.g., 2x Standard D4s v3 VMs, 1x VNet, 3x Storage Accounts)
2. **Determine limits for each resource type** using the user-selected subscription and region:
- Reference [./resources-limits-quotas.md](./resources-limits-quotas.md) for documented limits
- Use **azure-quotas** skill to check current quotas and usage for the selected subscription and region
- If `az quota list` returns `BadRequest` error, the resource provider doesn't support quota API
3. **For resources that don't support quota API** (e.g., Microsoft.DocumentDB, or when you get `BadRequest` from `az quota list`):
- Invoke **azure-resource-lookup** skill to count existing deployments of that resource type in the selected subscription and region
- Use the count to calculate: `Total After Deployment = Current Count + Planned Deployment`
- Reference [Azure service limits documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits) for the limit value
- Document in provisioning checklist as "Fetched from: azure-resource-lookup + Official docs"
4. **Validate deployment capacity**:
- Compare planned deployment quantities against available quota (limit - current usage)
- If **insufficient capacity** is found, notify the customer and return to **Step 4** to select a different region
- Use **azure-quotas** skill to compare capacity across multiple regions and recommend alternatives
## Record in Plan
After confirmation, record in `.azure/deployment-plan.md`:
```markdown
## Azure Context
- **Subscription**: jongdevdiv (25fd0362-aa79-488b-b37b-d6e892009fdf)
- **Location**: eastus2
```
---
## Step 6: Apply to AZD Environment
> **ā CRITICAL for Aspire and azd projects**: After user confirms subscription and location, you **MUST** set these values in the azd environment immediately after running `azd init` or `azd env new`. Always use `--no-prompt` with these commands to prevent interactive prompts from blocking execution.
>
> **DO NOT** wait until validation or deployment. The Azure CLI and azd maintain separate configuration contexts.
**For Aspire projects using `azd init --from-code`:**
```bash
# 1. Run azd init
azd init --from-code -e <environment-name> --no-prompt
# 2. IMMEDIATELY set the user-confirmed subscription
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
# 3. Set the location
azd env set AZURE_LOCATION <location>
# 4. Verify
azd env get-values
```
**For non-Aspire projects using `azd env new`:**
```bash
# 1. Create environment
azd env new <environment-name> --no-prompt
# 2. IMMEDIATELY set the user-confirmed subscription
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
# 3. Set the location
azd env set AZURE_LOCATION <location>
# 4. Verify
azd env get-values
```
**Why this is critical:**
- `az account show` returns the Azure CLI's default subscription
- `azd` maintains its own configuration with potentially different defaults
- If you don't set `AZURE_SUBSCRIPTION_ID` explicitly, azd will use its own default
- This can result in deploying to the wrong subscription despite user confirmation
functional-verification.md 3.1 KB
# Functional Verification
Verify that the application works correctly ā both UI and backend ā before proceeding to validation and deployment. This step prevents deploying broken or incomplete functionality to Azure.
## When to Verify
After generating all artifacts (code, infrastructure, configuration) and applying security hardening ā but **before** marking the plan as `Ready for Validation`.
## Verification Checklist
Use `ask_user` to confirm functional testing with the user:
```
"Before we proceed to deploy, would you like to verify the app works as expected?
We can test both the UI and backend to catch issues before they reach Azure."
```
### Backend Verification
| Check | How |
|-------|-----|
| **App starts without errors** | Run the app and confirm no startup crashes or missing dependencies |
| **API endpoints respond** | Test core routes (e.g., `curl` health, list, create endpoints) |
| **Data operations work** | Verify CRUD operations against storage, database, or other services |
| **Authentication flows** | Confirm auth works (tokens, managed identity fallback, login/logout) |
| **Error handling** | Verify error responses are meaningful (not unhandled exceptions) |
### UI Verification
| Check | How |
|-------|-----|
| **Page loads** | Open the app in a browser and confirm the UI renders |
| **Interactive elements work** | Test buttons, forms, file inputs, navigation links |
| **Data displays correctly** | Verify lists, images, and dynamic content render from the backend |
| **User workflows complete** | Walk through the core user journey end-to-end (e.g., upload ā view ā delete) |
## Decision Tree
```
App artifacts generated?
āāā Yes ā Ask user: "Would you like to verify functionality?"
ā āāā User says yes
ā ā āāā App can run locally? ā Run locally, verify backend + UI
ā ā āāā API-only / no UI? ā Test endpoints with curl or similar
ā ā āāā Static site? ā Open in browser, verify rendering
ā ā Then:
ā ā āāā Works ā Proceed to Update Plan (step 6)
ā ā āāā Issues found ā Fix issues, re-test
ā āāā User says no / skip ā Proceed to Update Plan (step 6)
āāā No ā Go back to Generate Artifacts (step 3)
```
## Running Locally
For apps that can run locally, help the user start the app based on the detected runtime:
| Runtime | Command | Notes |
|---------|---------|-------|
| Node.js | `npm install && npm start` | Set `PORT=3000` if not configured |
| Python | `pip install -r requirements.txt && python app.py` | Use virtual environment |
| .NET | `dotnet run` | Check `launchSettings.json` for port |
| Java | `mvn spring-boot:run` or `gradle bootRun` | Check `application.properties` |
> ā ļø **Warning:** For apps using Azure services (e.g., Blob Storage, Cosmos DB), local testing requires the user to be authenticated via `az login` with sufficient RBAC roles, or to have local emulators configured (e.g., Azurite for Storage).
## Record in Plan
After functional verification, add a note to `.azure/deployment-plan.md`:
```markdown
## Functional Verification
- Status: Verified / Skipped
- Backend: Tested / Not applicable
- UI: Tested / Not applicable
- Notes: <any issues found and resolved>
```
generate.md 5.3 KB
# Artifact Generation
Generate infrastructure and configuration files based on selected recipe.
## ā CRITICAL: Check for .NET Aspire Projects FIRST
**MANDATORY: Before generating any files, detect .NET Aspire projects:**
```bash
# Method 1: Find AppHost project files
find . -name "*.AppHost.csproj" -o -name "*AppHost.csproj"
# Method 2: Search for Aspire packages
grep -r "Aspire\.Hosting\|Aspire\.AppHost\.Sdk" . --include="*.csproj"
```
**If Aspire is detected:**
1. ā **STOP** - Do NOT manually create `azure.yaml`
2. ā **STOP** - Do NOT manually create `infra/` files
3. ā
**USE** - `azd init --from-code -e <env-name>` instead
4. š **READ** - [aspire.md](aspire.md) and [recipes/azd/aspire.md](recipes/azd/aspire.md) for complete guidance
**Why this is critical:**
- Aspire AppHost auto-generates infrastructure from code
- Manual `azure.yaml` without `services` section causes "infra\main.bicep not found" error
- `azd init --from-code` correctly detects AppHost and generates proper configuration
> ā ļø **Manually creating azure.yaml for Aspire projects is the most common deployment failure.** Always use `azd init --from-code`.
## Check for Other Special Patterns
After verifying the project is NOT Aspire, check for these patterns:
| Pattern | Detection | Action |
|---------|-----------|--------|
| **Complex existing codebase** | Multiple services, existing structure | Consider `azd init --from-code` |
| **Existing azure.yaml** | File already present | MODIFY mode - update existing config |
> **CRITICAL:** After running `azd init --from-code`, you **MUST** immediately set the user-confirmed subscription with `azd env set AZURE_SUBSCRIPTION_ID <id>`. Do NOT skip this step. See [aspire.md](aspire.md) Step 3 for the complete sequence.
## CRITICAL: Research Must Be Complete
**DO NOT generate any files without first completing the [Research Components](research.md) step.**
The research step loads service-specific references and invokes related skills to gather best practices. Apply all research findings to generated artifacts.
## Research Checklist
1. ā
Completed [Research Components](research.md) step
2. ā
Loaded all relevant `services/*.md` references
3. ā
Invoked related skills for specialized guidance
4. ā
Documented findings in `.azure/deployment-plan.md`
## Generation Order
| Order | Artifact | Notes |
|-------|----------|-------|
| 1 | Application config (azure.yaml) | AZD onlyādefines services and hosting |
| 2 | Application code scaffolding | Entry points, health endpoints, config |
| 3 | Dockerfiles | If containerized |
| 4 | Infrastructure (Bicep/Terraform) | IaC templates in `./infra/` |
| 5 | CI/CD pipelines | If requested |
## Recipe-Specific Generation
Load the appropriate recipe for detailed generation steps:
| Recipe | Guide |
|--------|-------|
| AZD | [AZD Recipe](recipes/azd/README.md) |
| AZCLI | [AZCLI Recipe](recipes/azcli/README.md) |
| Bicep | [Bicep Recipe](recipes/bicep/README.md) |
| Terraform | [Terraform Recipe](recipes/terraform/README.md) |
## Common Standards
### File Structure
```
project-root/
āāā .azure/
ā āāā deployment-plan.md
āāā infra/
ā āāā main.bicep (or main.tf)
ā āāā modules/
āāā src/
ā āāā <component>/
ā āāā Dockerfile
āāā azure.yaml (AZD only)
```
### Directory Creation
> ā ļø **Warning:** The `create` tool fails with `Parent directory does not exist` when intermediate directories are missing. Always create the full directory tree before writing files.
**Before creating nested files** (e.g., `src/frontend/src/App.jsx`), create all parent directories first:
```bash
mkdir -p src/frontend/src src/api
```
- Use **absolute paths** in `mkdir -p` when the working directory may differ from the project root
- Create directories for **all components** in a single command before writing any files
- Do **not** rely on the `create` tool to create parent directories ā it will not
### Security Requirements
- No hardcoded secrets
- Use Key Vault for sensitive values
- Managed Identity for service auth
- HTTPS only, TLS 1.2+
- SQL Server Bicep MUST use Entra-only auth ā omit `administratorLogin` and `administratorLoginPassword` entirely, including from conditional/ternary branches (see [services/sql-database/bicep.md](services/sql-database/bicep.md)). These property names must not appear anywhere in a generated `.bicep` file.
- **SQL + Managed Identity: MUST add postprovision hook** ā ARM role assignments only grant control-plane access; you MUST also generate `scripts/grant-sql-access.sh` + `.ps1` and add a `postprovision` hook in `azure.yaml` to run T-SQL grants. See [services/sql-database/bicep.md](services/sql-database/bicep.md).
- **App Service Bicep: MUST include `azd-service-name` tag** ā Every App Service `Microsoft.Web/sites` resource MUST have `tags: union(tags, { 'azd-service-name': serviceName })`. Without this tag, `azd deploy` cannot locate the resource. See [services/app-service/bicep.md](services/app-service/bicep.md).
### Runtime Configuration
Apply language-specific production settings for containerized apps:
| Runtime | Reference |
|---------|-----------|
| Node.js/Express | [runtimes/nodejs.md](runtimes/nodejs.md) |
## After Generation
1. Update `.azure/deployment-plan.md` with generated file list
2. Run validation checks
3. Proceed to **azure-validate** skill
global-rules.md 2.0 KB
# Global Rules
> **MANDATORY** ā These rules apply to ALL skills. Violations are unacceptable.
## Rule 1: Destructive Actions Require User Confirmation
ā **ALWAYS use `ask_user`** before ANY destructive action.
### What is Destructive?
| Category | Examples |
|----------|----------|
| **Delete** | `az group delete`, `azd down`, `rm -rf`, delete resource |
| **Overwrite** | Replace existing files, overwrite config, reset settings |
| **Irreversible** | Purge Key Vault, delete storage account, drop database |
| **Cost Impact** | Provision expensive resources, scale up significantly |
| **Security** | Expose secrets, change access policies, modify RBAC |
### How to Confirm
```
ask_user(
question: "This will permanently delete resource group 'rg-myapp'. Continue?",
choices: ["Yes, delete it", "No, cancel"]
)
```
### No Exceptions
- Do NOT assume user wants to delete/overwrite
- Do NOT proceed based on "the user asked to deploy" (deploy ā delete old)
- Do NOT batch destructive actions without individual confirmation
- ā Do NOT delete user project or workspace directories (e.g., `rm -rf <project-dir>`) even when adding features, converting, or migrating ā use MODIFY mode to edit existing files instead. File deletions within a project (e.g., removing build artifacts or temp files) are permitted when appropriate.
- ā `azd init -t <template>` (and any `azd init` command with a template argument) is for NEW projects only ā run it **only** in an empty/new directory. If the user explicitly requests re-initialization of an existing project, create a separate new directory, run the template there, and then migrate changes into the existing project with user-confirmed edits. Never run `azd init -t` directly in a non-empty existing workspace. `azd init` without a template argument may be used in existing workspaces when appropriate.
---
## Rule 2: Never Assume Subscription or Location
ā **ALWAYS use `ask_user`** to confirm:
- Azure subscription (show actual name and ID)
- Azure region/location
plan-template.md 10.2 KB
# Plan Template
Create `.azure/deployment-plan.md` using this template. This file is **mandatory** and serves as the source of truth for the entire workflow.
## ā BLOCKING REQUIREMENTS
1. You **MUST** create this plan file BEFORE generating any code, infrastructure, or configuration.
2. You **MUST** complete Step 6 Phase 2 (Provisioning Limit Checklist) with NO "_TBD_" entries remaining before presenting the plan to the user.
3. Present the plan to the user and get approval before proceeding to execution.
4. You **MUST NOT** skip any part of the plan.
---
## Template
```markdown
# Azure Deployment Plan
> **Status:** Planning | Approved | Executing | Ready for Validation | Validated | Deployed
Generated: {timestamp}
---
## 1. Project Overview
**Goal:** {what the user wants to build/deploy}
**Path:** New Project | Add Components | Modernize Existing
---
## 2. Requirements
| Attribute | Value |
|-----------|-------|
| Classification | POC / Development / Production |
| Scale | Small / Medium / Large |
| Budget | Cost-Optimized / Balanced / Performance |
| **Subscription** | {subscription-name-or-id} ā ļø MUST confirm with user |
| **Location** | {azure-region} ā ļø MUST confirm with user |
---
## 3. Components Detected
| Component | Type | Technology | Path |
|-----------|------|------------|------|
| {name} | Frontend / API / Worker | {stack} | {path} |
---
## 4. Recipe Selection
**Selected:** AZD / AZCLI / Bicep / Terraform
**Rationale:** {why this recipe was chosen}
---
## 5. Architecture
**Stack:** Containers / Serverless / App Service
### Service Mapping
| Component | Azure Service | SKU |
|-----------|---------------|-----|
| {component} | {azure-service} | {sku} |
### Supporting Services
| Service | Purpose |
|---------|---------|
| Log Analytics | Centralized logging |
| Application Insights | Monitoring & APM |
| Key Vault | Secrets management |
| Managed Identity | Service-to-service auth |
---
## 6. Provisioning Limit Checklist
**Purpose:** Validate that the selected subscription and region have sufficient quota/capacity for all resources to be deployed.
> **ā ļø REQUIRED:** This is a **TWO-PHASE** process. Complete both phases before proceeding.
### Phase 1: Prepare Resource Inventory
List all resources to be deployed with their types and quantities. Leave quota/limit columns empty.
| Resource Type | Number to Deploy | Total After Deployment | Limit/Quota | Notes |
|---------------|------------------|------------------------|-------------|-------|
| {ARM-resource-type} | {count} | _To be filled in Phase 2_ | _To be filled in Phase 2_ | _To be filled in Phase 2_ |
**Example format:**
| Resource Type | Number to Deploy | Total After Deployment | Limit/Quota | Notes |
|---------------|------------------|------------------------|-------------|-------|
| Microsoft.App/managedEnvironments | 1 | _TBD_ | _TBD_ | _TBD_ |
| Microsoft.Compute/virtualMachines (Standard_D4s_v3) | 3 | _TBD_ | _TBD_ | _TBD_ |
| Microsoft.Network/publicIPAddresses | 2 | _TBD_ | _TBD_ | _TBD_ |
| Microsoft.DocumentDB/databaseAccounts | 1 | _TBD_ | _TBD_ | _TBD_ |
| Microsoft.Storage/storageAccounts | 2 | _TBD_ | _TBD_ | _TBD_ |
### Phase 2: Fetch Quotas and Validate Capacity
**Action:** **MUST invoke azure-quotas skill first** to populate the remaining columns with actual quota data using Azure quota CLI. Only use fallback methods if quota CLI is not supported.
> **ā ļø IMPORTANT:** Process **ONE resource type at a time**. Do NOT try to apply all steps to all resources at once. Complete steps 1-7 for the first resource, then move to the next resource, and so on.
For each resource type:
1. **Check if quota CLI is supported** - Run `az quota list --scope /subscriptions/{subscription-id}/providers/{ProviderNamespace}/locations/{region}` to verify the provider is supported. If you encounter issues or need help finding the correct resource name, invoke the azure-quotas skill for troubleshooting.
2. **Get current usage and limit**:
- **If quota CLI is supported**:
- Get limit: `az quota show --resource-name {quota-resource-name} --scope /subscriptions/{subscription-id}/providers/{ProviderNamespace}/locations/{region}`
- Get current usage: `az quota usage show --resource-name {quota-resource-name} --scope /subscriptions/{subscription-id}/providers/{ProviderNamespace}/locations/{region}`
- **If quota CLI is NOT supported** (returns `BadRequest`):
- Get current usage: `az graph query -q "resources | where type == '{resource-type}' and location == '{location}' | count"` (requires `az extension add --name resource-graph`)
- Get limit: [Azure service limits documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits)
3. **Calculate total** - Add "Number to Deploy" + current usage = "Total After Deployment"
4. **Verify capacity** - Ensure "Total After Deployment" ⤠"Limit/Quota"
5. **Document source** - Note whether data came from "azure-quotas (resource-name)" or "Azure Resource Graph + Official docs"
**Completed example:**
| Resource Type | Number to Deploy | Total After Deployment | Limit/Quota | Notes |
|---------------|------------------|------------------------|-------------|-------|
| Microsoft.App/managedEnvironments | 1 | 1 | 50 | Fetched from: azure-quotas (ManagedEnvironmentCount) |
| Microsoft.Compute/virtualMachines (Standard_D4s_v3) | 3 | 15 | 350 vCPUs | Fetched from: azure-quotas (standardDSv3Family) |
| Microsoft.Network/publicIPAddresses | 2 | 5 | 100 | Fetched from: azure-quotas (PublicIPAddresses) |
| Microsoft.DocumentDB/databaseAccounts | 1 | 1 | 50 per region | Fetched from: Official docs (quota CLI not supported) |
| Microsoft.Storage/storageAccounts | 2 | 8 | 250 per region | Fetched from: Official docs |
**Status:** ā
All resources within limits | ā ļø Near limit (>80%) | ā Insufficient capacity
> **ā CRITICAL:** You **CANNOT** present this plan to the customer if ANY cells contain "_TBD_" or "_To be filled in Phase 2_". Phase 2 **MUST** be completed with actual quota data before user presentation.
**Notes:**
- **MUST use azure-quotas skill first** to check providers via quota CLI (`az quota` commands) - Microsoft.Compute, Microsoft.Network, Microsoft.App, etc.
- Azure quota CLI is **ALWAYS preferred over REST API** for checking quotas
- **ONLY for unsupported providers** (e.g., Microsoft.DocumentDB returns `BadRequest`), use fallback methods: [Azure service limits documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits)
- If any resource exceeds limits, return to Step 2 to select a different region or request quota increase
---
## 7. Execution Checklist
### Phase 1: Planning
- [ ] Analyze workspace
- [ ] Gather requirements
- [ ] Confirm subscription and location with user
- [ ] Prepare resource inventory (Step 6 Phase 1: list resource types and deployment quantities)
- [ ] Fetch quotas and validate capacity (Step 6 Phase 2: invoke azure-quotas skill to use quota CLI)
- [ ] Scan codebase
- [ ] Select recipe
- [ ] Plan architecture
- [ ] **User approved this plan**
### Phase 2: Execution
- [ ] Research components (load references, invoke skills)
- [ ] **ā For Azure Functions: Load composition rules** (`services/functions/templates/selection.md` ā `services/functions/templates/recipes/composition.md`) and use `functions_template_get` MCP tool to list and fetch templates, then write `functionFiles[]` + `projectFiles[]` directly ā NEVER hand-write Bicep/Terraform and use `azd init -t <template>`/`func init`/`func new` as fallback when composing multiple recipes and required templates are not found
- [ ] For other services: Generate infrastructure files following service-specific guidance
- [ ] Apply recipes for integrations (if needed)
- [ ] Generate application configuration
- [ ] Generate Dockerfiles (if containerized)
- [ ] **ā Update plan status to "Ready for Validation"** ā Use the `edit` tool to change the Status line in `.azure/deployment-plan.md`. This step is MANDATORY before invoking azure-validate.
### Phase 3: Validation
- [ ] **PREREQUISITE:** Plan status MUST be "Ready for Validation" (Phase 2 last step)
- [ ] Invoke azure-validate skill
- [ ] All validation checks pass
- [ ] _Replace this with recipe validation steps_
- [ ] Update plan status to "Validated"
- [ ] Record validation proof below
### Phase 4: Deployment
- [ ] Invoke azure-deploy skill
- [ ] Deployment successful
- [ ] Report deployed endpoint URLs
- [ ] Update plan status to "Deployed"
---
## 7. Validation Proof
> **ā REQUIRED**: The azure-validate skill MUST populate this section before setting status to `Validated`. If this section is empty and status is `Validated`, the validation was bypassed improperly.
| Check | Command Run | Result | Timestamp |
|-------|-------------|--------|-----------|
| {check-name} | {actual command executed} | ā
Pass / ā Fail | {timestamp} |
**Validated by:** azure-validate skill
**Validation timestamp:** {timestamp}
---
## 8. Files to Generate
| File | Purpose | Status |
|------|---------|--------|
| `.azure/deployment-plan.md` | This plan | ā
|
| `azure.yaml` | AZD configuration | ā³ |
| `infra/main.bicep` | Infrastructure | ā³ |
| `src/{component}/Dockerfile` | Container build | ā³ |
---
## 9. Next Steps
> Current: {current phase}
1. {next action}
2. {following action}
```
---
## Instructions
1. **Create the plan first** ā Fill in all sections based on analysis
2. **Complete quota validation** ā Ensure Step 6 Phase 2 is completed with NO "_TBD_" entries. **MUST use azure-quotas skill** as the primary method to fetch actual quota/usage data via quota CLI (`az quota` commands) for all resources. Use fallback methods ONLY when provider returns `BadRequest`.
3. **Present to user** ā Show the completed plan and ask for approval. **DO NOT** present if Step 6 contains any "_TBD_" or "_To be filled in Phase 2_" entries.
4. **Update as you go** ā Check off items in the execution checklist
5. **Track status** ā Update the Status field at the top as you progress
The plan is the **single source of truth** for azure-validate and azure-deploy skills.
recipe-selection.md 3.2 KB
# Recipe Selection
Choose the deployment recipe based on project needs and existing tooling.
## ā Special Cases: Detect First
**Before selecting a recipe, check for these special project types:**
| Project Type | Detection | Recipe Selection |
|--------------|-----------|------------------|
| **.NET Aspire** | `*.AppHost.csproj` or `Aspire.Hosting` package | **AZD (auto via `azd init --from-code`)** ā [aspire.md](aspire.md) |
> š” **Tip:** .NET Aspire projects always use AZD recipe with auto-generated configuration. Do not manually select recipe or create artifacts.
## Quick Decision
**Default: AZD** unless specific requirements indicate otherwise.
> š” **Tip:** azd supports both Bicep and Terraform as IaC providers. When Terraform is mentioned for Azure deployment, **default to azd+Terraform** for the best developer experience.
## Decision Criteria
| Choose | When |
|--------|------|
| **AZD (Bicep)** | New projects, multi-service apps, want simplest deployment (`azd up`) |
| **AZD (Terraform)** | **DEFAULT for Terraform** - Want Terraform IaC + azd simplicity, Azure deployment with Terraform |
| **AZCLI** | Existing az scripts, need imperative control, custom pipelines, AKS |
| **Bicep** | IaC-first approach, no CLI wrapper needed, direct ARM deployment |
| **Terraform** | Multi-cloud deployments (non-Azure-first), complex TF workflows incompatible with azd, explicitly requested |
## Auto-Detection
| Found in Workspace | Suggested Recipe |
|--------------------|------------------|
| `azure.yaml` with `infra.provider: terraform` | AZD (Terraform) |
| `azure.yaml` (Bicep or no provider specified) | AZD (Bicep) |
| `*.tf` files (no azure.yaml) | **AZD (Terraform) - DEFAULT** (unless multi-cloud) |
| `infra/*.bicep` (no azure.yaml) | Bicep or AZCLI |
| Existing `az` scripts | AZCLI |
| None | AZD (Bicep) - default |
## Recipe Comparison
| Feature | AZD (Bicep) | AZD (Terraform) | AZCLI | Bicep | Terraform |
|---------|-------------|-----------------|-------|-------|-----------|
| Config file | azure.yaml | azure.yaml + *.tf | scripts | *.bicep | *.tf |
| IaC language | Bicep | Terraform | N/A | Bicep | Terraform |
| Deploy command | `azd up` | `azd up` | `az` commands | `az deployment` | `terraform apply` |
| Dockerfile gen | Auto | Auto | Manual | Manual | Manual |
| Environment mgmt | Built-in | Built-in | Manual | Manual | Workspaces |
| CI/CD gen | Built-in | Built-in | Manual | Manual | Manual |
| Multi-cloud | No | Yes | No | No | Yes |
| Learning curve | Low | Low-Medium | Medium | Medium | Medium |
## Record Selection
Document in `.azure/deployment-plan.md`:
```markdown
## Recipe: AZD (Terraform)
**Rationale:**
- Team has Terraform expertise
- Want multi-cloud IaC flexibility
- But prefer azd's simple deployment workflow
- Multi-service app (API + Web)
```
Or for pure Terraform:
```markdown
## Recipe: Terraform
**Rationale:**
- Multi-cloud deployment (AWS + Azure)
- Complex Terraform modules incompatible with azd conventions
- Existing Terraform CI/CD pipeline
```
## Recipe References
- [AZD Recipe](recipes/azd/README.md)
- [AZCLI Recipe](recipes/azcli/README.md)
- [Bicep Recipe](recipes/bicep/README.md)
- [Terraform Recipe](recipes/terraform/README.md)
region-availability.md 1.7 KB
# Azure Region Availability Index
> **AUTHORITATIVE SOURCE** ā Consult service-specific files BEFORE recommending any region.
>
> Official reference: https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/table
## How to Use
1. Check if your architecture includes any **limited availability** services below
2. If yes ā consult the service-specific file or use the MCP tool to list supported regions with sufficient quota for that service, and only offer regions that support ALL services
3. If all services are "available everywhere" ā offer common regions
## MCP Tools Used
| Tool | Purpose |
|------|---------|
| `mcp_azure_mcp_quota` | Check Azure region availability and quota by setting `command` to `quota_usage_check` or `quota_region_availability_list` |
---
## Services with LIMITED Region Availability
| Service | Availability | Details |
|---------|--------------|---------|
| Static Web Apps | Limited (5 regions) | [Region Details](services/static-web-apps/region-availability.md) |
| Azure AI Foundry | Very limited (by model) | [Region Details](services/foundry/region-availability.md) |
| Azure Kubernetes Service (AKS) | Limited in some regions | To get available regions with enough quota, use `mcp_azure_mcp_quota` tool. |
| Azure Database for PostgreSQL | Limited in some regions | To get available regions with enough quota, use `mcp_azure_mcp_quota` tool. |
---
## Services Available in Most Regions
These services are available in all major Azure regions ā no special consideration needed:
- Container Apps
- Azure Functions
- App Service
- Azure SQL Database
- Cosmos DB
- Key Vault
- Storage Account
- Service Bus
- Event Grid
- Application Insights / Log Analytics
requirements.md 2.6 KB
# Requirements Gathering
Collect project requirements through conversation before making architecture decisions.
## Categories
### 1. Classification
| Type | Description | Implications |
|------|-------------|--------------|
| POC | Proof of concept | Minimal infra, cost-optimized |
| Development | Internal tooling | Balanced, team-focused |
| Production | Customer-facing | Full reliability, monitoring |
### 2. Scale
| Scale | Users | Considerations |
|-------|-------|----------------|
| Small | <1K | Single region, basic SKUs |
| Medium | 1K-100K | Auto-scaling, multi-zone |
| Large | 100K+ | Multi-region, premium SKUs |
### 3. Budget
| Profile | Focus |
|---------|-------|
| Cost-Optimized | Minimize spend, lower SKUs |
| Balanced | Value for money, standard SKUs |
| Performance | Maximum capability, premium SKUs |
### 4. Compliance
| Requirement | Impact |
|-------------|--------|
| Data residency | Region constraints |
| Industry regulations | Security controls |
| Internal policies | Approval workflows |
### 5. Subscription Policies
After the user confirms a subscription, query Azure Policy assignments to discover enforcement constraints before making architecture decisions.
```
mcp_azure_mcp_policy(command: "policy_assignment_list", subscription: "<subscriptionId>")
```
| Policy Constraint | Impact |
|-------------------|--------|
| Blocked resource types or SKUs | Exclude from architecture |
| Required tags | Add to all Bicep/Terraform resources |
| Allowed regions | Restrict location choices |
| Network restrictions (e.g., no public endpoints) | Adjust networking and access patterns |
| Storage policies (e.g., deny shared key access) | Use policy-compliant auth |
| Naming conventions | Apply to resource naming |
> ā ļø **Warning:** Skipping this step can cause deployment failures when Azure Policy denies resource creation. Checking policies here prevents wasted work in architecture and generation phases.
Record discovered policy constraints in `.azure/deployment-plan.md` under a **Policy Constraints** section so they feed into architecture decisions.
## Gather via Conversation
Use `ask_user` tool to confirm each of these with the user:
1. Project classification (POC/Dev/Prod)
2. Expected scale
3. Budget constraints
4. Compliance requirements (including data residency preferences)
5. Subscription and policy constraints (confirm subscription, then check policies automatically)
6. Architecture preferences (if any)
## Document in Plan
Record all requirements in `.azure/deployment-plan.md` immediately after gathering.
research.md 8.9 KB
# Research Components
After architecture planning, research each selected component to gather best practices before generating artifacts.
## Process
1. **Identify Components** ā List all Azure services from architecture plan
2. **Load Service References** ā For each service, load `services/<service>/README.md` first, then specific references as needed
3. **Check Resource Naming Rules** ā For each resource type, check [resource naming rules](https://learn.microsoft.com/azure/azure-resource-manager/management/resource-name-rules) for valid characters, length limits, and uniqueness scopes
4. **Load Recipe References** ā Load the selected recipe's guide (e.g., [AZD](recipes/azd/README.md)) and its IAC rules, MCP best practices, and schema tools listed in its "Before Generation" table
5. **Check Region Availability** ā Verify all selected services are available in the target region per [region-availability.md](region-availability.md)
6. **Check Provisioning Limits** ā Invoke **azure-quotas** skill to validate that the selected subscription and region have sufficient quota/capacity for all planned resources. Complete [Step 6 of the plan template](plan-template.md#6-provisioning-limit-checklist) in two phases: (1) prepare resource inventory with deployment quantities, (2) fetch quotas and validate capacity using azure-quotas skill
7. **Load Runtime References** ā For containerized apps, load language-specific production settings (e.g., [Node.js](runtimes/nodejs.md))
8. **Invoke Related Skills** ā For deeper guidance, invoke mapped skills from the table below
9. **Document Findings** ā Record key insights in `.azure/deployment-plan.md`
## Service-to-Reference Mapping
| Azure Service | Reference | Related Skills |
|---------------|-----------|----------------|
| **Hosting** | | |
| Container Apps | [Container Apps](services/container-apps/README.md) | `azure-diagnostics`, `azure-observability`, `azure-nodejs-production` |
| App Service | [App Service](services/app-service/README.md) | `azure-diagnostics`, `azure-observability`, `azure-nodejs-production` |
| Azure Functions | [Functions](services/functions/README.md) | ā |
| Static Web Apps | [Static Web Apps](services/static-web-apps/README.md) | ā |
| AKS | [AKS](services/aks/README.md) | `azure-networking` |
| **Data** | | |
| Azure SQL | [SQL Database](services/sql-database/README.md) | ā |
| Cosmos DB | [Cosmos DB](services/cosmos-db/README.md) | ā |
| PostgreSQL | ā | ā |
| Storage (Blob/Files) | [Storage](services/storage/README.md) | `azure-storage` |
| **Messaging** | | |
| Service Bus | [Service Bus](services/service-bus/README.md) | ā |
| Event Grid | [Event Grid](services/event-grid/README.md) | ā |
| Event Hubs | ā | ā |
| **Integration** | | |
| API Management | [APIM](apim.md) | `azure-aigateway` (invoke for AI Gateway policies) |
| Logic Apps | [Logic Apps](services/logic-apps/README.md) | ā |
| **Workflow & Orchestration** | | |
| Durable Functions | [Durable Functions](services/functions/durable.md), [Durable Task Scheduler](services/durable-task-scheduler/README.md) | ā |
| Durable Task Scheduler | [Durable Task Scheduler](services/durable-task-scheduler/README.md) | ā |
| **Security & Identity** | | |
| Key Vault | [Key Vault](services/key-vault/README.md) | `azure-keyvault-expiration-audit` |
| Managed Identity | ā | `entra-app-registration` |
| **Observability** | | |
| Application Insights | [App Insights](services/app-insights/README.md) | `appinsights-instrumentation` (invoke for instrumentation) |
| Log Analytics | ā | `azure-observability`, `azure-kusto` |
| **AI Services** | | |
| Azure OpenAI | [Foundry](services/foundry/README.md) | `microsoft-foundry` (invoke for AI patterns and model guidance) |
| AI Search | ā | `azure-ai` (invoke for search configuration) |
## Research Instructions
### Step 1: Load Internal References (Progressive Loading)
For each selected service, load the README.md first, then load specific files as needed:
```
Selected: Container Apps, Cosmos DB, Key Vault
ā Load: services/container-apps/README.md (overview)
ā If need Bicep: services/container-apps/bicep.md
ā If need Terraform: services/container-apps/terraform.md
ā If need scaling: services/container-apps/scaling.md
ā If need health probes: services/container-apps/health-probes.md
ā Load: services/cosmos-db/README.md (overview)
ā If need partitioning: services/cosmos-db/partitioning.md
ā If need SDK: services/cosmos-db/sdk.md
ā Load: services/key-vault/README.md (overview)
ā If need SDK: services/key-vault/sdk.md
```
### Step 2: Invoke Related Skills (When Deeper Guidance Needed)
Invoke related skills for specialized scenarios:
| Scenario | Action |
|----------|--------|
| **Using GitHub Copilot SDK** | **Invoke `azure-hosted-copilot-sdk`** (scaffold + config, then resume azure-prepare) |
| Using Azure Functions | Stay in **azure-prepare** ā load [selection.md](services/functions/templates/selection.md) ā Follow [composition.md](services/functions/templates/recipes/composition.md) algorithm |
| PostgreSQL with passwordless auth | Handle directly without a separate skill |
| Need detailed security hardening | Handle directly with service-specific security guidance and platform best practices |
| Setting up App Insights instrumentation | `appinsights-instrumentation` |
| Building AI applications | `microsoft-foundry` |
| Cost-sensitive deployment | `azure-cost` |
**Skill/Reference Invocation Pattern:**
For **Azure Functions**:
1. Load: [selection.md](services/functions/templates/selection.md) (decision tree)
2. Follow: [composition.md](services/functions/templates/recipes/composition.md) (algorithm)
3. Result: Base template + recipe composition (never synthesize IaC)
For **PostgreSQL**:
1. Handle passwordless auth patterns directly without a separate skill
### Step 3: Document in Plan
Add research findings to `.azure/deployment-plan.md` under a `## Research Summary` section with source references and key insights per component.
## Common Research Patterns
### Web Application + API + Database (Cosmos DB)
1. Load: [services/container-apps/README.md](services/container-apps/README.md) ā [bicep.md](services/container-apps/bicep.md) or [terraform.md](services/container-apps/terraform.md), [scaling.md](services/container-apps/scaling.md)
2. Load: [services/cosmos-db/README.md](services/cosmos-db/README.md) ā [partitioning.md](services/cosmos-db/partitioning.md)
3. Load: [services/key-vault/README.md](services/key-vault/README.md)
4. Invoke: `azure-observability` (monitoring setup)
5. Review service-specific security guidance directly before generation
### Container Apps + API + SQL Database
1. Load: [services/container-apps/README.md](services/container-apps/README.md) ā [bicep.md](services/container-apps/bicep.md) or [terraform.md](services/container-apps/terraform.md), [scaling.md](services/container-apps/scaling.md)
2. Load: [services/sql-database/README.md](services/sql-database/README.md) ā [bicep.md](services/sql-database/bicep.md), [auth.md](services/sql-database/auth.md)
3. Load: [services/key-vault/README.md](services/key-vault/README.md)
4. Review [auth.md](services/sql-database/auth.md) directly for Entra-only auth configuration
### App Service + API + SQL Database
1. Load: [services/app-service/README.md](services/app-service/README.md) ā [bicep.md](services/app-service/bicep.md)
2. Load: [services/sql-database/README.md](services/sql-database/README.md) ā [bicep.md](services/sql-database/bicep.md), [auth.md](services/sql-database/auth.md)
3. Load: [services/key-vault/README.md](services/key-vault/README.md)
4. Review [auth.md](services/sql-database/auth.md) directly for Entra-only auth configuration
### Serverless Event-Driven
1. Load: [services/functions/README.md](services/functions/README.md) (contains mandatory composition workflow)
2. Load: [services/event-grid/README.md](services/event-grid/README.md) or [services/service-bus/README.md](services/service-bus/README.md) (if using messaging)
3. Load: [services/storage/README.md](services/storage/README.md) (if using queues/blobs)
4. Invoke: `azure-observability` (distributed tracing)
### AI Application
1. Invoke: `microsoft-foundry` (AI patterns and best practices)
2. Load: [services/container-apps/README.md](services/container-apps/README.md) ā [bicep.md](services/container-apps/bicep.md) or [terraform.md](services/container-apps/terraform.md)
3. Load: [services/cosmos-db/README.md](services/cosmos-db/README.md) ā [partitioning.md](services/cosmos-db/partitioning.md) (vector storage)
4. Review Key Vault and Foundry references directly for API key management
### GitHub Copilot SDK Application
1. Invoke: `azure-hosted-copilot-sdk` skill (scaffold, infra, model config)
2. After it completes, resume azure-prepare workflow (validate ā deploy)
## After Research
Proceed to **Generate Artifacts** step with research findings applied.
resources-limits-quotas.md 13.0 KB
# Azure Resource Limits and Quotas
Check Azure resource availability during azure-prepare workflow. Validate after customer selects region.
## Types
1. **Hard Limits** - Fixed constraints that cannot be changed
2. **Quotas** - Subscription limits that can be increased via support request
**CLI First:** Always use `az quota` CLI for quota checks. Provides better error handling and consistent output. "No Limit" in REST/Portal doesn't mean unlimited - verify with service docs.
## Hard Limits
Fixed service constraints (cannot be changed).
**Check via**: `azure__documentation` tool or azure-provisioning-limit skill
**Examples**: Cosmos DB item size (2 MB), Container Apps HTTP timeout (240s), App Service Free tier deployment slots (0)
**Process**:
1. Identify services and resource sizes needed
2. Look up limits in documentation
3. Compare plan vs limits
4. If exceeded: redesign or change tier
## Quotas
Subscription/regional limits that can be increased via support request.
**Check via**: `az quota` CLI (install: `az extension add --name quota`)
**Examples**: AKS clusters (5,000/region), Storage accounts (250/region), Container Apps environments (50/region)
**Key Concept**: No 1:1 mapping between ARM resource types and quota names.
- ARM: `Microsoft.App/managedEnvironments` ā Quota: `ManagedEnvironmentCount`
- ARM: `Microsoft.Compute/virtualMachines` ā Quota: `standardDSv3Family`, `cores`, `virtualMachines`
**Process**:
1. Install extension: `az extension add --name quota`
2. Discover quota names: `az quota list --scope /subscriptions/{id}/providers/{Provider}/locations/{region}`
3. Check usage: `az quota usage show --resource-name {name} --scope ...`
4. Check limit: `az quota show --resource-name {name} --scope ...`
5. Calculate: Available = Limit - Current Usage
6. If exceeded: Request increase via `az quota update`
**Unsupported Providers** (BadRequest error):
Not all providers support quota API. If `az quota list` fails with BadRequest, use fallback:
1. Get current usage:
```bash
# Option A: Azure Resource Graph (recommended)
az extension add --name resource-graph
az graph query -q "resources | where type == '{type}' and location == '{loc}' | count"
# Option B: Resource list
az resource list --subscription "{id}" --resource-type "{Type}" --location "{loc}" | jq 'length'
```
2. Get limit from [service documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits)
3. Calculate: Available = Documented Limit - Current Usage
**Known Support Status**:
- ā Microsoft.DocumentDB (Cosmos DB)
- ā
Microsoft.Compute, Microsoft.Network, Microsoft.App, Microsoft.Storage, Microsoft.MachineLearningServices
## Workflow
**Phase 1: Identify & Check Hard Limits**
1. Analyze app requirements and select Azure services
2. Determine resource counts, sizes, tiers, throughput
3. Check hard limits via azure-provisioning-limit skill or documentation
4. Validate plan against limits; redesign if needed
**Phase 2: Check Quotas After Region Selection**
1. Get customer subscription and region preference
2. For each service/region, check quota:
- Use `az quota usage list` and `az quota show`
- Calculate available capacity
3. If quota exceeded: request increase or choose different region
**Phase 3: Validate Region**
- Confirm sufficient quota in selected region
- Request increases if needed
- Only proceed after validation complete
## Limit Scopes
| Scope | Example |
|-------|---------|
| Subscription | 50 Cosmos DB accounts (any region) |
| Regional | 250 storage accounts per region |
| Resource | 500 apps per Container Apps environment |
## Service Patterns
| Service | Hard Limits (examples) | Quota Check | Notes |
|---------|------------------------|-------------|-------|
| **Cosmos DB** | Item: 2MB, Partition key: 2KB, Serverless storage: 50GB | ā Not supported. Use Resource Graph + [docs](https://learn.microsoft.com/en-us/azure/cosmos-db/concepts-limits). Default: 50 accounts/region | Query: `az graph query -q "resources \| where type == 'microsoft.documentdb/databaseaccounts' and location == 'eastus' \| count"` |
| **AKS** | Pods/node (Azure CNI): 250, Node pools/cluster: 100 | ā
`az quota` supported | Provider: Microsoft.ContainerService |
| **Storage** | Block blob: 190.7 TiB, Page blob: 8 TiB | ā
Quota: `StorageAccounts` (limit: 250/region) | Provider: Microsoft.Storage |
| **Container Apps** | Revisions/app: 100, HTTP timeout: 240s | ā
Quota: `ManagedEnvironmentCount` (limit: 50/region) | Provider: Microsoft.App |
| **Functions** | Timeout (Consumption): 10 min, Queue msg: 64KB | ā
Check function apps quota | Provider: Microsoft.Web |
## CLI Reference
**Prerequisites**: `az extension add --name quota`
**Discovery**: List quotas to find resource names
```bash
az quota list --scope /subscriptions/{id}/providers/{provider}/locations/{location}
```
**Check Usage**:
```bash
az quota usage show --resource-name {quota-name} --scope /subscriptions/{id}/providers/{provider}/locations/{location}
```
**Check Limit**:
```bash
az quota show --resource-name {quota-name} --scope /subscriptions/{id}/providers/{provider}/locations/{location}
```
**Request Increase**:
```bash
az quota update --resource-name {quota-name} --scope /subscriptions/{id}/providers/{provider}/locations/{location} --limit-object value={new-limit} --resource-type {type}
```
## azure-prepare Integration
**When to Check**:
1. After selecting services - Check hard limits
2. After customer selects region - Check quotas
3. Before generating infrastructure code - Validate availability
**Required Steps**:
**Phase 1 - Planning**:
- Select Azure services
- Check hard limits (service documentation)
- Create provisioning limit checklist (leave quota columns as "_TBD_")
**Phase 2 - Execution**:
- Get subscription and region preference
- **Must invoke azure-quotas skill** - Process ONE resource type at a time:
a. Try `az quota list` first (required)
b. If supported: Use `az quota usage show` and `az quota show`
c. If NOT supported (BadRequest): Use Resource Graph + service docs
d. Calculate available capacity
e. Document in checklist (no "_TBD_" entries allowed)
f. If insufficient: Request increase or change region
**Phase 3 - Generate Artifacts**:
- Only proceed after Phase 2 complete (all quotas validated)
## Error Messages
| Error | Type | Action |
|-------|------|--------|
| "Quota exceeded" | Quota | Use azure-quotas to request increase |
| "(BadRequest) Bad request" | Unsupported provider | Use [service limits docs](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits) |
| "Limit exceeded" | Hard Limit | Redesign or change tier |
| "Maximum size exceeded" | Hard Limit | Split data or use alternative storage |
| "Too many requests" | Rate Limit | Implement retry logic or increase tier |
| "Cannot exceed X" | Hard Limit | Stay within limit or use multiple resources |
| "Subscription limit reached" | Quota | Request quota increase using azure-quotas skill |
| "Regional capacity" | Quota | Choose different region or request increase |
## Best Practices
1. **MUST use Azure CLI quota API first**: `az quota` commands are MANDATORY as the primary method for checking quotas - only use fallback methods (REST API, Portal, docs) when quota API returns `BadRequest`
2. **Don't trust "No Limit" values**: If REST API or Portal shows "No Limit" or unlimited, verify with official service documentation - it likely means the quota API doesn't support that resource type, not that capacity is unlimited
3. **Always check after customer selects region**: Validates availability and allows time for quota requests
4. **Use the discovery workflow**: Never assume quota resource names - always run `az quota list` first to discover correct names
5. **Check both usage and limit**: Run `az quota usage show` AND `az quota show` to calculate available capacity
6. **Handle unsupported providers gracefully**: If you get `BadRequest` error, fall back to official documentation (Azure Resource Graph + docs)
7. **Request quota increases proactively**: If selected region lacks capacity, submit request before deployment
8. **Have alternative regions ready**: If quota increase denied, suggest backup regions
9. **Document capacity assumptions**: Note quota availability and source in `.azure/deployment-plan.md`
10. **Design for limits**: Architecture should account for both hard limits and quotas
11. **Monitor usage trends**: Regular quota checks help predict future needs
12. **Use lower environments wisely**: Dev/test environments count against quotas
## Quick Reference Limits
For complete quota checking workflow and commands, invoke the **azure-quotas** skill.
> **Note:** These are typical default limits. Always verify actual quotas using `az quota show` for your specific subscription and region.
Common quotas to check:
### Subscription Level
- Cosmos DB accounts: 50 per region (check via docs - quota API not supported)
- SQL logical servers: 250 per region
- Service Bus namespaces: 100-1,000 (tier dependent)
### Regional Level
- Storage accounts: 250 per region (quota resource name: `StorageAccounts`)
- AKS clusters: 5,000 per region (quota resource name: varies by configuration)
- Container Apps environments: 50 per region (quota resource name: `ManagedEnvironmentCount`)
- Function apps: 200 per region (Consumption)
### Resource Level
- Cosmos DB containers per account: Unlimited (subject to storage)
- Apps per Container Apps environment: 500
- Databases per SQL server: 500
- Queues/topics per Service Bus namespace: 10,000
## Related Documentation
- **azure-quotas skill** - Complete quota checking workflow and CLI commands (invoke the **azure-quotas** skill)
- [Azure subscription limits](https://learn.microsoft.com/azure/azure-resource-manager/management/azure-subscription-service-limits) - Official Microsoft documentation
- [Azure Quotas Overview](https://learn.microsoft.com/en-us/azure/quotas/quotas-overview) - Understanding quotas and limits
- [azure-context.md](azure-context.md) - How to confirm subscription and region
- [architecture.md](architecture.md) - Architecture planning workflow
## Example: Complete Check Workflow
```bash
# Scenario: Deploying app with Cosmos DB, Storage, and Container Apps
# Customer selected region: East US
# 1. Check Hard Limits (from azure-provisioning-limit skill)
# Cosmos DB: Item size max 2 MB ā
# Storage: Blob size max 190.7 TiB ā
# Container Apps: Timeout 240 sec ā
# 2. Get Customer's Region Preference
# Customer: "I prefer East US"
# 3. Check Quotas for Customer's Selected Region (East US)
# 3a. Cosmos DB - NOT SUPPORTED by quota API
az quota list \
--scope /subscriptions/abc-123/providers/Microsoft.DocumentDB/locations/eastus
# Error: (BadRequest) Bad request
# Fallback: Get current usage with Azure Resource Graph
# Install extension first (if needed)
az extension add --name resource-graph
az graph query -q "resources | where type == 'microsoft.documentdb/databaseaccounts' and location == 'eastus' | count"
# Result: 3 database accounts currently deployed
# Or use Azure CLI resource list
az resource list \
--subscription "abc-123" \
--resource-type "Microsoft.DocumentDB/databaseAccounts" \
--location "eastus" | jq 'length'
# Result: 3
# Get limit from documentation: 50 database accounts per region
# Calculate: Available = 50 - 3 = 47 ā
# Document as: "Fetched from: Azure Resource Graph + Official docs"
# 3b. Storage Accounts
# Step 1: Discover resource name
az quota list \
--scope /subscriptions/abc-123/providers/Microsoft.Storage/locations/eastus
# Step 2: Check usage (use discovered name "StorageAccounts")
az quota usage show \
--resource-name StorageAccounts \
--scope /subscriptions/abc-123/providers/Microsoft.Storage/locations/eastus
# Current: 180
# Step 3: Check limit
az quota show \
--resource-name StorageAccounts \
--scope /subscriptions/abc-123/providers/Microsoft.Storage/locations/eastus
# Limit: 250
# Available: 250 - 180 = 70 ā
# 3c. Container Apps
# Step 1: Discover resource name
az quota list \
--scope /subscriptions/abc-123/providers/Microsoft.App/locations/eastus
# Shows: "ManagedEnvironmentCount"
# Step 2: Check usage
az quota usage show \
--resource-name ManagedEnvironmentCount \
--scope /subscriptions/abc-123/providers/Microsoft.App/locations/eastus
# Current: 8
# Step 3: Check limit
az quota show \
--resource-name ManagedEnvironmentCount \
--scope /subscriptions/abc-123/providers/Microsoft.App/locations/eastus
# Limit: 50
# Available: 50 - 8 = 42 ā
# 4. Validate Availability
# ā
All services have sufficient quota in East US
# ā
Proceed with deployment
# Alternative: If quotas were insufficient
# ā Container Apps: 49/50 (only 1 available, need 3)
# Action: Request quota increase
#
# az quota update \
# --resource-name ManagedEnvironmentCount \
# --scope /subscriptions/abc-123/providers/Microsoft.App/locations/eastus \
# --limit-object value=100 \
# --resource-type Microsoft.App/managedEnvironments
```
---
> **Remember**: Checking limits and quotas early prevents deployment failures and ensures smooth infrastructure provisioning.
scan.md 3.6 KB
# Codebase Scan
Analyze workspace to identify components, technologies, and dependencies.
## Detection Patterns
### Languages & Frameworks
| File | Indicates |
|------|-----------|
| `package.json` | Node.js |
| `requirements.txt`, `pyproject.toml` | Python |
| `*.csproj`, `*.sln` | .NET |
| `pom.xml`, `build.gradle` | Java |
| `go.mod` | Go |
### ā Specialized SDK Detection ā Check FIRST
Before classifying components, grep dependency files for SDKs that require a specialized skill:
| Dependency in code | Invoke instead |
|--------------------|----------------|
| `@github/copilot-sdk` Ā· `github-copilot-sdk` Ā· `copilot-sdk-go` Ā· `GitHub.CopilotSdk` | **azure-hosted-copilot-sdk** |
> ā ļø If ANY match is found, **STOP and invoke that skill**. Do NOT continue with azure-prepare ā the skill has tested templates and patterns.
### Component Types
| Pattern | Component Type |
|---------|----------------|
| React/Vue/Angular in package.json | SPA Frontend |
| Only .html/.css/.js files, no package.json | Pure Static Site |
| Express/Fastify/Koa | API Service |
| Flask/FastAPI/Django | API Service |
| Next.js/Nuxt | SSR Web App |
| Celery/Bull/Agenda | Background Worker |
| azure-functions SDK | Azure Function |
| .AppHost.csproj or Aspire.Hosting package | .NET Aspire App |
**Pure Static Site Detection:**
- No package.json, requirements.txt, or build configuration
- Contains only HTML, CSS, JavaScript, and asset files
- No framework dependencies (React, Vue, Angular, etc.)
- ā ļø For pure static sites, do NOT add `language` field to azure.yaml to avoid triggering build steps
### Existing Tooling
| Found | Tooling |
|-------|---------|
| `azure.yaml` | AZD configured |
| `infra/*.bicep` | Bicep IaC |
| `infra/*.tf` | Terraform IaC |
| `Dockerfile` | Containerized |
| `.github/workflows/` | GitHub Actions CI/CD |
| `azure-pipelines.yml` | Azure DevOps CI/CD |
### .NET Aspire Detection
**.NET Aspire projects** are identified by:
- A project ending with `.AppHost.csproj` (e.g., `OrleansVoting.AppHost.csproj`)
- Reference to `Aspire.Hosting` or `Aspire.Hosting.AppHost` package in .csproj files
- Multiple .NET projects in a solution, typically including an AppHost orchestrator
**When Aspire is detected:**
- Use `azd init --from-code -e <environment-name>` instead of manual azure.yaml creation
- The `--from-code` flag automatically detects the AppHost and generates appropriate configuration
- The `-e` flag is **required** for non-interactive environments (agents, CI/CD)
- ā ļø **CRITICAL:** If the AppHost contains `AddAzureFunctionsProject`, you **MUST** add `.WithEnvironment("AzureWebJobsSecretStorageType", "Files")` to the Functions builder chain BEFORE deployment. Without this, Functions will fail at startup with `Secret initialization from Blob storage failed`. See [aspire.md](aspire.md) Step 4b for the complete detection and fix procedure.
- See [aspire.md](aspire.md) for detailed Aspire-specific guidance
## Output
Document findings:
```markdown
## Components
| Component | Type | Technology | Path |
|-----------|------|------------|------|
| api | API Service | Node.js/Express | src/api |
| web | SPA | React | src/web |
| worker | Background | Python | src/worker |
## Dependencies
| Component | Depends On | Type |
|-----------|-----------|------|
| api | PostgreSQL | Database |
| web | api | HTTP |
| worker | Service Bus | Queue |
## Existing Infrastructure
| Item | Status |
|------|--------|
| azure.yaml | Not found |
| infra/ | Not found |
| Dockerfiles | Found: src/api/Dockerfile |
```
security.md 8.4 KB
# Security Hardening
Secure Azure resources following Zero Trust principles.
## Security Principles
1. **Zero Trust** ā Never trust, always verify
2. **Least Privilege** ā Minimum required permissions
3. **Defense in Depth** ā Multiple security layers
4. **Encryption Everywhere** ā At rest and in transit
---
## Security Services
| Service | Use When | MCP Tools | CLI |
|---------|----------|-----------|-----|
| Key Vault | Secrets, keys, certificates | `azure__keyvault` | `az keyvault` |
| Managed Identity | Credential-free authentication | ā | `az identity` |
| RBAC | Role-based access control | `azure__role` | `az role` |
| Entra ID | Identity and access management | ā | `az ad` |
| Defender | Threat protection, security posture | ā | `az security` |
### MCP Tools (Preferred)
When Azure MCP is enabled:
**Key Vault:**
- `azure__keyvault` with command `keyvault_list` ā List Key Vaults
- `azure__keyvault` with command `keyvault_secret_list` ā List secrets
- `azure__keyvault` with command `keyvault_secret_get` ā Get secret value
- `azure__keyvault` with command `keyvault_key_list` ā List keys
- `azure__keyvault` with command `keyvault_certificate_list` ā List certificates
**RBAC:**
- `azure__role` with command `role_assignment_list` ā List role assignments
- `azure__role` with command `role_definition_list` ā List role definitions
### CLI Quick Reference
```bash
# Key Vault
az keyvault list --output table
az keyvault secret list --vault-name VAULT --output table
# RBAC
az role assignment list --output table
# Managed Identity
az identity list --output table
```
---
## Identity and Access
### Checklist
- [ ] Use managed identities (no credentials in code)
- [ ] Enable MFA for all users
- [ ] Apply least privilege RBAC
- [ ] Use Microsoft Entra ID for authentication
- [ ] SQL Server: Entra-only auth ā NEVER generate `administratorLogin` or `administratorLoginPassword` anywhere in Bicep, including inside conditional branches (see [sql-database/auth.md](services/sql-database/auth.md))
- [ ] Review access regularly
### Managed Identity
```bash
# App Service
az webapp identity assign --name APP -g RG
# Container Apps
az containerapp identity assign --name APP -g RG --system-assigned
# Function App
az functionapp identity assign --name APP -g RG
```
### Grant Access
```bash
# Grant Key Vault access
az role assignment create \
--role "Key Vault Secrets User" \
--assignee IDENTITY_PRINCIPAL_ID \
--scope /subscriptions/SUB/resourceGroups/RG/providers/Microsoft.KeyVault/vaults/VAULT
```
### Permissions Required to Grant Roles
> ā ļø **Important**: To assign RBAC roles to identities, you need a role with the `Microsoft.Authorization/roleAssignments/write` permission.
| Your Role | Permissions | Recommended For |
|-----------|-------------|-----------------|
| **User Access Administrator** | Assign roles (no data access) | ā
Least privilege for role assignment |
| **Owner** | Full access + assign roles | ā More permissions than needed |
| **Custom Role** | Specific permissions including roleAssignments/write | ā
Fine-grained control |
**Common Scenario**: Granting Storage Blob Data Owner to a Web App's managed identity
```bash
# You need User Access Administrator (or Owner) on the Storage Account to run this:
az role assignment create \
--role "Storage Blob Data Owner" \
--assignee WEBAPP_PRINCIPAL_ID \
--scope /subscriptions/SUB/resourceGroups/RG/providers/Microsoft.Storage/storageAccounts/ACCOUNT
```
If you encounter `AuthorizationFailed` errors when assigning roles, you likely need the User Access Administrator role at the target scope.
### RBAC Best Practices
| Role | Use When |
|------|----------|
| Owner | Full access + assign roles |
| Contributor | Full access except IAM |
| Reader | View-only access |
| Key Vault Secrets User | Read secrets only |
| Storage Blob Data Reader | Read blobs only |
```bash
# Grant minimal role at resource scope
az role assignment create \
--role "Storage Blob Data Reader" \
--assignee PRINCIPAL_ID \
--scope /subscriptions/SUB/resourceGroups/RG/providers/Microsoft.Storage/storageAccounts/ACCOUNT
```
---
## Network Security
### Checklist
- [ ] Use private endpoints for PaaS services
- [ ] Configure NSGs on all subnets
- [ ] Disable public endpoints where possible
- [ ] Enable DDoS protection
- [ ] Use Azure Firewall or NVA
### Private Endpoints
```bash
# Create private endpoint for storage
az network private-endpoint create \
--name myEndpoint -g RG \
--vnet-name VNET --subnet SUBNET \
--private-connection-resource-id STORAGE_ID \
--group-id blob \
--connection-name myConnection
```
### NSG Rules
```bash
# Deny all inbound by default, allow only required traffic
az network nsg rule create \
--nsg-name NSG -g RG \
--name AllowHTTPS \
--priority 100 \
--destination-port-ranges 443 \
--access Allow
```
### Best Practices
1. **Default deny** ā Block all traffic by default, allow only required
2. **Segment networks** ā Use subnets and NSGs to isolate workloads
3. **Private endpoints** ā Use for all PaaS services in production
4. **Service endpoints** ā Alternative to private endpoints for simpler scenarios
5. **Azure Firewall** ā Centralize egress traffic control
---
## Data Protection
### Checklist
- [ ] Enable encryption at rest (default for most Azure services)
- [ ] Use TLS 1.2+ for transit
- [ ] Store secrets in Key Vault
- [ ] Enable soft delete for Key Vault
- [ ] Use customer-managed keys (CMK) for sensitive data
### Key Vault Security
```bash
# Enable soft delete and purge protection
az keyvault update \
--name VAULT -g RG \
--enable-soft-delete true \
--enable-purge-protection true
# Enable RBAC permission model
az keyvault update \
--name VAULT -g RG \
--enable-rbac-authorization true
```
### Best Practices
1. **Never store secrets in code** ā Use Key Vault or managed identity
2. **Rotate secrets regularly** ā Set expiration dates and automate rotation
3. **Enable soft delete** ā Protect against accidental deletion
4. **Enable purge protection** ā Prevent permanent deletion during retention
5. **Use RBAC for Key Vault** ā Prefer over access policies
6. **Customer-managed keys** ā For sensitive data requiring key control
---
## Monitoring and Defender
### Checklist
- [ ] Enable Microsoft Defender for Cloud
- [ ] Configure diagnostic logging
- [ ] Set up security alerts
- [ ] Enable audit logging
### Microsoft Defender for Cloud
```bash
# Enable Defender plans
az security pricing create \
--name VirtualMachines \
--tier Standard
```
### Security Assessment
Use Microsoft Defender for Cloud for:
- Security score
- Recommendations
- Compliance assessment
- Threat detection
### Best Practices
1. **Enable Defender** ā For all production workloads
2. **Review security score** ā Address high-priority recommendations
3. **Configure alerts** ā Set up notifications for security events
4. **Diagnostic logs** ā Enable for all resources, send to Log Analytics
5. **Audit logging** ā Track administrative actions and access
---
## Azure Identity SDK
All Azure SDKs use their language's Identity library for credential-free authentication. Use `DefaultAzureCredential` for **local development only**; in production, use `ManagedIdentityCredential` or another deterministic credential ā see [auth-best-practices.md](auth-best-practices.md). Rust uses `DeveloperToolsCredential` as it doesn't have a `DefaultAzureCredential` equivalent.
| Language | Package | Install |
|----------|---------|---------|
| .NET | `Azure.Identity` | `dotnet add package Azure.Identity` |
| Java | `azure-identity` | Maven: `com.azure:azure-identity` |
| JavaScript | `@azure/identity` | `npm install @azure/identity` |
| Python | `azure-identity` | `pip install azure-identity` |
| Go | `azidentity` | `go get github.com/Azure/azure-sdk-for-go/sdk/azidentity` |
| Rust | `azure_identity` | `cargo add azure_identity` |
For Key Vault SDK examples, see: [Key Vault Reference](services/key-vault/README.md)
For Storage SDK examples, see: [Storage Reference](services/storage/README.md)
---
## Further Reading
- [Key Vault documentation](https://learn.microsoft.com/azure/key-vault/general/overview)
- [Managed identities documentation](https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview)
- [Azure RBAC documentation](https://learn.microsoft.com/azure/role-based-access-control/overview)
- [Microsoft Defender for Cloud](https://learn.microsoft.com/azure/defender-for-cloud/defender-for-cloud-introduction)
specialized-routing.md 3.4 KB
# Specialized Technology Routing
**MANDATORY**: Before starting any planning, check the user's prompt for specialized technology keywords. If matched, invoke the corresponding skill FIRST ā it has tested templates and optimized workflows for that technology.
## Prompt-Based Routing Table
> **ā ļø PRIORITY RULE**: Check rows **top to bottom**. The first match wins. If the prompt mentions **AWS Lambda migration or AWS Lambda**, invoke **azure-cloud-migrate** even if Azure Functions are also mentioned.
| Priority | User prompt mentions | Invoke skill FIRST | Then resume azure-prepare at |
|----------|---------------------|--------------------|-----------------------------|
| **1 (highest)** | Lambda, AWS Lambda, migrate AWS, migrate GCP, Lambda to Functions, migrate from AWS, migrate from GCP | **azure-cloud-migrate** | Phase 1 Step 4 (Select Recipe) ā azure-cloud-migrate does assessment + code conversion, then azure-prepare takes over for infrastructure, local testing, or deployment |
| 2 | copilot SDK, copilot app, copilot-powered, @github/copilot-sdk, CopilotClient, sendAndWait, copilot-sdk-service | **azure-hosted-copilot-sdk** | Phase 1 Step 4 (Select Recipe) |
| 3 | Azure Functions, function app, serverless function, timer trigger, HTTP trigger, queue trigger, func new, func start | Stay in **azure-prepare** | Phase 1 Step 4 (Select Recipe) ā prefer Azure Functions templates |
| 4 (lowest) | workflow, orchestration, multi-step, pipeline, fan-out/fan-in, saga, long-running process, durable, order processing | Stay in **azure-prepare** | Phase 1 Step 4 ā select **durable** recipe. **MUST** load [durable.md](services/functions/durable.md), [DTS reference](services/durable-task-scheduler/README.md), and [DTS Bicep patterns](services/durable-task-scheduler/bicep.md). |
> ā ļø This checks the user's **prompt text**, not just existing code. Essential for greenfield projects where there is no codebase to scan.
## Why This Step Exists
azure-prepare is the default entry point for all Azure app work. Some technologies (Copilot SDK) have dedicated skills with:
- Pre-tested `azd` templates that avoid manual scaffolding errors
- Specialized configuration (BYOM model config)
- Optimized infrastructure patterns
Without this check, azure-prepare generates generic infrastructure that misses these optimizations.
> ā ļø **Re-entry guard**: When azure-prepare is invoked as a **resume** from a specialized skill (e.g., azure-hosted-copilot-sdk Step 4), **skip this routing check** and proceed directly to Step 4. The specialized skill has already completed its work.
## Flow
```
User prompt ā azure-prepare activated
ā
āā Prompt mentions specialized tech?
ā āā YES ā Invoke specialized skill ā Skill scaffolds + configures
ā ā ā Resume azure-prepare at Step 4 (recipe/infra/validate/deploy)
ā āā NO ā Continue normal azure-prepare workflow from Step 1
ā
āā Phase 1 Step 3 (Scan Codebase) also detects SDKs in existing files
ā See [scan.md](scan.md) for file-based detection
```
## Complementary Checks
This prompt-based check complements ā does not replace ā existing file-based detection:
- **[scan.md](scan.md)** ā Detects SDKs in dependency files (package.json, requirements.txt)
- **[analyze.md](analyze.md)** ā Delegation table triggered by user mentions during planning
- **[research.md](research.md)** ā Skill invocation during research phase
The prompt check catches **greenfield** scenarios where no code exists yet.
README.md 1.9 KB
# AZCLI Recipe
Azure CLI workflow for imperative Azure deployments.
## When to Use
- Existing az scripts in project
- Need imperative control over deployment
- Custom deployment pipelines
- AKS deployments
- Direct resource manipulation
## Before Generation
**REQUIRED: Research best practices before generating any files.**
| Artifact | Research Action |
|----------|-----------------|
| Bicep files | Call `mcp_bicep_get_bicep_best_practices` |
| Bicep modules | Call `mcp_bicep_list_avm_metadata` and follow [AVM module order](../azd/iac-rules.md#avm-module-selection-order-mandatory) |
| Azure CLI commands | Call `activate_azure_cli_management_tools` |
| Azure best practices | Call `mcp_azure_mcp_get_azure_bestpractices` |
## Generation Steps
### 1. Generate Infrastructure (Bicep)
Create Bicep templates in `./infra/`.
**Structure:**
```
infra/
āāā main.bicep
āāā main.parameters.json
āāā modules/
āāā *.bicep
```
### 2. Generate Deployment Scripts
Create deployment scripts for provisioning.
ā [scripts.md](scripts.md)
### 3. Generate Dockerfiles (if containerized)
Manual Dockerfile creation required.
## Output Checklist
| Artifact | Path |
|----------|------|
| Main Bicep | `./infra/main.bicep` |
| Parameters | `./infra/main.parameters.json` |
| Modules | `./infra/modules/*.bicep` |
| Deploy script | `./scripts/deploy.sh` or `deploy.ps1` |
| Dockerfiles | `src/<service>/Dockerfile` |
## Deployment Commands
See [commands.md](commands.md) for common patterns.
## Naming Convention
Resources: `{prefix}{token}{instance}`
- Alphanumeric only, no special characters
- Prefix ā¤3 chars (e.g., `kv` for Key Vault)
- Token = 5 char random string
- Total ā¤32 characters
## References
- [Deployment Commands](commands.md)
- [Deployment Scripts](scripts.md)
## Next
ā Update `.azure/deployment-plan.md` ā **azure-validate**
commands.md 1.7 KB
# Azure CLI Commands
Common az commands for deployment workflows.
## Resource Group
```bash
# Create
az group create --name <rg-name> --location <location>
# Delete
az group delete --name <rg-name> --yes --no-wait
```
## Container Registry
```bash
# Create
az acr create --name <acr-name> --resource-group <rg-name> --sku Basic
# Login
az acr login --name <acr-name>
# Build and push
az acr build --registry <acr-name> --image <image:tag> .
```
## Container Apps
```bash
# Create environment
az containerapp env create \
--name <env-name> \
--resource-group <rg-name> \
--location <location>
# Deploy app
az containerapp create \
--name <app-name> \
--resource-group <rg-name> \
--environment <env-name> \
--image <acr-name>.azurecr.io/<image:tag> \
--target-port 8080 \
--ingress external
```
## App Service
```bash
# Create plan
az appservice plan create \
--name <plan-name> \
--resource-group <rg-name> \
--sku B1 --is-linux
# Create web app
az webapp create \
--name <app-name> \
--resource-group <rg-name> \
--plan <plan-name> \
--runtime "NODE:22-lts"
```
## Functions
```bash
# Create function app
az functionapp create \
--name <func-name> \
--resource-group <rg-name> \
--storage-account <storage-name> \
--consumption-plan-location <location> \
--runtime node \
--functions-version 4
```
## Key Vault
```bash
# Create
az keyvault create \
--name <kv-name> \
--resource-group <rg-name> \
--location <location>
# Set secret
az keyvault secret set \
--vault-name <kv-name> \
--name <secret-name> \
--value <secret-value>
```
scripts.md 2.3 KB
# Deployment Scripts
Script templates for AZCLI deployments.
## Bash Script
```bash
#!/bin/bash
set -euo pipefail
# Configuration
RESOURCE_GROUP="${RESOURCE_GROUP:-rg-myapp}"
LOCATION="${LOCATION:-eastus2}"
ENVIRONMENT="${ENVIRONMENT:-dev}"
echo "Deploying to $ENVIRONMENT environment..."
# Create resource group
az group create \
--name "$RESOURCE_GROUP" \
--location "$LOCATION" \
--tags environment="$ENVIRONMENT"
# Deploy infrastructure
az deployment group create \
--resource-group "$RESOURCE_GROUP" \
--template-file ./infra/main.bicep \
--parameters ./infra/main.parameters.json \
--parameters environmentName="$ENVIRONMENT"
# Get outputs
ACR_NAME=$(az deployment group show \
--resource-group "$RESOURCE_GROUP" \
--name main \
--query properties.outputs.acrName.value -o tsv)
# Build and push containers
az acr login --name "$ACR_NAME"
az acr build --registry "$ACR_NAME" --image api:latest ./src/api
echo "Deployment complete!"
```
## PowerShell Script
```powershell
#Requires -Version 7.0
$ErrorActionPreference = "Stop"
# Configuration
$ResourceGroup = $env:RESOURCE_GROUP ?? "rg-myapp"
$Location = $env:LOCATION ?? "eastus2"
$Environment = $env:ENVIRONMENT ?? "dev"
Write-Host "Deploying to $Environment environment..."
# Create resource group
az group create `
--name $ResourceGroup `
--location $Location `
--tags environment=$Environment
# Deploy infrastructure
az deployment group create `
--resource-group $ResourceGroup `
--template-file ./infra/main.bicep `
--parameters ./infra/main.parameters.json `
--parameters environmentName=$Environment
# Get outputs
$AcrName = az deployment group show `
--resource-group $ResourceGroup `
--name main `
--query properties.outputs.acrName.value -o tsv
# Build and push containers
az acr login --name $AcrName
az acr build --registry $AcrName --image api:latest ./src/api
Write-Host "Deployment complete!"
```
## Script Best Practices
| Practice | Description |
|----------|-------------|
| Fail fast | `set -euo pipefail` (bash) or `$ErrorActionPreference = "Stop"` (pwsh) |
| Use variables | Environment-based configuration |
| Idempotent | Safe to run multiple times |
| Output logging | Clear progress messages |
| Error handling | Capture and report failures |
README.md 3.9 KB
# AZD Recipe
Azure Developer CLI workflow for preparing Azure deployments.
## When to Use
- New projects, multi-service apps, want `azd up`
- Need environment management, auto-generated CI/CD
- Team prefers simplified deployment workflow
> š” **Tip:** azd supports both Bicep and Terraform as IaC providers. Choose based on your team's expertise and requirements.
## IaC Provider Options
| Provider | Use When |
|----------|----------|
| **Bicep** (default) | Azure-only, no existing IaC, want simplest setup |
| **Terraform** | Multi-cloud IaC, existing TF expertise, want azd simplicity |
**For Terraform with azd:** See [terraform.md](terraform.md)
## Before Generation
**REQUIRED: Research best practices before generating any files.**
### Check for Existing Codebase Patterns
**ā ļø CRITICAL: For existing codebases with special patterns, use `azd init --from-code -e <environment-name>` instead of manual generation.**
| Pattern | Detection | Action |
|---------|-----------|--------|
| **.NET Aspire** | `*.AppHost.csproj` or `Aspire.Hosting` package | Use `azd init --from-code -e <environment-name>` ā [aspire.md](../../aspire.md) |
| **Existing azure.yaml** | `azure.yaml` present | MODIFY mode - update existing config |
| **New project** | No azure.yaml, no special patterns | Manual generation (steps below) |
> š” **Note:** The `-e <environment-name>` flag is **required** when running `azd init --from-code` in non-interactive environments (agents, CI/CD pipelines). Without it, the command will fail with a prompt error.
### References for Manual Generation
| Artifact | Reference |
|----------|-----------|
| azure.yaml | [Schema Guide](azure-yaml.md) |
| .NET Aspire projects | [Aspire Guide](../../aspire.md) |
| Terraform with azd | [Terraform Guide](terraform.md) |
| AZD IAC rules | [IAC Rules](iac-rules.md) |
| Azure Functions templates | [Templates](../../services/functions/templates/README.md) |
| Bicep best practices | `mcp_bicep_get_bicep_best_practices` |
| Bicep resource schema | `mcp_bicep_get_az_resource_type_schema` |
| Azure Verified Modules | `mcp_bicep_list_avm_metadata` + [AVM module order](iac-rules.md#avm-module-selection-order-mandatory) |
| Terraform best practices | `mcp_azure_mcp_azureterraformbestpractices` |
| Dockerfiles | [Docker Guide](docker.md) |
## Generation Steps
### For Bicep (default)
| # | Artifact | Reference |
|---|----------|-----------|
| 1 | azure.yaml | [Schema Guide](azure-yaml.md) |
| 2 | Application code | Entry points, health endpoints, config |
| 3 | Dockerfiles | [Docker Guide](docker.md) (if containerized) |
| 4 | Infrastructure | `./infra/main.bicep` + modules per [IAC Rules](iac-rules.md) |
### For Terraform
| # | Artifact | Reference |
|---|----------|-----------|
| 1 | azure.yaml with `infra.provider: terraform` | [Terraform Guide](terraform.md) |
| 2 | Application code | Entry points, health endpoints, config |
| 3 | Dockerfiles | [Docker Guide](docker.md) (if containerized) |
| 4 | Terraform files | `./infra/*.tf` per [Terraform Guide](terraform.md) |
## Outputs
### For Bicep
| Artifact | Path |
|----------|------|
| azure.yaml | `./azure.yaml` |
| App Code | `src/<service>/*` |
| Dockerfiles | `src/<service>/Dockerfile` (if containerized) |
| Infrastructure | `./infra/` (Bicep files) |
### For Terraform
| Artifact | Path |
|----------|------|
| azure.yaml | `./azure.yaml` (with `infra.provider: terraform`) |
| App Code | `src/<service>/*` |
| Dockerfiles | `src/<service>/Dockerfile` (if containerized) |
| Infrastructure | `./infra/` (Terraform files) |
## References
- [.NET Aspire Projects](../../aspire.md)
- [azure.yaml Schema](azure-yaml.md)
- [.NET Aspire Apps](aspire.md)
- [Terraform with AZD](terraform.md)
- [Docker Configuration](docker.md)
- [IAC Rules](iac-rules.md)
## Next
ā Update `.azure/deployment-plan.md` ā **azure-validate**
aspire.md 8.9 KB
# .NET Aspire Projects with AZD
**ā MANDATORY: For .NET Aspire projects, NEVER manually create azure.yaml. Use `azd init --from-code` instead.**
## Detection
| Indicator | How to Detect |
|-----------|---------------|
| `*.AppHost.csproj` | `find . -name "*.AppHost.csproj"` |
| `Aspire.Hosting` package | `grep -r "Aspire\.Hosting" . --include="*.csproj"` |
| `Aspire.AppHost.Sdk` | `grep -r "Aspire\.AppHost\.Sdk" . --include="*.csproj"` |
## Workflow
### ā DO NOT (Wrong Approach)
```yaml
# ā WRONG - Missing services section
name: aspire-app
metadata:
template: azd-init
# Results in: "Could not find infra\main.bicep" error
```
### ā
DO (Correct Approach)
```bash
# Generate environment name
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
# Use azd init with auto-detection
azd init --from-code -e "$ENV_NAME"
```
**Generated azure.yaml:**
```yaml
name: aspire-app
metadata:
template: azd-init
services:
app:
language: dotnet
project: ./MyApp.AppHost/MyApp.AppHost.csproj
host: containerapp
```
> š” **AddDockerfile services:** If the AppHost uses `AddDockerfile()` (e.g., `builder.AddDockerfile("ginapp", "./ginapp")`), do NOT add separate service entries for those resources. Aspire handles container builds for `AddDockerfile` resources at runtime through the AppHost. The `azure.yaml` should contain only the single `app` service pointing to the AppHost.
> ā **BEFORE continuing:** Complete the mandatory [AddParameter + WithBuildArg scan](#after-azd-init-fix-addparameter-used-with-withbuildarg-before-builddeploy) below. Skipping this step is the #1 cause of failed Aspire container-build deployments.
## Command Flags
| Flag | Required | Purpose |
|------|----------|---------|
| `--from-code` | ā
| Auto-detect AppHost, no prompts |
| `-e <name>` | ā
| Environment name (non-interactive) |
| `--no-prompt` | Optional | Skip all confirmations |
**Why `--from-code` is critical:**
- Without: Prompts "How do you want to initialize?" (needs TTY)
- With: Auto-detects AppHost, no interaction needed
- Essential for agents and CI/CD
## ā After `azd init`: Fix AddParameter Used with WithBuildArg Before Build/Deploy
> **MANDATORY** ā After running `azd init --from-code`, but before `azd package`, `azd up`, or any Docker image build/deploy step, scan the AppHost source for `AddParameter` calls that are passed to `WithBuildArg` or `WithBuildSecret`. This pattern triggers an azd bug that causes Docker builds to fail.
.NET Aspire [external parameters](https://aspire.dev/fundamentals/external-parameters/) let apps request values from the deployment environment. Each `AddParameter()` call generates a Bicep parameter in the deployment manifest. However, azd cannot resolve these Bicep parameters during the Docker image build phase, producing the error `parameter infra.parameters.<name> not found`. This bug has been present across all azd versions tested (including 1.24.0).
### Scan for the pattern
**Bash:**
```bash
grep -RIn --include="*.cs" -E "AddParameter|WithBuildArg|WithBuildSecret" <path/to/AppHost>
```
**PowerShell:**
```powershell
Get-ChildItem -Path "<path/to/AppHost>" -Recurse -Filter "*.cs" |
Select-String -Pattern "AddParameter|WithBuildArg|WithBuildSecret"
```
**Problematic pattern:**
```csharp
// ā azd cannot resolve AddParameter values during Docker builds
var goVersion = builder.AddParameter("goversion", "1.25.4", publishValueAsDefault: true);
builder.AddDockerfile("ginapp", "./ginapp")
.WithBuildArg("GO_VERSION", goVersion);
```
### Fix Option A: Replace AddParameter with a constant (preferred)
For every `AddParameter(name, defaultValue, ...)` whose result is used **only** as a `WithBuildArg` argument, replace it with a `const string` (or `string`) constant:
```csharp
// ā
Use a constant instead
const string goVersion = "1.25.4";
builder.AddDockerfile("ginapp", "./ginapp")
.WithBuildArg("GO_VERSION", goVersion);
```
**Why:** This eliminates the parameter from Bicep output entirely, so azd never attempts to resolve it during Docker builds. Use this when the build arg value does not need to vary per deployment environment.
### Fix Option B: Pre-set parameter in azd environment config
If the value must remain an [external parameter](https://aspire.dev/fundamentals/external-parameters/) (e.g., it varies per environment), pre-set it in the azd environment config immediately after `azd init`:
```bash
azd env config set infra.parameters.<name> <value>
```
**Example:**
```bash
azd env config set infra.parameters.goversion 1.25.4
```
**Why:** This writes the parameter value into `.azure/<env>/config.json` so azd can find it during Docker builds without modifying AppHost source code. Use this when the build arg value must be configurable per environment.
> ā ļø **Do NOT skip this step for container-build projects.** If the AppHost passes an `AddParameter` result to `WithBuildArg`, apply one of these fixes before running `azd up`.
---
## Troubleshooting
### Error: "Could not find infra\main.bicep"
**Cause:** Manual azure.yaml without services section
**Fix:**
1. Delete manual azure.yaml
2. Run `azd init --from-code -e <env-name>`
3. Verify services section exists
### Error: "no default response for prompt"
**Cause:** Missing `--from-code` flag
**Fix:** Always use `--from-code` for Aspire:
```bash
azd init --from-code -e "$ENV_NAME"
```
### Error: "parameter infra.parameters.<name> not found"
**Cause:** The AppHost uses `AddParameter()` as a `WithBuildArg` argument. azd cannot resolve Aspire [external parameters](https://aspire.dev/fundamentals/external-parameters/) during Docker builds, regardless of azd version.
**Example error:**
```
ERROR: building service 'ginapp': parameter infra.parameters.goversion not found
```
**Fix (Option A ā preferred):** In the AppHost source, replace the `AddParameter(...)` call with a constant:
```csharp
// ā Before (causes the error)
var goVersion = builder.AddParameter("goversion", "1.25.4", publishValueAsDefault: true);
builder.AddDockerfile("ginapp", "./ginapp")
.WithBuildArg("GO_VERSION", goVersion);
// ā
After (fix)
const string goVersion = "1.25.4";
builder.AddDockerfile("ginapp", "./ginapp")
.WithBuildArg("GO_VERSION", goVersion);
```
**Fix (Option B):** Pre-set the parameter in azd environment config:
```bash
azd env config set infra.parameters.goversion 1.25.4
```
### AppHost Not Detected
**Solutions:**
1. Verify: `find . -name "*.AppHost.csproj"`
2. Build: `dotnet build`
3. Check package references in .csproj
4. Run from solution root
### Error: "unsupported resource type" ā Custom Aspire Resources
**Symptoms:** `azd init --from-code` fails with `unsupported resource type` for one or more resources in the AppHost (e.g., custom child resources, ClockHand, or other custom Aspire integration types).
**Cause:** The AppHost contains custom Aspire resource types designed for local development tooling only. These resources have no Azure equivalent and are not deployable.
**Resolution:**
1. ā **Stop ā do NOT fix this error by modifying source code.** Do not add `.ExcludeFromManifest()` to suppress the error.
2. ā **Do NOT proceed with deployment.**
3. ā
Inform the user: the application uses custom Aspire resource authoring patterns intended for local tooling, not cloud deployment.
4. ā
Record a deployment blocker: "AppHost contains custom Aspire resource types (`unsupported resource type`) with no Azure deployment target."
> ā ļø Adding `.ExcludeFromManifest()` to work around this error violates the application's design intent and may produce an incomplete or incorrect deployment.
## Infrastructure Auto-Generation
| Traditional | Aspire |
|------------|--------|
| Manual infra/main.bicep | Auto-gen from AppHost |
| Define in IaC | Define in C# code |
| Update IaC per service | Add to AppHost |
**How it works:**
1. AppHost defines services in C#
2. `azd provision` analyzes AppHost
3. Generates Bicep automatically
4. Deploys to Azure Container Apps
## Validation Steps
1. **ā Fix `AddParameter` used with `WithBuildArg`** ā see [Post-Init: Fix AddParameter Used with WithBuildArg](#after-azd-init-fix-addparameter-used-with-withbuildarg-before-builddeploy)
2. Verify azure.yaml has a non-empty services section
3. Do NOT add separate service entries for `AddDockerfile()` resources ā Aspire handles container builds at runtime through the AppHost
4. Run `azd package` to validate Docker build succeeds
5. Review generated infra/ (don't modify)
## Next Steps
1. Set subscription: `azd env set AZURE_SUBSCRIPTION_ID <id>`
2. Proceed to **azure-validate**
3. Deploy with **azure-deploy** (`azd up`)
## References
- [.NET Aspire External Parameters](https://aspire.dev/fundamentals/external-parameters/)
- [.NET Aspire Docs](https://learn.microsoft.com/dotnet/aspire/)
- [azd + Aspire](https://learn.microsoft.com/dotnet/aspire/deployment/azure/aca-deployment-azd-in-depth)
- [Samples](https://github.com/dotnet/aspire-samples)
- [Main Guide](../../aspire.md)
- [azure.yaml Schema](azure-yaml.md)
- [Docker Guide](docker.md) azure-yaml.md 6.9 KB
# azure.yaml Generation
> ā **CRITICAL: Check for .NET Aspire projects FIRST**
>
> **DO NOT manually create azure.yaml for .NET Aspire projects.** If you detect:
> - Files ending with `*.AppHost.csproj` (e.g., `MyApp.AppHost.csproj`)
> - `Aspire.Hosting` or `Aspire.AppHost.Sdk` in `.csproj` files
>
> **STOP and use `azd init --from-code` instead.** See [aspire.md](aspire.md) for details.
Create `azure.yaml` in project root for AZD.
## Structure
### Basic (Bicep - default)
```yaml
name: <project-name>
metadata:
template: azd-init
services:
<service-name>:
project: <path-to-source>
language: <python|js|ts|java|dotnet|go>
host: <containerapp|appservice|function|staticwebapp|aks>
```
### With Terraform Provider
```yaml
name: <project-name>
metadata:
template: azd-init
# Specify Terraform as IaC provider
infra:
provider: terraform
path: ./infra
services:
<service-name>:
project: <path-to-source>
language: <python|js|ts|java|dotnet|go>
host: <containerapp|appservice|function|staticwebapp|aks>
```
> š” **Tip:** Omit `infra` section to use Bicep (default). Add `infra.provider: terraform` to use Terraform. See [terraform.md](terraform.md) for details.
## Host Types
| Host | Azure Service | Use For |
|------|---------------|---------|
| `containerapp` | Container Apps | APIs, microservices, workers |
| `appservice` | App Service | Traditional web apps |
| `function` | Azure Functions | Serverless functions |
| `staticwebapp` | Static Web Apps | SPAs, static sites |
| `aks` | AKS | Kubernetes workloads |
## Examples
### Container App with Bicep (default)
```yaml
name: myapp
services:
api:
project: ./src/api
language: python
host: containerapp
docker:
path: ./src/api/Dockerfile
```
### Container App with Terraform
```yaml
name: myapp
infra:
provider: terraform
path: ./infra
services:
api:
project: ./src/api
language: python
host: containerapp
docker:
path: ./src/api/Dockerfile
```
### Container App with Custom Docker Context
When a **non-Aspire** project has a Dockerfile that expects files relative to a specific directory:
```yaml
name: myapp
services:
ginapp:
project: .
host: containerapp
image: ginapp
docker:
path: ginapp/Dockerfile
context: ginapp
```
> š” **Tip:** The `context` field specifies the Docker build context directory. This is crucial for:
> - Dockerfiles with `COPY` commands expecting files relative to a subdirectory
> - Multi-service repos where each service has its own context
> ā ļø **Aspire projects:** Do NOT manually add per-service entries with `docker.context` for Aspire `AddDockerfile()` resources. Aspire handles container builds at runtime through the AppHost. The generated `azure.yaml` should contain only a single `app` service pointing to the AppHost. See [aspire.md](aspire.md) for details.
> ā ļø **Language Field:** When using the `docker` section, the `language` field should be **omitted** or set to the language that azd will use for framework-specific behaviors. For containerized apps with custom Dockerfiles, the language is not used by azd since the build is handled by Docker. Only include `language` if you need azd to perform additional framework-specific actions beyond Docker build.
### Azure Functions
```yaml
services:
functions:
project: ./src/functions
language: js
host: function
```
### Static Web App (with framework build)
For React, Vue, Angular, Next.js, etc. that require `npm run build`:
```yaml
services:
web:
project: ./src/web # folder containing package.json
language: js # triggers: npm install && npm run build
host: staticwebapp
dist: dist # build output folder (e.g., dist, build, out)
```
### Static Web App (pure HTML/CSS - no build)
For pure HTML sites without a framework build step:
**Static files in subfolder (recommended):**
```yaml
services:
web:
project: ./src/web # folder containing index.html
host: staticwebapp
dist: . # works when project != root
```
**Static files in root - requires build script:**
> ā ļø **SWA CLI Limitation:** When `project: .`, you cannot use `dist: .`. Files must be copied to a separate output folder.
Add a minimal `package.json` with a build script:
```json
{
"scripts": {
"build": "node -e \"require('fs').mkdirSync('public',{recursive:true});require('fs').readdirSync('.').filter(f=>/\\.(html|css|js|png|jpe?g|gif|svg|ico|json|xml|txt|webmanifest|map)$/i.test(f)).forEach(f=>require('fs').copyFileSync(f,'public/'+f))\""
}
}
```
Then configure azure.yaml with `language: js` to trigger the build:
```yaml
services:
web:
project: .
language: js # triggers npm install && npm run build
host: staticwebapp
dist: public
```
### SWA Project Structure Detection
| Layout | Configuration |
|--------|---------------|
| Static in root | `project: .`, `language: js`, `dist: public` + package.json build script |
| Framework in root | `project: .`, `language: js`, `dist: <output>` |
| Static in subfolder | `project: ./path`, `dist: .` |
| Framework in subfolder | `project: ./path`, `language: js`, `dist: <output>` |
> **Key rules:**
> - `dist` is **relative to `project`** path
> - **SWA CLI limitation**: When `project: .`, cannot use `dist: .` - must use a distinct folder
> - For static files in root, add `package.json` with build script to copy files to dist folder
> - Use `language: js` to trigger npm build even for pure static sites in root
> - `language: html` and `language: static` are **NOT valid** - will fail
### SWA Bicep Requirement
Bicep must include the `azd-service-name` tag:
```bicep
resource staticWebApp 'Microsoft.Web/staticSites@2022-09-01' = {
name: name
location: location
tags: union(tags, { 'azd-service-name': 'web' })}
```
}
```
### App Service
```yaml
services:
api:
project: ./src/api
language: dotnet
host: appservice
```
## Hooks (Optional)
```yaml
hooks:
preprovision:
shell: sh
run: ./scripts/setup.sh
postprovision:
shell: sh
run: ./scripts/seed-data.sh
```
## Valid Values
| Field | Options |
|-------|---------|
| `language` | python, js, ts, java, dotnet, go (omit for staticwebapp without build) |
| `host` | containerapp, appservice, function, staticwebapp, aks |
| `docker.path` | Path to Dockerfile (relative to project root) |
| `docker.context` | Docker build context directory (optional, defaults to directory containing Dockerfile) |
> š” **Docker Context:** When `docker.context` is omitted, azd uses the directory containing the Dockerfile as the build context. Specify `context` explicitly when the Dockerfile expects files from a different directory.
## Output
- `./azure.yaml`
docker.md 2.1 KB
# Dockerfile Generation
Create Dockerfiles for containerized services.
## When to Containerize
| Include | Exclude |
|---------|---------|
| APIs, microservices | Static websites (use Static Web Apps) |
| Web apps (SSR) | Azure Functions (native deploy) |
| Background workers | Database services |
| Message processors | Logic Apps |
## Templates by Language
### Node.js
```dockerfile
FROM node:22-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
```
### Python
```dockerfile
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
### .NET
```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["*.csproj", "./"]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "App.dll"]
```
### Java
```dockerfile
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
```
### Go
```dockerfile
FROM golang:1.22-alpine AS build
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o main .
FROM alpine:latest
WORKDIR /app
COPY --from=build /app/main .
EXPOSE 8080
CMD ["./main"]
```
## .dockerignore
```
.git
node_modules
__pycache__
*.pyc
.env
.azure
```
## Best Practices
- Use slim/alpine base images
- Multi-stage builds for compiled languages
- Non-root user when possible
- Include health check endpoint in app
## Runtime-Specific Configuration
For production settings specific to each runtime:
| Runtime | Reference |
|---------|-----------|
| Node.js/Express | [runtimes/nodejs.md](../../runtimes/nodejs.md) |
iac-rules.md 5.9 KB
# AZD IAC Rules
IaC rules for AZD projects. **Additive** ā for Bicep, apply `mcp_bicep_get_bicep_best_practices`, `mcp_bicep_list_avm_metadata`, and `mcp_bicep_get_az_resource_type_schema` first; for Terraform, apply `mcp_azure_mcp_azureterraformbestpractices` first; then apply these azd-specific rules.
## AVM Module Selection Order (MANDATORY)
Always prefer modules in provider-specific order:
For **Bicep**:
1. AVM Bicep Pattern Modules (AVM+AZD first when available)
2. AVM Bicep Resource Modules
3. AVM Bicep Utility Modules
For **Terraform**:
1. AVM Terraform Pattern Modules
2. AVM Terraform Resource Modules
3. AVM Terraform Utility Modules
If no pattern module exists for the active provider, default immediately to AVM modules in the same provider order (resource, then utility) instead of using non-AVM modules.
## Retrieval Strategy (azure-documentation MCP primary + optional Context7)
- **Primary (authoritative):** Use `mcp_azure_mcp_documentation` (`azure-documentation`) for current Azure guidance and AVM integration documentation.
- **Primary (module catalog):** Use `mcp_bicep_list_avm_metadata` plus official AVM indexes to select concrete modules.
- **Secondary (supplemental):** Use Context7 only for implementation examples when `mcp_azure_mcp_documentation` does not provide enough detail. If Context7 is not available, instruct the user to install it: `npx -y @upstash/context7-mcp@latest`.
## Validation Plan
Before finalizing generated guidance:
1. Verify the selected module path uses the required AVM order above.
2. Verify AVM+AZD pattern modules were checked first, and fallback moved to AVM resource/utility modules when no pattern module exists.
3. Verify Terraform guidance follows pattern -> resource -> utility ordering.
4. Include selected module names and source links in the plan/output for traceability.
## File Structure
| Requirement | Details |
|-------------|---------|
| Location | `./infra/` folder |
| Entry point | `main.bicep` with `targetScope = 'subscription'` |
| Parameters | `main.parameters.json` (ARM JSON ā see format below) |
| Modules | `./infra/modules/*.bicep` with `targetScope = 'resourceGroup'` |
## Parameter File Format
`main.parameters.json` uses ARM JSON syntax. Do **not** use `.bicepparam` syntax (`using`, `param`, `readEnvironmentVariable()`) in this file ā `azd` will fail with a JSON parse error.
```json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": { "value": "${AZURE_ENV_NAME}" },
"location": { "value": "${AZURE_LOCATION}" }
}
}
```
Use `azd env set` to supply values. During `azd provision`, azd substitutes `${VAR}` placeholders with values from the environment.
## Naming Convention
> ā ļø **Before generating any resource name in Bicep, check [Resource naming rules](https://learn.microsoft.com/azure/azure-resource-manager/management/resource-name-rules) for that resource type's valid characters, length limits, and uniqueness scope.** Some resources forbid dashes or special characters, require globally unique names, or have short length limits. Adapt the pattern below accordingly.
**Default pattern:** `{resourceAbbreviation}-{name}-{uniqueHash}`
For resources that disallow dashes, omit separators: `{resourceAbbreviation}{name}{uniqueHash}`
- [Resource abbreviations](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations) ā recommended prefixes per resource type
```bicep
var resourceSuffix = take(uniqueString(subscription().id, environmentName, location), 6)
// Adapt separator/format per resource naming rules
var defaultName = '${name}-${resourceSuffix}'
var alphanumericName = replace('${name}${resourceSuffix}', '-', '')
```
**Forbidden:** Hard-coded tenant IDs, subscription IDs, resource group names
## Required Tags
| Tag | Apply To | Value |
|-----|----------|-------|
| `azd-env-name` | Resource group | `{environmentName}` |
| `azd-service-name` | Hosting resources | Service name from azure.yaml |
## Module Parameters
All modules must accept: `name` (string), `location` (string), `tags` (object)
## Security
| Rule | Details |
|------|---------|
| No secrets | Use Key Vault references |
| Managed Identity | Least privilege |
| Diagnostics | Enable logging |
| API versions | Use latest |
## Recommended Outputs
`azd` reads `output` values from `main.bicep` and stores UPPERCASE names as environment variables (accessible via `azd env get-values`).
| Output | When |
|--------|------|
| `AZURE_RESOURCE_GROUP` | Always (required) |
| `AZURE_CONTAINER_REGISTRY_ENDPOINT` | If using containers |
| `AZURE_KEY_VAULT_NAME` | If using secrets |
| `AZURE_LOG_ANALYTICS_WORKSPACE_ID` | If using monitoring |
| `API_URL`, `WEB_URL`, etc. | One per service endpoint |
## Templates
**main.bicep:**
```bicep
targetScope = 'subscription'
param environmentName string
param location string
var resourceSuffix = take(uniqueString(subscription().id, environmentName, location), 6)
var tags = { 'azd-env-name': environmentName }
resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
name: 'rg-${environmentName}'
location: location
tags: tags
}
module resources './modules/resources.bicep' = {
name: 'resources'
scope: rg
params: { location: location, tags: tags }
}
// Outputs ā UPPERCASE names become azd env vars
output AZURE_RESOURCE_GROUP string = rg.name
output API_URL string = resources.outputs.apiUrl
```
**Child module:**
```bicep
targetScope = 'resourceGroup'
param name string
param location string = resourceGroup().location
param tags object = {}
var resourceSuffix = take(uniqueString(subscription().id, resourceGroup().name, name), 6)
```
> ā ļø **Container resources:** CPU must use `json()` wrapper: `cpu: json('0.5')`, memory as string: `memory: '1Gi'`
terraform.md 13.9 KB
# AZD with Terraform
Use Azure Developer CLI (azd) with Terraform as the infrastructure provider.
## When to Use
Choose azd+Terraform when you want:
- **Terraform's multi-cloud capabilities** with **azd's deployment simplicity**
- Existing Terraform expertise but want `azd up` convenience
- Team familiar with Terraform but needs environment management
- Multi-cloud IaC with Azure-first deployment experience
## Benefits
| Feature | Pure Terraform | AZD + Terraform |
|---------|---------------|-----------------|
| Deploy command | `terraform apply` | `azd up` |
| Environment management | Manual workspaces | Built-in `azd env` |
| CI/CD generation | Manual setup | Auto-generated pipelines |
| Service deployment | Manual scripts | Automatic from azure.yaml |
| State management | Manual backend setup | Configurable |
| Multi-cloud | ā
Yes | ā
Yes |
## Configuration
### 1. azure.yaml Structure
Create `azure.yaml` in project root:
```yaml
name: myapp
metadata:
template: azd-init
# Specify Terraform as IaC provider
infra:
provider: terraform
path: ./infra
# Define services as usual
services:
api:
project: ./src/api
language: python
host: containerapp
docker:
path: ./src/api/Dockerfile
web:
project: ./src/web
language: js
host: staticwebapp
dist: dist
```
### 2. Terraform File Structure
Place Terraform files in `./infra/`:
```
infra/
āāā main.tf # Main resources
āāā variables.tf # Variable definitions
āāā outputs.tf # Output values
āāā provider.tf # Provider configuration
āāā modules/
āāā api/
ā āāā main.tf
āāā web/
āāā main.tf
```
### 3. Provider Configuration
**provider.tf:**
```hcl
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.2"
}
azurecaf = {
source = "aztfmod/azurecaf"
version = "~> 1.2"
}
}
# Optional: Remote state for team collaboration
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "tfstate${var.state_suffix}"
container_name = "tfstate"
key = "app.terraform.tfstate"
}
}
provider "azurerm" {
features {}
}
```
> **ā ļø IMPORTANT**: For **Azure Functions Flex Consumption**, use azurerm provider **v4.2 or later**:
> ```hcl
> terraform {
> required_providers {
> azurerm = {
> source = "hashicorp/azurerm"
> version = "~> 4.2"
> }
> }
> }
> ```
> See [Terraform Functions patterns](../../services/functions/terraform.md) for Flex Consumption examples.
### 4. Variables and Outputs
> ā ļø **WARNING: Use `${VAR}` syntax in `main.tfvars.json`, NOT Go-style `{{ .Env.* }}`**
>
> azd's template engine processes `azure.yaml` and service manifests ā it does **NOT** interpolate
> Go-style `{{ .Env.* }}` template variables in `.tfvars.json` or any Terraform variable files.
> Literal strings like `{{ .Env.AZURE_ENV_NAME }}` will be passed directly to Terraform, causing
> deployment failures.
>
> azd reads `infra/main.tfvars.json`, substitutes `${VAR}` references using its built-in envsubst,
> and passes the resolved file to Terraform via `-var-file=`. Use this pattern:
>
> ```json
> {
> "environment_name": "${AZURE_ENV_NAME}",
> "location": "${AZURE_LOCATION}",
> "subscription_id": "${AZURE_SUBSCRIPTION_ID}"
> }
> ```
>
> For additional variables not in `main.tfvars.json`, use **`TF_VAR_*` environment variables**:
> `azd env set TF_VAR_myvar value`
**variables.tf:**
```hcl
variable "environment_name" {
type = string
description = "Environment name from azd"
}
variable "location" {
type = string
description = "Azure region"
default = "eastus2"
}
variable "principal_id" {
type = string
description = "User principal ID from azd auth"
default = ""
}
```
**outputs.tf:**
```hcl
# Required: Resource group name
output "AZURE_RESOURCE_GROUP" {
value = azurerm_resource_group.main.name
}
# Service-specific outputs
output "API_URL" {
value = azurerm_container_app.api.latest_revision_fqdn
}
output "WEB_URL" {
value = azurerm_static_web_app.web.default_host_name
}
```
> š” **Tip:** Output names in UPPERCASE are automatically set as azd environment variables.
### 5. Required Tags for azd
**CRITICAL:** Tag hosting resources with service names from azure.yaml:
```hcl
resource "azurerm_container_app" "api" {
name = "ca-${var.environment_name}-api"
resource_group_name = azurerm_resource_group.main.name
# Required for azd deploy to find this resource
tags = merge(var.tags, {
"azd-service-name" = "api" # Matches service name in azure.yaml
})
# ... rest of configuration
}
resource "azurerm_static_web_app" "web" {
name = "swa-${var.environment_name}-web"
resource_group_name = azurerm_resource_group.main.name
# Required for azd deploy to find this resource
tags = merge(var.tags, {
"azd-service-name" = "web" # Matches service name in azure.yaml
})
# ... rest of configuration
}
```
> ā ļø **WARNING:** Without `azd-service-name` tags, `azd deploy` will fail to find deployment targets.
### 6. Resource Group Tags
Tag the resource group with environment name:
```hcl
resource "azurerm_resource_group" "main" {
name = "rg-${var.environment_name}"
location = var.location
tags = {
"azd-env-name" = var.environment_name
}
}
```
## Deployment Workflow
### Initial Setup
```bash
# 1. Create azd environment
azd env new dev --no-prompt
# 2. Set required variables
azd env set AZURE_LOCATION eastus2
# 3. Provision infrastructure (runs terraform init, plan, apply)
azd provision
# 4. Deploy services
azd deploy
# Or do both with single command
azd up
```
### Variables and State
**azd environment variables** ā **Terraform variables**
azd passes variables to Terraform through `main.tfvars.json` (with `${VAR}` substitution) or
explicit `TF_VAR_*` environment variables. Define the variable in `variables.tf` and reference
it in `main.tfvars.json`.
infra/main.tfvars.json ā azd substitutes ${VAR} references via envsubst:
```json
{
"environment_name": "${AZURE_ENV_NAME}",
"location": "${AZURE_LOCATION}",
"database_name": "${DATABASE_NAME}"
}
```
variables.tf ā value provided via main.tfvars.json or TF_VAR_database_name:
```hcl
variable "database_name" {
type = string
}
```
For variables not in `main.tfvars.json`, use `TF_VAR_*` environment variables:
```bash
azd env set TF_VAR_custom_setting "my-value"
```
> ā ļø **Use `${VAR}` syntax in `main.tfvars.json`, NOT Go-style `{{ .Env.* }}`.** azd substitutes
> `${VAR}` references using its built-in envsubst. Go-style template variables are only processed
> in `azure.yaml` and service manifests. See [Variables and Outputs](#4-variables-and-outputs) for details.
**Remote state setup:**
```bash
# Create state storage (one-time setup)
az group create --name rg-terraform-state --location eastus2
az storage account create \
--name tfstate<unique> \
--resource-group rg-terraform-state \
--sku Standard_LRS
az storage container create \
--name tfstate \
--account-name tfstate<unique>
# Set backend variables for azd
azd env set TF_STATE_RESOURCE_GROUP rg-terraform-state
azd env set TF_STATE_STORAGE_ACCOUNT tfstate<unique>
```
## Generation Steps
When preparing a new azd+Terraform project:
1. **Generate azure.yaml** with `infra.provider: terraform`
2. **Create Terraform files** in `./infra/`:
- `main.tf` - Core resources and resource group
- `variables.tf` - environment_name, location, tags
- `outputs.tf` - Service URLs and resource names (UPPERCASE)
- `provider.tf` - azurerm provider + backend config
3. **Add required tags**:
- Resource group: `azd-env-name`
- Hosting resources: `azd-service-name` (matches azure.yaml services)
4. **Research best practices** - Call `mcp_azure_mcp_azureterraformbestpractices`
## AVM Terraform Module Priority
For Terraform module selection, enforce this order:
1. AVM Terraform Pattern Modules
2. AVM Terraform Resource Modules
3. AVM Terraform Utility Modules
Use `mcp_azure_mcp_documentation` (`azure-documentation`) for current guidance and AVM context first, then use Context7 only as supplemental examples if required. If Context7 is not available, instruct the user to install it:
```bash
npx @upstash/context7-mcp@latest
```
## Migration from Pure Terraform
Converting existing Terraform project to use azd:
1. Create `azure.yaml` with services and `infra.provider: terraform`
2. Move `.tf` files to `./infra/` directory
3. Add `azd-service-name` tags to hosting resources
4. Ensure outputs include service URLs in UPPERCASE
5. Test with `azd provision` and `azd deploy`
## CI/CD Integration
azd can auto-generate pipelines for Terraform:
```bash
# Generate GitHub Actions workflow
azd pipeline config
# Generate Azure DevOps pipeline
azd pipeline config --provider azdo
```
Generated pipelines will:
- Install Terraform
- Run `terraform init`, `plan`, `apply`
- Use azd authentication
- Deploy services with `azd deploy`
## Comparison: azd+Terraform vs Pure Terraform
| Aspect | Pure Terraform | azd + Terraform |
|--------|---------------|-----------------|
| **IaC** | Terraform | Terraform |
| **Provision** | `terraform apply` | `azd provision` (wraps terraform) |
| **Deploy apps** | Manual scripts | `azd deploy` (automatic) |
| **Environment mgmt** | Workspaces | `azd env` |
| **Auth** | Manual az login | `azd auth login` |
| **CI/CD** | Manual setup | `azd pipeline config` |
| **Multi-service** | Manual orchestration | Automatic from azure.yaml |
| **Learning curve** | Medium | Low |
## When NOT to Use azd+Terraform
Use pure Terraform (without azd) when:
- Multi-cloud deployment (not Azure-first)
- Complex Terraform modules/workspaces that conflict with azd conventions
- Existing complex Terraform CI/CD that's hard to migrate
- Team has strong Terraform expertise but no bandwidth for azd learning
## Azure Policy Compliance
Enterprise Azure subscriptions typically enforce security policies. Your Terraform must comply:
### Storage Account (Required for Functions)
```hcl
resource "azurerm_storage_account" "storage" {
name = "stmyapp${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
# Azure policy requirements
allow_nested_items_to_be_public = false # Disable anonymous blob access
local_user_enabled = false # Disable local users
shared_access_key_enabled = false # RBAC-only, no access keys
}
```
### Function App with Managed Identity Storage
```hcl
provider "azurerm" {
features {}
storage_use_azuread = true # Required when shared_access_key_enabled = false
}
resource "azurerm_linux_function_app" "function" {
name = "func-myapp"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
service_plan_id = azurerm_service_plan.plan.id
storage_account_name = azurerm_storage_account.storage.name
storage_uses_managed_identity = true # Use MI instead of access key
identity {
type = "SystemAssigned"
}
tags = {
"azd-service-name" = "api" # REQUIRED for azd deploy
}
depends_on = [azurerm_role_assignment.deployer_storage]
}
# RBAC for deploying user (create function with MI storage)
resource "azurerm_role_assignment" "deployer_storage" {
scope = azurerm_storage_account.storage.id
role_definition_name = "Storage Blob Data Owner"
principal_id = data.azurerm_client_config.current.object_id
}
# RBAC for function app after creation
resource "azurerm_role_assignment" "function_storage" {
scope = azurerm_storage_account.storage.id
role_definition_name = "Storage Blob Data Owner"
principal_id = azurerm_linux_function_app.function.identity[0].principal_id
}
```
### Services with Disabled Local Auth
```hcl
# Service Bus
resource "azurerm_servicebus_namespace" "sb" {
local_auth_enabled = false # RBAC-only
}
# Event Hubs
resource "azurerm_eventhub_namespace" "eh" {
local_authentication_enabled = false # RBAC-only
}
# Cosmos DB
resource "azurerm_cosmosdb_account" "cosmos" {
local_authentication_disabled = true # RBAC-only
}
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| `resource not found: unable to find a resource tagged with 'azd-service-name'` | Add `azd-service-name` tag to hosting resource in Terraform |
| `RequestDisallowedByPolicy: shared key access` | Set `shared_access_key_enabled = false` on storage |
| `RequestDisallowedByPolicy: local auth disabled` | Set `local_auth_enabled = false` on Service Bus |
| `RequestDisallowedByPolicy: anonymous blob access` | Set `allow_nested_items_to_be_public = false` on storage |
| `terraform command not found` | Install Terraform CLI: `brew install terraform` or download from terraform.io |
| State conflicts | Configure remote backend in provider.tf |
| Variable not passed to Terraform | Ensure variable is set with `azd env set` and defined in variables.tf |
| Literal `{{ .Env.* }}` in Terraform errors | Use `${VAR}` syntax in `main.tfvars.json`, not Go-style `{{ .Env.* }}`. azd substitutes `${VAR}` references via envsubst |
| `main.tfvars.json` interpolation failure | Ensure `main.tfvars.json` uses `${VAR}` syntax (e.g., `${AZURE_ENV_NAME}`), not Go-style `{{ .Env.* }}` templates |
## References
- [Microsoft Docs: Use Terraform with azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/use-terraform-for-azd)
- [azd-starter-terraform template](https://github.com/Azure-Samples/azd-starter-terraform)
- [Terraform Azure Provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs)
- [Azure CAF Naming](https://registry.terraform.io/providers/aztfmod/azurecaf/latest/docs)
README.md 1.4 KB
# Bicep Recipe
Standalone Bicep workflow (without AZD).
## When to Use
- IaC-first approach
- No CLI wrapper needed
- Direct ARM deployment control
- Existing Bicep modules to reuse
- Custom deployment orchestration
## Before Generation
**REQUIRED: Research best practices before generating any files.**
| Artifact | Research Action |
|----------|-----------------|
| Bicep files | Call `mcp_bicep_get_bicep_best_practices` |
| Bicep modules | Call `mcp_bicep_list_avm_metadata` and follow [AVM module order](../azd/iac-rules.md#avm-module-selection-order-mandatory) |
| Resource schemas | Use `activate_azure_resource_schema_tools` if needed |
## Generation Steps
### 1. Generate Infrastructure
Create Bicep templates in `./infra/`.
ā [patterns.md](patterns.md)
**Structure:**
```
infra/
āāā main.bicep
āāā main.parameters.json
āāā modules/
āāā container-app.bicep
āāā storage.bicep
āāā ...
```
### 2. Generate Dockerfiles (if containerized)
Manual Dockerfile creation required.
## Output Checklist
| Artifact | Path |
|----------|------|
| Main Bicep | `./infra/main.bicep` |
| Parameters | `./infra/main.parameters.json` |
| Modules | `./infra/modules/*.bicep` |
| Dockerfiles | `src/<service>/Dockerfile` |
## References
- [Bicep Patterns](patterns.md)
## Next
ā Update `.azure/deployment-plan.md` ā **azure-validate**
patterns.md 3.1 KB
# Bicep Patterns
Common patterns for Bicep infrastructure templates.
## File Structure
```
infra/
āāā main.bicep # Entry point (subscription scope)
āāā main.parameters.json # Parameter values
āāā modules/
āāā resources.bicep # Base resources
āāā container-app.bicep # Container App module
āāā ...
```
## main.bicep Template
```bicep
targetScope = 'subscription'
@minLength(1)
@maxLength(64)
param environmentName string
@minLength(1)
param location string
var tags = { environment: environmentName }
resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
name: 'rg-${environmentName}'
location: location
tags: tags
}
module resources './modules/resources.bicep' = {
name: 'resources'
scope: rg
params: {
location: location
environmentName: environmentName
tags: tags
}
}
output resourceGroupName string = rg.name
```
## main.parameters.json
> ā ļø **Warning:** This file uses ARM JSON syntax. Do **not** use `.bicepparam` syntax (`using`, `param`, `readEnvironmentVariable()`) in this file ā `azd` will fail with a JSON parse error.
```json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": { "value": "${AZURE_ENV_NAME}" },
"location": { "value": "${AZURE_LOCATION}" }
}
}
```
Use `azd env set` to supply values at deploy time:
```bash
azd env set AZURE_ENV_NAME myapp-1234
azd env set AZURE_LOCATION eastus2
```
## Naming Convention
```bicep
var resourceToken = uniqueString(subscription().id, resourceGroup().id, location)
// Pattern: {prefix}{name}{token}
// Total ā¤32 chars, alphanumeric only
var kvName = 'kv${environmentName}${resourceToken}'
var storName = 'stor${resourceToken}'
// Container Registry: alphanumeric only (5-50 chars)
var acrName = replace('cr${environmentName}${resourceToken}', '-', '')
```
## Security Requirements
| Requirement | Pattern |
|-------------|---------|
| No hardcoded secrets | Use Key Vault references |
| Managed Identity | `identity: { type: 'UserAssigned' }` |
| HTTPS only | `httpsOnly: true` |
| TLS 1.2+ | `minTlsVersion: '1.2'` |
| No public blob access | `allowBlobPublicAccess: false` |
## Common Modules
### Log Analytics
```bicep
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: 'log-${resourceToken}'
location: location
properties: {
sku: { name: 'PerGB2018' }
retentionInDays: 30
}
}
```
### Application Insights
```bicep
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: 'appi-${resourceToken}'
location: location
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: logAnalytics.id
}
}
```
### Key Vault
```bicep
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'kv-${resourceToken}'
location: location
properties: {
sku: { family: 'A', name: 'standard' }
tenantId: subscription().tenantId
enableRbacAuthorization: true
}
}
```
README.md 2.6 KB
# Terraform Recipe
Terraform workflow for Azure deployments.
> **ā ļø IMPORTANT: Consider azd+Terraform First**
>
> If you're deploying to Azure, you should **default to [azd with Terraform](../azd/terraform.md)** instead of pure Terraform. azd+Terraform gives you:
> - Terraform's IaC capabilities
> - Simple `azd up` deployment workflow
> - Built-in environment management
> - Automatic CI/CD pipeline generation
> - Service orchestration from azure.yaml
>
> ā **See [azd+Terraform documentation](../azd/terraform.md)** ā
## When to Use Pure Terraform (Without azd)
Only use pure Terraform workflow when you have specific requirements that prevent using azd:
- **Multi-cloud deployments** where Azure is not the primary target
- **Complex Terraform modules/workspaces** that are incompatible with azd conventions
- **Existing Terraform CI/CD** pipelines that are hard to migrate
- **Organization mandate** for pure Terraform workflow without any wrapper tools
- **Explicitly requested** by the user to use Terraform without azd
## When to Use azd+Terraform Instead
Use azd+Terraform (the default) when:
- **Azure-first deployment** (even if you want multi-cloud IaC)
- Want **`azd up` simplicity** with Terraform IaC
- **Multi-service apps** needing orchestration
- Team wants to learn Terraform with a simpler workflow
ā See [azd+Terraform documentation](../azd/terraform.md)
## Before Generation
**REQUIRED: Research best practices before generating any files.**
| Artifact | Research Action |
|----------|-----------------|
| Terraform patterns | Call `mcp_azure_mcp_azureterraformbestpractices` |
| Azure best practices | Call `mcp_azure_mcp_get_azure_bestpractices` |
## Generation Steps
### 1. Generate Infrastructure
Create Terraform files in `./infra/`.
ā [patterns.md](patterns.md)
**Structure:**
```
infra/
āāā main.tf
āāā variables.tf
āāā outputs.tf
āāā terraform.tfvars
āāā backend.tf
āāā modules/
āāā ...
```
### 2. Set Up State Backend
Azure Storage for remote state.
### 3. Generate Dockerfiles (if containerized)
Manual Dockerfile creation required.
## Output Checklist
| Artifact | Path |
|----------|------|
| Main config | `./infra/main.tf` |
| Variables | `./infra/variables.tf` |
| Outputs | `./infra/outputs.tf` |
| Values | `./infra/terraform.tfvars` |
| Backend | `./infra/backend.tf` |
| Modules | `./infra/modules/` |
| Dockerfiles | `src/<service>/Dockerfile` |
## References
- [Terraform Patterns](patterns.md)
## Next
ā Update `.azure/deployment-plan.md` ā **azure-validate**
patterns.md 3.7 KB
# Terraform Patterns
Common patterns for Terraform Azure deployments.
## File Structure
```
infra/
āāā main.tf # Main resources
āāā variables.tf # Variable definitions
āāā outputs.tf # Output values
āāā terraform.tfvars # Variable values
āāā backend.tf # State backend
āāā modules/
āāā ...
```
## Provider Configuration
```hcl
# backend.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
azurecaf = {
source = "aztfmod/azurecaf"
version = "~> 1.2"
}
}
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "tfstate<unique>"
container_name = "tfstate"
key = "app.terraform.tfstate"
}
}
provider "azurerm" {
features {}
}
```
## Variables
```hcl
# variables.tf
variable "environment" {
type = string
description = "Environment name"
}
variable "location" {
type = string
description = "Azure region"
default = "eastus2"
}
```
## Main Configuration
```hcl
# main.tf
resource "azurerm_resource_group" "main" {
name = "rg-${var.environment}"
location = var.location
tags = { environment = var.environment }
}
module "app" {
source = "./modules/app"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
environment = var.environment
}
```
## Outputs
```hcl
# outputs.tf
output "resource_group_name" {
value = azurerm_resource_group.main.name
}
output "app_url" {
value = module.app.url
}
```
## Naming with Azure CAF
```hcl
resource "azurecaf_name" "storage" {
name = var.environment
resource_type = "azurerm_storage_account"
random_length = 5
}
resource "azurerm_storage_account" "main" {
name = azurecaf_name.storage.result
# ...
}
```
## State Backend Setup
```bash
# Create state storage
az group create --name rg-terraform-state --location eastus2
az storage account create \
--name tfstate<unique> \
--resource-group rg-terraform-state \
--sku Standard_LRS
az storage container create \
--name tfstate \
--account-name tfstate<unique>
```
## Security Requirements
| Requirement | Pattern |
|-------------|---------|
| No hardcoded secrets | Use Key Vault data sources |
| Managed Identity | `identity { type = "UserAssigned" }` |
| State encryption | Azure Storage encryption |
| State locking | Azure Blob lease |
## Container Apps with ACR
> **ā ļø Two-Phase Deployment Required.** See [Container Apps Terraform Patterns](../../services/container-apps/terraform.md) for full details.
Use a **public placeholder image** during initial provisioning. Never reference an ACR image that hasn't been built yet. Do **not** use `admin_enabled` on the ACR or add a `registry` block with `username`/`password_secret_name` ā use managed identity instead.
```hcl
resource "azurerm_container_app" "api" {
# ...
template {
container {
name = "api"
image = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"
# ...
}
}
# Prevent Terraform from reverting post-apply image and registry updates
lifecycle {
ignore_changes = [
template[0].container[0].image,
registry,
]
}
}
```
After `terraform apply`, build the real image and update the Container App via CLI ā see the **azure-deploy** skill's `references/recipes/terraform/README.md` (Container Apps Two-Phase Deployment section).
nodejs.md 5.9 KB
# Node.js/Express Production Configuration for Azure
Configure Express/Node.js applications for production deployment on Azure Container Apps and App Service.
## Required Production Settings
### 1. Trust Proxy (CRITICAL)
Azure load balancers and reverse proxies sit in front of your app. Without trust proxy, you'll get wrong client IPs, HTTPS detection failures, and cookie issues.
```javascript
const app = express();
// REQUIRED for Azure - trust the Azure load balancer
app.set('trust proxy', 1); // Trust first proxy
// Or trust all proxies (less secure but simpler)
app.set('trust proxy', true);
```
### 2. Cookie Configuration
Azure's infrastructure requires specific cookie settings:
```javascript
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only in prod
sameSite: 'lax', // Required for Azure
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
```
**Key settings:**
- `sameSite: 'lax'` ā Required for cookies through Azure's proxy
- `secure: true` ā Only in production (HTTPS)
- `httpOnly: true` ā Prevent XSS attacks
### 3. Health Check Endpoint
Azure Container Apps and App Service check your app's health:
```javascript
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
});
```
**Configure in Container Apps:**
```bash
az containerapp update \
--name APP \
--resource-group RG \
--health-probe-path /health \
--health-probe-interval 30
```
### 4. Port Configuration
Azure sets the port via environment variable:
```javascript
const port = process.env.PORT || process.env.WEBSITES_PORT || 3000;
app.listen(port, '0.0.0.0', () => {
console.log(`Server running on port ${port}`);
});
```
**Important:** Bind to `0.0.0.0`, not `localhost` or `127.0.0.1`.
### 5. Environment Detection
```javascript
const isProduction = process.env.NODE_ENV === 'production';
const isAzure = process.env.WEBSITE_SITE_NAME || process.env.CONTAINER_APP_NAME;
if (isProduction || isAzure) {
app.set('trust proxy', 1);
}
```
---
## Complete Production Configuration
```javascript
// app.js - Production-ready Express configuration for Azure
const express = require('express');
const session = require('express-session');
const app = express();
const isProduction = process.env.NODE_ENV === 'production';
// Trust Azure load balancer
if (isProduction) {
app.set('trust proxy', 1);
}
// Security headers
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
next();
});
// JSON parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Session (if using)
app.use(session({
secret: process.env.SESSION_SECRET || 'dev-secret-change-in-prod',
resave: false,
saveUninitialized: false,
cookie: {
secure: isProduction,
sameSite: 'lax',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000
}
}));
// Health check
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
// Your routes here
app.get('/', (req, res) => {
res.json({ message: 'Hello from Azure!' });
});
// Error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: isProduction ? 'Internal error' : err.message });
});
// Start server
const port = process.env.PORT || 3000;
app.listen(port, '0.0.0.0', () => {
console.log(`Server running on port ${port}`);
});
```
---
## Dockerfile for Azure
```dockerfile
FROM node:20-alpine
WORKDIR /app
# Install dependencies first (better caching)
COPY package*.json ./
RUN npm ci --only=production
# Copy app
COPY . .
# Set production environment
ENV NODE_ENV=production
# Expose port (Azure uses PORT env var)
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Start app
CMD ["node", "app.js"]
```
---
## Common Issues
### Cookies Not Setting
**Symptom:** Session lost between requests
**Fix:**
1. Add `app.set('trust proxy', 1)`
2. Set `sameSite: 'lax'` in cookie config
3. Set `secure: true` only if using HTTPS
### Wrong Client IP
**Symptom:** `req.ip` returns Azure internal IP
**Fix:** `app.set('trust proxy', 1);`
### HTTPS Redirect Loop
**Symptom:** Infinite redirects when forcing HTTPS
**Fix:**
```javascript
const TRUSTED_HOST = process.env.APP_PUBLIC_HOSTNAME;
app.use((req, res, next) => {
if (req.get('x-forwarded-proto') !== 'https' && process.env.NODE_ENV === 'production') {
if (!TRUSTED_HOST) return next();
return res.redirect(`https://${TRUSTED_HOST}${req.originalUrl}`);
}
next();
});
```
### Health Check Failing
**Symptom:** Container restarts repeatedly
**Fix:**
1. Ensure `/health` endpoint returns 200
2. Check app starts within startup probe timeout
3. Verify port matches container configuration
---
## Environment Variables
> ā ļø **Important distinction**: `azd env set` vs Application Environment Variables
>
> **`azd env set`** sets variables for the **azd provisioning process**, NOT application runtime. These are used by azd and Bicep during deployment.
>
> **Application environment variables** must be configured via:
> 1. **Bicep templates** ā Define in the resource's `env` property
> 2. **Azure CLI** ā Use `az containerapp update --set-env-vars`
> 3. **azure.yaml** ā Use the `env` section in service configuration
**Azure CLI:**
```bash
az containerapp update \
--name APP \
--resource-group RG \
--set-env-vars \
NODE_ENV=production \
SESSION_SECRET=your-secret-here \
PORT=3000
```
**azure.yaml:**
```yaml
services:
api:
host: containerapp
env:
NODE_ENV: production
PORT: "3000"
```
**Bicep:**
```bicep
env: [
{ name: 'NODE_ENV', value: 'production' }
{ name: 'SESSION_SECRET', secretRef: 'session-secret' }
]
```
azd-deployment.md 0.8 KB
# Azure Developer CLI ā Quick Reference
> Condensed from **azd-deployment**. Full patterns (Bicep modules,
> hooks, RBAC post-provision, service discovery, idempotent deploys)
> in the **azd-deployment** plugin skill if installed.
## Install
curl -fsSL https://aka.ms/install-azd.sh | bash
## Quick Start
```bash
azd auth login
azd init
azd up # provision + build + deploy
```
## Best Practices
- Always use remoteBuild: true ā local builds fail on ARM Macs deploying to AMD64
- Bicep outputs auto-populate .azure/<env>/.env ā don't manually edit
- Use azd env set for secrets ā not main.parameters.json defaults
- Service tags (azd-service-name) are required for azd to find Container Apps
- Use `|| true` in hooks ā prevent RBAC "already exists" errors from failing deploy
azure-appconfiguration-java.md 1.5 KB
# App Configuration ā Java SDK Quick Reference
> Condensed from **azure-appconfiguration-java**. Full patterns (feature flags,
> secret references, snapshots, async client, conditional requests)
> in the **azure-appconfiguration-java** plugin skill if installed.
## Install
```xml
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-data-appconfiguration</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
</dependency>
```
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```java
import com.azure.data.appconfiguration.ConfigurationClientBuilder;
import com.azure.identity.DefaultAzureCredentialBuilder;
var client = new ConfigurationClientBuilder()
.credential(new DefaultAzureCredentialBuilder().build())
.endpoint(System.getenv("AZURE_APPCONFIG_ENDPOINT"))
.buildClient();
```
## Best Practices
- Use labels ā separate configurations by environment (Dev, Staging, Production)
- Use snapshots ā create immutable snapshots for releases
- Feature flags ā use for gradual rollouts and A/B testing
- Secret references ā store sensitive values in Key Vault
- Conditional requests ā use ETags for optimistic concurrency
- Read-only protection ā lock critical production settings
- Use Entra ID ā preferred over connection strings
- Async client ā use for high-throughput scenarios
azure-appconfiguration-py.md 1.1 KB
# App Configuration ā Python SDK Quick Reference
> Condensed from **azure-appconfiguration-py**. Full patterns (feature flags,
> snapshots, read-only settings, async client, labels)
> in the **azure-appconfiguration-py** plugin skill if installed.
## Install
pip install azure-appconfiguration azure-identity
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```python
from azure.appconfiguration import AzureAppConfigurationClient
from azure.identity import DefaultAzureCredential
client = AzureAppConfigurationClient(base_url="https://<name>.azconfig.io", credential=DefaultAzureCredential())
```
## Best Practices
- Use labels for environment separation (dev, staging, prod)
- Use key prefixes for logical grouping (app:database:*, app:cache:*)
- Make production settings read-only to prevent accidental changes
- Create snapshots before deployments for rollback capability
- Use Entra ID instead of connection strings in production
- Refresh settings periodically in long-running applications
- Use feature flags for gradual rollouts and A/B testing
azure-appconfiguration-ts.md 1.2 KB
# App Configuration ā TypeScript SDK Quick Reference
> Condensed from **azure-appconfiguration-ts**. Full patterns (provider,
> dynamic refresh, Key Vault references, feature flags, snapshots)
> in the **azure-appconfiguration-ts** plugin skill if installed.
## Install
npm install @azure/app-configuration @azure/identity
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```typescript
import { AppConfigurationClient } from "@azure/app-configuration";
import { DefaultAzureCredential } from "@azure/identity";
const client = new AppConfigurationClient(process.env.AZURE_APPCONFIG_ENDPOINT!, new DefaultAzureCredential());
```
## Best Practices
- Use provider for apps ā @azure/app-configuration-provider for runtime config
- Use low-level for management ā @azure/app-configuration for CRUD operations
- Enable refresh for dynamic configuration updates
- Use labels to separate configurations by environment
- Use snapshots for immutable release configurations
- Sentinel pattern ā use a sentinel key to trigger full refresh
- RBAC roles ā App Configuration Data Reader for read-only access
azure-identity-dotnet.md 0.9 KB
# Authentication ā .NET SDK Quick Reference
> Condensed from **azure-identity-dotnet**. Full patterns (ASP.NET DI,
> sovereign clouds, brokered auth, certificate credentials)
> in the **azure-identity-dotnet** plugin skill if installed.
## Install
dotnet add package Azure.Identity
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```csharp
using Azure.Identity;
var credential = new DefaultAzureCredential();
```
## Best Practices
- Use DefaultAzureCredential for **local development only**. In production, use deterministic credentials (ManagedIdentityCredential) ā see [auth-best-practices.md](../auth-best-practices.md)
- Reuse credential instances ā single instance shared across clients
- Configure retry policies for credential operations
- Enable logging with AzureEventSourceListener for debugging auth issues
azure-identity-java.md 1.2 KB
# Authentication ā Java SDK Quick Reference
> Condensed from **azure-identity-java**. Full patterns (workload identity,
> certificate auth, device code, sovereign clouds)
> in the **azure-identity-java** plugin skill if installed.
## Install
```xml
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.15.0</version>
</dependency>
```
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```java
import com.azure.identity.DefaultAzureCredentialBuilder;
var credential = new DefaultAzureCredentialBuilder().build();
```
## Best Practices
- Use DefaultAzureCredential for **local development only** (CLI, PowerShell, VS Code). In production, use ManagedIdentityCredential ā see [auth-best-practices.md](../auth-best-practices.md)
- Managed identity in production ā no secrets to manage, automatic rotation
- Azure CLI for local dev ā run `az login` before running your app
- Least privilege ā grant only required permissions to service principals
- Token caching ā enabled by default, reduces auth round-trips
- Environment variables ā use for CI/CD, not hardcoded secrets
azure-identity-py.md 1.1 KB
# Authentication ā Python SDK Quick Reference
> Condensed from **azure-identity-py**. Full patterns (async,
> ChainedTokenCredential, token caching, all credential types)
> in the **azure-identity-py** plugin skill if installed.
## Install
```bash
pip install azure-identity
```
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```python
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
```
## Best Practices
- Use DefaultAzureCredential for **local development only** (CLI, PowerShell, VS Code). In production, use ManagedIdentityCredential ā see [auth-best-practices.md](../auth-best-practices.md)
- Never hardcode credentials ā use environment variables or managed identity
- Prefer managed identity in production Azure deployments
- Use ChainedTokenCredential when you need a custom credential order
- Close async credentials explicitly or use context managers
- Set AZURE_CLIENT_ID env var for user-assigned managed identities
- Exclude unused credentials to speed up authentication
azure-identity-ts.md 1.1 KB
# Authentication ā TypeScript SDK Quick Reference
> Condensed from **azure-identity-ts**. Full patterns (sovereign clouds,
> device code flow, custom credentials, bearer token provider)
> in the **azure-identity-ts** plugin skill if installed.
## Install
npm install @azure/identity
## Quick Start
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../auth-best-practices.md) for production patterns.
```typescript
import { DefaultAzureCredential } from "@azure/identity";
const credential = new DefaultAzureCredential();
```
## Best Practices
- Use DefaultAzureCredential for **local development only** (CLI, PowerShell, VS Code). In production, use ManagedIdentityCredential ā see [auth-best-practices.md](../auth-best-practices.md)
- Never hardcode credentials ā use environment variables or managed identity
- Prefer managed identity ā no secrets to manage in production
- Scope credentials appropriately ā use user-assigned identity for multi-tenant scenarios
- Handle token refresh ā Azure SDK handles this automatically
- Use ChainedTokenCredential for custom fallback scenarios
README.md 1.1 KB
# Azure Kubernetes Service (AKS)
Full Kubernetes orchestration for complex containerized workloads.
## When to Use
- Complex microservices requiring Kubernetes orchestration
- Teams with Kubernetes expertise
- Workloads needing fine-grained infrastructure control
- Multi-container pods with sidecars
- Custom networking requirements
- Hybrid/multi-cloud Kubernetes strategies
## Service Type in azure.yaml
```yaml
services:
my-service:
host: aks
project: ./src/my-service
docker:
path: ./Dockerfile
k8s:
deploymentPath: ./k8s
```
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| Container Registry | Image storage |
| Log Analytics Workspace | Monitoring |
| Virtual Network | Network isolation (optional) |
| Key Vault | Secrets management |
## Node Pool Types
| Pool | Purpose |
|------|---------|
| System | Cluster infrastructure, 3 nodes minimum |
| User | Application workloads, auto-scaling |
## References
- [Bicep Patterns](bicep.md)
- [K8s Manifests](manifests.md)
- [Add-ons](addons.md)
addons.md 1.0 KB
# AKS - Add-ons
## Container Monitoring
```bicep
addonProfiles: {
omsagent: {
enabled: true
config: {
logAnalyticsWorkspaceResourceID: logAnalytics.id
}
}
}
```
## Azure CNI Networking
```bicep
networkProfile: {
networkPlugin: 'azure'
networkPolicy: 'calico'
}
```
## Azure Key Vault Provider
```bicep
addonProfiles: {
azureKeyvaultSecretsProvider: {
enabled: true
config: {
enableSecretRotation: 'true'
}
}
}
```
## Application Gateway Ingress Controller
```bicep
addonProfiles: {
ingressApplicationGateway: {
enabled: true
config: {
applicationGatewayId: appGateway.id
}
}
}
```
## Add-ons Summary
| Add-on | Purpose |
|--------|---------|
| omsagent | Container Insights monitoring |
| azureKeyvaultSecretsProvider | Mount Key Vault secrets as volumes |
| ingressApplicationGateway | Application Gateway as ingress controller |
| azurepolicy | Azure Policy for Kubernetes |
bicep.md 1.7 KB
# AKS - Bicep Patterns
## Cluster Resource
```bicep
resource aks 'Microsoft.ContainerService/managedClusters@2023-07-01' = {
name: '${resourcePrefix}-aks-${uniqueHash}'
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
dnsPrefix: '${resourcePrefix}-aks'
kubernetesVersion: '1.28'
agentPoolProfiles: [
{
name: 'default'
count: 3
vmSize: 'Standard_DS2_v2'
mode: 'System'
osType: 'Linux'
enableAutoScaling: true
minCount: 1
maxCount: 5
}
]
networkProfile: {
networkPlugin: 'azure'
serviceCidr: '10.0.0.0/16'
dnsServiceIP: '10.0.0.10'
}
}
}
```
## ACR Pull Role Assignment
```bicep
resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(aks.id, containerRegistry.id, 'acrpull')
scope: containerRegistry
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
principalId: aks.properties.identityProfile.kubeletidentity.objectId
principalType: 'ServicePrincipal'
}
}
```
## Node Pool Configuration
### System Pool (Required)
```bicep
{
name: 'system'
count: 3
vmSize: 'Standard_DS2_v2'
mode: 'System'
osType: 'Linux'
}
```
### User Pool (Workloads)
```bicep
{
name: 'workload'
count: 2
vmSize: 'Standard_DS4_v2'
mode: 'User'
osType: 'Linux'
enableAutoScaling: true
minCount: 1
maxCount: 10
}
```
## Workload Identity
```bicep
properties: {
oidcIssuerProfile: {
enabled: true
}
securityProfile: {
workloadIdentity: {
enabled: true
}
}
}
```
manifests.md 1.4 KB
# AKS - Kubernetes Manifests
## Deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
replicas: 3
selector:
matchLabels:
app: my-service
template:
metadata:
labels:
app: my-service
spec:
containers:
- name: my-service
image: myacr.azurecr.io/my-service:latest
ports:
- containerPort: 8080
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
```
## Service
```yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
```
## Ingress
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
kubernetes.io/ingress.class: azure/application-gateway
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
```
README.md 0.9 KB
# App Insights
Azure Application Insights for telemetry, monitoring, and APM.
## When to Add
- User wants observability/monitoring
- User mentions telemetry, tracing, or logging
- Production apps needing health visibility
## Implementation
> **ā Invoke the `appinsights-instrumentation` skill**
>
> This skill has detailed guides for:
> - Auto-instrumentation (ASP.NET Core on App Service)
> - Manual instrumentation (Node.js, Python, C#)
> - Bicep templates and CLI scripts
## Quick Reference
| Aspect | Value |
|--------|-------|
| Resource | `Microsoft.Insights/components` |
| Depends on | Log Analytics Workspace |
| SKU | PerGB2018 (consumption-based) |
## Architecture Notes
- Create in same resource group as the app
- Connect to centralized Log Analytics Workspace
- Use connection string (not instrumentation key) for new apps
README.md 2.4 KB
# Azure App Service
Hosting patterns and best practices for Azure App Service.
## When to Use
- Traditional web applications
- REST APIs without containerization
- .NET, Node.js, Python, Java, PHP applications
- When Docker is not required/desired
- When built-in deployment slots are needed
## Service Type in azure.yaml
```yaml
services:
my-web:
host: appservice
project: ./src/my-web
```
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| App Service Plan | Compute hosting |
| Application Insights | Monitoring |
| Key Vault | Secrets (optional) |
## Runtime Stacks
> š” **Tip:** Prefer Linux App Service Plans for Node.js, Python, and Java. Use Windows only when explicitly required (e.g. .NET Framework). See [Bicep Patterns](bicep.md) for Linux vs Windows configuration.
### Linux (recommended)
| Language | linuxFxVersion |
|----------|----------------|
| Node.js 18 | `NODE\|18-lts` |
| Node.js 20 | `NODE\|20-lts` |
| Python 3.11 | `PYTHON\|3.11` |
| .NET 8 | `DOTNETCORE\|8.0` |
| Java 17 | `JAVA\|17-java17` |
### Windows
| Language | Setting |
|----------|---------|
| Node.js | `WEBSITE_NODE_DEFAULT_VERSION: '~20'` (app setting) |
| .NET 8 | Built-in (no extra config) |
## SKU Selection
| SKU | Use Case |
|-----|----------|
| F1/D1 | Development/testing (free/shared) |
| B1-B3 | Small production, basic features |
| S1-S3 | Production with auto-scale, slots |
| P1v3-P3v3 | High-performance production |
## Health Checks
Always configure health check path:
```bicep
siteConfig: {
healthCheckPath: '/health'
}
```
Endpoint should return 200 OK when healthy.
## Templates
For App Service templates with composable recipes (SQL, Cosmos DB, Auth, Redis), see [Template Selection](templates/selection.md).
## Common Data Backends
When pairing App Service with a data layer, load the corresponding service references:
| Data Service | Reference |
| ------------ | ----------------------------------------- |
| Azure SQL | [SQL Database](../sql-database/README.md) |
| Cosmos DB | [Cosmos DB](../cosmos-db/README.md) |
## References
- [Bicep Patterns](bicep.md)
- [Deployment Slots](deployment-slots.md)
- [Auto-Scaling](scaling.md)
- [Networking](networking.md)
- [SKU Selection](sku-selection.md)
- [Custom Domains](custom-domains.md)
bicep.md 4.3 KB
# App Service Bicep Patterns
## Linux vs Windows
> ā ļø **Warning:** `linuxFxVersion` and `reserved: true` are **Linux-only** properties. Setting `linuxFxVersion` on a Windows App Service Plan causes a deployment error: `LinuxFxVersion cannot be set for non-Linux App Service plans`. Omit both for Windows plans.
| Property | Linux | Windows |
|----------|-------|---------|
| Plan `reserved` | `true` | omit (defaults to `false`) |
| Site `kind` | `'app,linux'` | `'app'` |
| Site `linuxFxVersion` | e.g. `'NODE\|20-lts'` | omit |
| Site `WEBSITE_NODE_DEFAULT_VERSION` | omit | e.g. `'~20'` |
> š” **Tip:** When both frontend and API use Node.js, prefer a **single Linux App Service Plan** for both. This avoids mixed-platform complexity and keeps all services on one plan.
## Linux App Service (Recommended for Node.js)
> ā ļø **REQUIRED: `azd-service-name` tag** ā The `tags` property MUST include `union(tags, { 'azd-service-name': serviceName })` so that `azd deploy` can locate the resource. Without this tag, `azd deploy` fails with `resource not found: unable to find a resource tagged with 'azd-service-name: web'`.
```bicep
resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${resourcePrefix}-plan-${uniqueHash}'
location: location
kind: 'linux'
sku: {
name: 'B1'
tier: 'Basic'
}
properties: {
reserved: true // Required for Linux
}
}
resource webApp 'Microsoft.Web/sites@2022-09-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
kind: 'app,linux'
tags: union(tags, { 'azd-service-name': serviceName }) // REQUIRED for azd deploy
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'NODE|20-lts'
alwaysOn: true
healthCheckPath: '/health'
appSettings: [
{
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
value: 'true' // Required for source-based Node.js deploys (azd deploy)
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: applicationInsights.properties.ConnectionString
}
{
name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
value: '~3'
}
]
}
httpsOnly: true
}
identity: {
type: 'SystemAssigned'
}
}
```
## Windows App Service
Use when the runtime requires Windows (e.g. .NET Framework) or when explicitly requested.
```bicep
resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${resourcePrefix}-plan-${uniqueHash}'
location: location
sku: {
name: 'B1'
tier: 'Basic'
}
// Do NOT set reserved: true for Windows
}
resource webApp 'Microsoft.Web/sites@2022-09-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
kind: 'app'
tags: union(tags, { 'azd-service-name': serviceName }) // REQUIRED for azd deploy
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
// Do NOT set linuxFxVersion for Windows plans
alwaysOn: true
healthCheckPath: '/health'
appSettings: [
{
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
value: 'true' // Required for source-based Node.js deploys (azd deploy)
}
{
name: 'WEBSITE_NODE_DEFAULT_VERSION'
value: '~20'
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: applicationInsights.properties.ConnectionString
}
{
name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
value: '~3'
}
]
}
httpsOnly: true
}
identity: {
type: 'SystemAssigned'
}
}
```
## Node.js Build Configuration
> ā ļø **Warning:** For source-based Node.js deployments to App Service (e.g. `azd deploy`), you **must** set `SCM_DO_BUILD_DURING_DEPLOYMENT=true` so App Service runs `npm install` during deployment. Without this, the app fails at runtime with `Cannot find module` errors.
This setting is already included in both the Linux and Windows examples above.
## Key Vault Integration
Reference secrets from Key Vault:
```bicep
appSettings: [
{
name: 'DATABASE_URL'
value: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=database-url)'
}
]
```
custom-domains.md 4.8 KB
# App Service Custom Domains and Managed TLS
## Prerequisites
| Requirement | Details |
|------------|---------|
| SKU tier | Basic (B1) or higher |
| DNS access | Ability to create CNAME, A, and TXT records |
| Domain ownership | Verified via TXT record |
## DNS Configuration
### Subdomain (CNAME)
| Record Type | Name | Value |
|------------|------|-------|
| CNAME | `www` | `<app-name>.azurewebsites.net` |
| TXT | `asuid.www` | `<verification-id>` |
### Apex / Root Domain (A Record)
| Record Type | Name | Value |
|------------|------|-------|
| A | `@` | `<app-ip-address>` |
| TXT | `asuid` | `<verification-id>` |
Get the verification ID and IP address:
```bash
# Get verification ID
az webapp show -n $APP -g $RG --query "customDomainVerificationId" -o tsv
# Get IP address (for A records)
az webapp show -n $APP -g $RG --query "inboundIpAddress" -o tsv
```
> š” **Tip:** Prefer CNAME records for subdomains. For apex domains, consider using an Azure DNS alias record to avoid hardcoding IP addresses that may change.
## Bind Custom Domain via CLI
```bash
# Add custom domain
az webapp config hostname add -n $APP -g $RG --hostname www.contoso.com
# Create managed certificate (free)
az webapp config ssl create -n $APP -g $RG --hostname www.contoso.com
# Capture certificate thumbprint
THUMBPRINT=$(az webapp config ssl list -n $APP -g $RG \
--query "[?contains(hostNames, 'www.contoso.com')].thumbprint | [0]" -o tsv)
# Bind the certificate
az webapp config ssl bind -n $APP -g $RG \
--certificate-thumbprint $THUMBPRINT --ssl-type SNI
```
## Bicep ā Custom Domain with Managed Certificate
```bicep
resource customDomain 'Microsoft.Web/sites/hostNameBindings@2022-09-01' = {
parent: webApp
name: 'www.contoso.com'
properties: {
siteName: webApp.name
hostNameType: 'Verified'
sslState: 'Disabled' // enable after cert is created
}
}
resource managedCert 'Microsoft.Web/certificates@2022-09-01' = {
name: 'www.contoso.com'
location: location
properties: {
serverFarmId: appServicePlan.id
canonicalName: 'www.contoso.com'
}
dependsOn: [customDomain]
}
```
Then run a follow-up Bicep deployment to enable SNI and bind the managed certificate to the hostname:
```bicep
resource managedCert 'Microsoft.Web/certificates@2022-09-01' existing = {
name: 'www.contoso.com'
}
resource customDomainTlsBinding 'Microsoft.Web/sites/hostNameBindings@2022-09-01' = {
parent: webApp
name: 'www.contoso.com'
properties: {
siteName: webApp.name
hostNameType: 'Verified'
sslState: 'SniEnabled'
thumbprint: managedCert.properties.thumbprint
}
}
```
> ā ļø **Warning:** Managed certificate creation requires the DNS records to be in place first. The hostname binding must exist before requesting the certificate.
## Terraform ā Custom Domain with Managed Certificate
```hcl
resource "azurerm_app_service_custom_hostname_binding" "domain" {
hostname = "www.contoso.com"
app_service_name = azurerm_linux_web_app.app.name
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_app_service_managed_certificate" "cert" {
custom_hostname_binding_id = azurerm_app_service_custom_hostname_binding.domain.id
}
resource "azurerm_app_service_certificate_binding" "binding" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.domain.id
certificate_id = azurerm_app_service_managed_certificate.cert.id
ssl_state = "SniEnabled"
}
```
## TLS Options
| Option | Cost | Renewal | Use Case |
|--------|------|---------|----------|
| App Service Managed Certificate | Free | Auto-renewed | Standard custom domains |
| App Service Certificate (purchased) | ~$70/yr | Auto-renewed | Extended validation, wildcard |
| Bring your own certificate | Varies | Manual | Enterprise PKI, specific CA |
### Enforce HTTPS Only
```bicep
resource webApp 'Microsoft.Web/sites@2022-09-01' = {
name: appName
location: location
properties: {
httpsOnly: true
// ...
}
}
```
```hcl
resource "azurerm_linux_web_app" "app" {
name = var.app_name
# ...
https_only = true
}
```
## Minimum TLS Version
```bash
# Set minimum TLS version to 1.2
az webapp config set -n $APP -g $RG --min-tls-version 1.2
```
```bicep
siteConfig: {
minTlsVersion: '1.2'
}
```
> ā ļø **Warning:** TLS 1.0 and 1.1 are deprecated. Always set minimum TLS version to 1.2 for production workloads.
## Troubleshooting
| Issue | Cause | Fix |
|-------|-------|-----|
| Domain verification fails | Missing TXT record | Add `asuid` TXT record and wait for DNS propagation |
| Certificate creation fails | DNS not yet propagated | Wait 5-15 min for propagation; verify with `nslookup` |
| SSL binding error | SKU too low | Upgrade to Basic (B1) or higher |
| Managed cert not renewing | DNS record changed | Verify CNAME/A record still points to the app |
deployment-slots.md 4.6 KB
# App Service Deployment Slots
Zero-downtime deployments using staging slots.
## Basic Staging Slot
```bicep
resource stagingSlot 'Microsoft.Web/sites/slots@2022-09-01' = {
parent: webApp
name: 'staging'
location: location
properties: {
serverFarmId: appServicePlan.id
}
}
```
## Slot Requirements ā App Service
| SKU Tier | Slots Supported |
|----------|-----------------|
| Free/Shared | 0 |
| Basic | 0 |
| Standard | 5 |
| Premium | 20 |
## Slot Requirements ā Azure Functions
> ā ļø Slot support for Azure Functions varies by OS and hosting plan.
| Hosting Plan | OS | Slots Supported |
|---|---|---|
| Flex Consumption (FC1) | Linux | ā 0 |
| Consumption (Y1) | **Windows** | ā
1 staging slot |
| Consumption (Y1) | Linux | ā 0 |
| Elastic Premium (EP1-EP3) | Windows or Linux | ā
20 slots |
| Dedicated (Standard+) | Windows or Linux | ā
5ā20 slots |
> š” **For Azure Functions requiring deployment slots:**
> - **Windows Consumption (Y1) supports 1 staging slot** ā this is a supported platform capability.
> If you need it, use it. See the Bicep example below.
> - Recommendation for new projects: prefer **Elastic Premium (EP1+)** (no cold starts, VNet integration)
> or a **Dedicated plan (Standard+)**. Y1 cold starts can affect slot swap warm-up reliability.
> - **Linux Consumption and Flex Consumption do not support deployment slots.**
### Windows Consumption Function App with Staging Slot (Bicep)
```bicep
resource functionAppPlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${resourcePrefix}-funcplan-${uniqueHash}'
location: location
sku: { name: 'Y1', tier: 'Dynamic' }
// No 'reserved: true' ā Windows Consumption
}
resource functionApp 'Microsoft.Web/sites@2022-09-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
kind: 'functionapp' // Windows (no 'linux' suffix)
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: functionAppPlan.id
httpsOnly: true
siteConfig: {
appSettings: [
{ name: 'WEBSITE_NODE_DEFAULT_VERSION', value: '~20' }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'node' }
{ name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'WEBSITE_CONTENTSHARE', value: '${toLower(serviceName)}-prod' }
{ name: 'AzureWebJobsStorage', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: applicationInsights.properties.ConnectionString }
]
}
}
}
// Staging slot ā only 1 staging slot supported on Consumption
resource stagingSlot 'Microsoft.Web/sites/slots@2022-09-01' = {
parent: functionApp
name: 'staging'
location: location
kind: 'functionapp'
properties: {
serverFarmId: functionAppPlan.id
siteConfig: {
appSettings: [
{ name: 'WEBSITE_NODE_DEFAULT_VERSION', value: '~20' }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'node' }
{ name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'WEBSITE_CONTENTSHARE', value: '${toLower(serviceName)}-staging' } // MUST differ from production
{ name: 'AzureWebJobsStorage', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: applicationInsights.properties.ConnectionString }
]
}
}
}
```
> ā ļø `WEBSITE_CONTENTSHARE` **must be unique per slot** on Windows Consumption ā each slot needs its own file share.
> Use slot-sticky settings (via `slotConfigNames`) for `WEBSITE_CONTENTSHARE` and `WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`
> so these values do not swap with production.
## Deployment Flow
1. Deploy to staging slot
2. Warm up and test staging
3. Swap staging with production
4. Rollback by swapping again if needed
## Slot Settings
Configure settings that should not swap:
```bicep
resource slotConfigNames 'Microsoft.Web/sites/config@2022-09-01' = {
parent: webApp
name: 'slotConfigNames'
properties: {
appSettingNames: [
'APPLICATIONINSIGHTS_CONNECTION_STRING'
]
}
}
```
networking.md 6.5 KB
# App Service Networking
VNet integration, Private Endpoints, Access Restrictions, and Hybrid Connections.
## Feature Availability by SKU
| Feature | Free | Basic | Standard | Premium | Isolated |
|---------|:-:|:-:|:-:|:-:|:-:|
| VNet integration (outbound) | ā | ā
| ā
| ā
| ā
(native) |
| Private Endpoints (inbound) | ā | ā
| ā
| ā
| ā
|
| Access Restrictions | ā
| ā
| ā
| ā
| ā
|
| Hybrid Connections | ā | 5 | 25 | 200 | 200 |
| Access to service-endpoint-protected resources | ā | ā
| ā
| ā
| ā
|
> Note: Service endpoints are configured on VNets/subnets and downstream services (e.g., Storage, SQL). App Service accesses them via VNet integration rather than enabling service endpoints directly on the app.
## VNet Integration (Outbound)
Routes outbound traffic from the app through a VNet subnet, enabling access to private resources (databases, storage, VMs).
### Subnet Requirements
| Requirement | Value |
|------------|-------|
| Minimum subnet size | `/26` (64 addresses) recommended |
| Delegation | `Microsoft.Web/serverFarms` |
| Dedicated | One subnet per App Service plan |
### Bicep ā VNet Integration
```bicep
resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = {
parent: vnet
name: 'app-service-subnet'
properties: {
addressPrefix: '10.0.1.0/26'
delegations: [
{
name: 'Microsoft.Web.serverFarms'
properties: { serviceName: 'Microsoft.Web/serverFarms' }
}
]
}
}
resource webApp 'Microsoft.Web/sites@2024-11-01' = {
name: appName
location: location
properties: {
serverFarmId: appServicePlan.id
virtualNetworkSubnetId: subnet.id
outboundVnetRouting: {
allTraffic: true // route all outbound through VNet
}
}
}
```
### CLI - VNet Integration
```bash
# Configure virtual network integration
az webapp vnet-integration add --resource-group RG --name APP --vnet VNET --subnet SUBNET
# Update app configuration to route all outbound traffic through the virtual network integration
az resource update --resource-group RG --name APP --resource-type "Microsoft.Web/sites" --set properties.outboundVnetRouting.allTraffic=true
```
> š” **Tip:** Set `outboundVnetRouting.allTraffic: true` to route ALL outbound traffic through the VNet. Without this, only RFC1918 traffic is routed through the VNet.
## Private Endpoints (Inbound)
Expose the app on a private IP address within your VNet. Public access can be disabled entirely.
### Bicep ā Private Endpoint
```bicep
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = {
name: '${appName}-pe'
location: location
properties: {
subnet: { id: privateEndpointSubnet.id }
privateLinkServiceConnections: [
{
name: '${appName}-connection'
properties: {
privateLinkServiceId: webApp.id
groupIds: ['sites']
}
}
]
}
}
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.azurewebsites.net'
location: 'global'
}
resource dnsLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZone
name: '${vnet.name}-link'
location: 'global'
properties: {
virtualNetwork: { id: vnet.id }
registrationEnabled: false
}
}
resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-11-01' = {
parent: privateEndpoint
name: 'default'
properties: {
privateDnsZoneConfigs: [
{
name: 'webapp-dns-zone'
properties: {
privateDnsZoneId: privateDnsZone.id
}
}
]
}
}
```
### CLI - Private Endpoint
```bash
# Retrieve web app resource id
id=$(az webapp show --name APP --resource-group RG --query id --output tsv)
# Create Private Endpoint
az network private-endpoint create --connection-name CONNECTIONNAME --name private-endpoint --private-connection-resource-id $id --resource-group RG --subnet SUBNET --group-id sites --vnet-name VNET
# Create Private DNS Zone
az network private-dns zone create --resource-group RG --name "privatelink.azurewebsites.net"
# Link the DNS Zone to virtual network
az network private-dns link vnet create --resource-group RG --zone-name "privatelink.azurewebsites.net" --name dns-link --virtual-network VNET --registration-enabled false
```
> ā ļø **Warning:** Private Endpoints require Basic (B1+) or higher tier. The private DNS zone `privatelink.azurewebsites.net` must be linked to the VNet for name resolution.
## Access Restrictions
Control inbound access with IP-based or service-tag rules. Available on all SKUs.
### Bicep ā Access Restrictions
```bicep
siteConfig: {
ipSecurityRestrictions: [
{
name: 'allow-office'
priority: 100
action: 'Allow'
ipAddress: '203.0.113.0/24'
}
{
name: 'deny-all'
priority: 2147483647
action: 'Deny'
ipAddress: 'Any'
}
]
scmIpSecurityRestrictionsUseMain: true
}
```
### CLI - Access Restrictions
```bash
# Add restriction to allow traffic from set range used by the office
az webapp config access-restriction add --resource-group RG --name APP --rule-name 'allow-office' --action Allow --ip-address 203.0.113.0/24 --priority 100
# Add restriction to deny access from any other address range
az webapp config access-restriction add --resource-group RG --name APP --rule-name 'deny-all' --action Deny --ip-address Any --priority 2147483647
# Set SCM Site (Kudu) to use same access restrictions as main site
az webapp config access-restriction set -g RG -n APP --use-same-restrictions-for-scm-site true
```
> š” **Tip:** Always restrict the SCM/Kudu site too. Use `scmIpSecurityRestrictionsUseMain: true` to inherit main site rules, or define separate SCM rules.
## Hybrid Connections
Connect to on-premises resources without VPN. Requires Basic tier or higher. Uses Hybrid Connection Manager (HCM) agent on-premises relaying through Azure Relay.
> ā ļø **Warning:** Each Hybrid Connection maps to a single host:port endpoint. Basic tier supports 5; Standard tier supports 25; Premium/Isolated support 200.
## Troubleshooting
| Issue | Cause | Fix |
|-------|-------|-----|
| Cannot reach private DB | VNet integration not enabled | Enable VNet integration; check `outboundVnetRouting.allTraffic` |
| DNS resolution fails | Private DNS zone not linked | Link `privatelink.*` DNS zone to VNet |
| Access restriction not working | Priority ordering wrong | Lower numbers = higher priority; check rule order |
| Hybrid Connection timeout | HCM not running | Verify HCM service status on-premises |
| Outbound traffic blocked | NSG rules on subnet | Allow outbound to required services in NSG |
scaling.md 1.5 KB
# App Service Auto-scaling
## Basic Auto-scale Configuration
```bicep
resource autoScale 'Microsoft.Insights/autoscalesettings@2022-10-01' = {
name: '${webApp.name}-autoscale'
location: location
properties: {
targetResourceUri: appServicePlan.id
enabled: true
profiles: [
{
name: 'Auto scale'
capacity: {
minimum: '1'
maximum: '10'
default: '1'
}
rules: [
{
metricTrigger: {
metricName: 'CpuPercentage'
metricResourceUri: appServicePlan.id
timeGrain: 'PT1M'
statistic: 'Average'
timeWindow: 'PT5M'
timeAggregation: 'Average'
operator: 'GreaterThan'
threshold: 70
}
scaleAction: {
direction: 'Increase'
type: 'ChangeCount'
value: '1'
cooldown: 'PT5M'
}
}
]
}
]
}
}
```
## Common Metrics
| Metric | Use Case |
|--------|----------|
| CpuPercentage | CPU-bound workloads |
| MemoryPercentage | Memory-intensive apps |
| HttpQueueLength | Request queue depth |
| Requests | Request volume |
## Recommendations
| Workload | Min | Max | Metric |
|----------|-----|-----|--------|
| Production API | 2 | 10 | CPU + Requests |
| Dev/Test | 1 | 3 | CPU |
| High-traffic | 3 | 20 | HTTP Queue |
## SKU Requirements
Auto-scaling requires **Standard (S1+)** or **Premium** tier.
sku-selection.md 4.5 KB
# App Service SKU Selection
## SKU Comparison Matrix
| Feature | Free (F1) | Basic (B1-B3) | Standard (S1-S3) | Premium (P0v3-P3v3 and P1Mv3-P5Mv3;P0v4-P3v4 and P1Mv4-P5Mv4) | Isolated (I1v2-I6v2) |
|---------|:-:|:-:|:-:|:-:|:-:|
| **Custom domains** | ā | ā
| ā
| ā
| ā
|
| **TLS/SSL bindings** | ā | ā
(SNI) | ā
(SNI + IP) | ā
(SNI + IP) | ā
(SNI + IP) |
| **Deployment slots** | ā | ā | 5 | 20 | 20 |
| **Auto-scale** | ā | ā | ā
(10 inst.) | ā
(30 inst.) | ā
(100 inst.) |
| **VNet integration** | ā | ā
| ā
| ā
| ā
(ASE is in VNet) |
| **Private endpoints** | ā | ā
| ā
| ā
| ā
|
| **Always On** | ā | ā
| ā
| ā
| ā
|
| **Backup/Restore** | ā | ā | ā
| ā
| ā
|
| **Hybrid Connections** | ā | 5 | 25 | 200 | 200 |
| **Traffic Manager** | ā
| ā
| ā
| ā
| ā
|
| **SLA** | None | None | 99.95% | 99.95% | 99.95% |
## Pricing Overview
| SKU | vCPU | RAM | Storage | Approx. Monthly Cost |
|-----|------|-----|---------|---------------------|
| F1 | Shared | 1 GB | 1 GB | Free |
| B1 | 1 | 1.75 GB | 10 GB | ~$55 |
| B2 | 2 | 3.5 GB | 10 GB | ~$110 |
| S1 | 1 | 1.75 GB | 50 GB | ~$73 |
| S2 | 2 | 3.5 GB | 50 GB | ~$146 |
| P1v3 | 2 | 8 GB | 250 GB | ~$138 |
| P2v3 | 4 | 16 GB | 250 GB | ~$276 |
| P3v3 | 8 | 32 GB | 250 GB | ~$552 |
| I1v2 | 2 | 8 GB | 1 TB | ~$460 |
> š” **Tip:** Figures are representative for **Windows OS** in **Central US**, **as of 2026-04**. Prices vary by region, OS, and offer. Use the [Azure Pricing Calculator](https://azure.microsoft.com/pricing/calculator/) for exact figures.
### Save by using Reserved Instances and Savings Plans
Cost savings can be made on Premium V3, Premium V4 and Isolated V2 plans by committing to reserved instances for 1 or 3 year terms, details can found at [https://learn.microsoft.com/azure/cost-management-billing/reservations/prepay-app-service](https://learn.microsoft.com/azure/cost-management-billing/reservations/prepay-app-service).
Alternatively cost savings can be made using [Azure Savings plans](https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/).
## Decision Criteria
```
Production workload?
āā No ā Free (F1) or Basic (B1) for dev/test
āā Yes
Need deployment slots, auto-scale, or backups?
āā No ā Basic (B1-B3) if budget-constrained (supports VNet integration and Private Endpoints)
āā Yes
Need network isolation (dedicated ASE)?
āā Yes ā Isolated (I1v2+)
āā No
Need more than 5 deployment slots or more than 10 instances?
āā Yes ā Premium (P1v3+)
āā No ā Standard (S1-S3) with Private Endpoints and VNet integration
```
## Feature Unlock Summary
Key features unlocked at each tier:
| Upgrade Path | Features Gained |
|-------------|-----------------|
| Free ā Basic | Custom domains, TLS/SSL, Always On, VNet Integration, Private Endpoints, Hybrid Connections (5) |
| Basic ā Standard | Deployment slots, auto-scale, backups |
| Standard ā Premium | More slots (20), higher scale (30 inst.) |
| Premium ā Isolated | Full network isolation (ASE), dedicated infrastructure |
## Bicep ā App Service Plan with SKU
```bicep
resource appServicePlan 'Microsoft.Web/serverfarms@2025-03-01' = {
name: planName
location: location
sku: {
name: 'P1v3'
tier: 'PremiumV3'
capacity: 2 // number of instances
}
kind: 'linux'
properties: {
reserved: true // required for Linux
}
}
```
## Terraform ā App Service Plan with SKU
```hcl
resource "azurerm_service_plan" "plan" {
name = var.plan_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
os_type = "Linux"
sku_name = "P1v3"
}
```
## Scaling Within a Tier
Scale up (change SKU) vs scale out (add instances):
| Strategy | When to Use | How |
|----------|-------------|-----|
| Scale up | App needs more CPU/RAM | Change SKU (e.g., S1 ā S2) |
| Scale out | Handle more concurrent load | Increase instance count or enable auto-scale |
> ā ļø **Warning:** Scaling from one tier family to another (e.g., Standard to Premium) may cause a brief restart. Schedule changes during low-traffic windows.
## Recommendations by Workload
| Workload | Recommended SKU | Reason |
|----------|----------------|--------|
| Personal blog / prototype | F1 or B1 | Minimal cost, no SLA needed |
| Team dev/test | B1-B2 | Always On, custom domain |
| Production API | S1-S3 (P0v3/P0v4+ for higher scale/perf) | Auto-scale, slots, VNet |
| Enterprise with compliance | P1v3+/P1v4+ | Private endpoints, 20 slots, 30 instances |
| Regulated / multi-tenant SaaS | I1v2+ | Full network isolation |
README.md 2.7 KB
# App Service Template Recipes ā REFERENCE ONLY
Composable IaC + source code modules that extend the Web API or Web App base template to support specific Azure service integrations.
## Architecture
```
Base Template (per language/scenario, from AZD gallery)
ā
āāā Source code (REST API or full-stack web app)
āāā IaC (App Service Plan, App Service, App Insights, UAMI, RBAC)
āāā AZD config (azure.yaml, parameters)
+ Recipe (per integration)
ā
āāā Source code delta (service client, middleware, config)
āāā IaC delta (new resource + RBAC + networking modules)
āāā App settings delta
ā
= Complete deployable project ā `azd up`
```
## Available Recipes
| Recipe | IaC Delta? | Source Delta? | Status |
|--------|-----------|--------------|--------|
| [sql](sql/README.md) | ā
SQL Server + DB + firewall + RBAC | ā
EF Core (.NET), SQLAlchemy (Python), Prisma (Node.js) Ā· ā³ Spring Data JPA (Java): planned | ā
Available |
| [cosmos](cosmos/README.md) | ā
Cosmos account + DB + container + RBAC + PE | ā
Cosmos SDK (.NET, Python, Node.js) Ā· ā³ Spring Data Cosmos (Java): planned | ā
Available |
| [auth](auth/README.md) | ā
App registration + Easy Auth config | ā
MSAL / Identity middleware (.NET, Python, Node.js) Ā· ā³ Spring Security (Java): planned | ā
Available |
| [redis](redis/README.md) | ā
Redis cache + RBAC + PE | ā
Distributed cache client (.NET, Python, Node.js) Ā· ā³ Spring Session (Java): planned | ā
Available |
## How It Works
### Step 1: Fetch Base Template
```bash
# Pick template by language + scenario (see selection.md)
azd init -t <template> -e "$ENV_NAME" --no-prompt
```
### Step 2: Apply Recipe
The skill reads the recipe's README.md for:
- **IaC module files** to copy into `infra/`
- **App settings** to add to web app configuration
- **RBAC roles** with exact GUIDs (never generated by LLM)
- **Source code** to add alongside existing application code
- **Networking** to add private endpoints (conditional on VNET_ENABLED)
### Step 3: Wire Into Base
**Bicep:** Add `module` reference in `main.bicep`, pass `appServicePrincipalId`
**Terraform:** Copy `.tf` file into `infra/`, merge locals into web app settings
### 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 AZD template repos |
| **Never modify base; only extend** | Recipes are additive ā no risk of breaking core |
| **Recipes own their RBAC** | Exact role GUIDs, no LLM guessing |
| **Managed identity by default** | No passwords or connection strings in app settings |
| **Health checks required** | Every app must expose `/health` |
composition.md 4.7 KB
# Composition Algorithm ā REFERENCE ONLY
Step-by-step algorithm for composing a base App Service template with an integration recipe.
> **This is the authoritative process. Follow it exactly.**
## Algorithm
```
INPUT:
- language: dotnet | typescript | javascript | python | java
- scenario: web-api | web-app
- integration: none | sql | cosmos | auth | redis
- iac: bicep | terraform
OUTPUT:
- Complete project directory ready for `azd up`
```
### Step 1: Fetch Base Template
```bash
# Determine template by scenario + language
IF scenario == 'web-api':
TEMPLATE = web_api_templates[language] # See web-api.md
ELSE IF scenario == 'web-app':
TEMPLATE = web_app_templates[language] # See web-app.md
# Non-interactive init
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
# PowerShell: $ENV_NAME = "$(Split-Path -Leaf (Get-Location) | ForEach-Object { $_.ToLower() -replace '[ _]','-' })-dev"
azd init -t $TEMPLATE -e "$ENV_NAME" --no-prompt
```
### Step 2: Check if Recipe Needed
```
IF integration IN [none]:
ā DONE. Base template is complete.
IF integration IN [sql, cosmos, redis, auth]:
ā Full recipe. Continue to Step 3.
```
### Step 3: Add IaC Module (for full recipes only)
**Bicep:**
1. Read recipe's `README.md` for the Bicep module file
2. Copy module into `infra/app/`
3. Add module reference in `infra/main.bicep`:
```bicep
module sqlServer './app/sql.bicep' = {
name: 'sqlServer'
scope: rg
params: {
name: name
location: location
tags: tags
appServicePrincipalId: web.outputs.SERVICE_WEB_IDENTITY_PRINCIPAL_ID
}
}
```
4. If VNET_ENABLED, add the network module for private endpoints
**Terraform:**
1. Copy recipe `.tf` files into `infra/`
2. Merge app settings into web app resource block
3. Networking uses `count = var.vnet_enabled ? 1 : 0`
### Step 4: Add App Settings
Read the recipe's `README.md` for required app settings. Add them to the web app config.
> **CRITICAL: Managed Identity Configuration**
>
> For service bindings, prefer User Assigned Managed Identity (UAMI).
> Always include connection settings that reference managed identity, not passwords:
>
> ā ļø **Connection-string format is language-specific.** The example below is **.NET / ADO.NET** format. For other stacks, use the per-language env vars documented in `recipes/sql/source/{language}.md`:
>
> | Language | Env var(s) | Format |
> |---|---|---|
> | .NET | `AZURE_SQL_CONNECTION_STRING` | `Server=...;Authentication=Active Directory Managed Identity;User Id=<clientId>;` (ADO.NET) |
> | Python | `AZURE_SQL_SERVER`, `AZURE_SQL_DATABASE`, `AZURE_CLIENT_ID` | Code obtains MI access token and passes via ODBC `attrs_before` |
> | Node.js | `DATABASE_URL` | `sqlserver://<host>:1433;database=<db>;authentication=ActiveDirectoryMsi;clientId=<clientId>` (Prisma) |
> ```bicep
> appSettings: [
> { name: 'AZURE_SQL_CONNECTION_STRING', value: 'Server=${sqlServer.properties.fullyQualifiedDomainName};Database=${dbName};Authentication=Active Directory Managed Identity;User Id=${managedIdentity.properties.clientId};' }
> ]
> ```
### Step 5: Add Source Code Integration
1. Read `recipes/{integration}/source/{language}.md`
2. Add the integration code (service client, middleware, configuration)
3. Add package dependencies (NuGet, npm, pip, Maven)
> ā **Do NOT replace the main entry point file** (Program.cs, app.py, index.js).
> Recipes ADD integration code alongside the existing application code.
### Step 6: Update azure.yaml (if needed)
Some recipes require hooks:
```yaml
hooks:
postprovision:
posix:
shell: sh
run: ./infra/scripts/setup-db.sh
windows:
shell: pwsh
run: ./infra/scripts/setup-db.ps1
```
### Step 7: Validate and Deploy
**Required Environment Setup:**
```bash
azd env set AZURE_LOCATION eastus2
```
**Deployment (two-phase recommended):**
```bash
azd provision --no-prompt # Create resources + RBAC assignments
sleep 60 # Wait for RBAC propagation
azd deploy --no-prompt # Deploy code (RBAC now active)
```
> **CRITICAL: Never store database passwords in app settings.**
> The correct approach is managed identity with passwordless connections.
## Critical Rules
1. **NEVER synthesize Bicep or Terraform from scratch** ā always start from base template IaC
2. **Do not restructure or replace 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 `--no-prompt`** ā the agent must never elicit user input during azd commands
5. **ALWAYS include a health check endpoint** at `/health`
6. **ALWAYS use managed identity** ā no connection strings with passwords
7. **ALWAYS tag the App Service** with `azd-service-name` matching `azure.yaml`
README.md 3.1 KB
# Entra ID / Easy Auth Recipe ā REFERENCE ONLY
Adds authentication and authorization to an App Service base template using Microsoft Entra ID.
## Overview
This recipe configures authentication for App Service apps using either Easy Auth (built-in authentication) or MSAL SDK-based authentication. Easy Auth requires zero code changes; MSAL gives full control.
## Integration Type
| Aspect | Value |
|--------|-------|
| **Provider** | Microsoft Entra ID (Azure AD) |
| **Method** | Easy Auth (built-in) or MSAL SDK |
| **Protocols** | OpenID Connect, OAuth 2.0 |
| **Token validation** | Automatic (Easy Auth) or middleware (MSAL) |
## Option A: Easy Auth (Recommended for most apps)
Zero-code authentication built into App Service. Handles login, token management, and session cookies.
### Bicep Configuration
> š” Call `mcp_bicep_get_az_resource_type_schema` with resource type `Microsoft.Web/sites/config` to validate properties before generating this resource.
```bicep
resource authSettings 'Microsoft.Web/sites/config@2023-12-01' = {
parent: webApp
name: 'authsettingsV2'
properties: {
globalValidation: {
requireAuthentication: true
unauthenticatedClientAction: 'RedirectToLoginPage'
}
identityProviders: {
azureActiveDirectory: {
enabled: true
registration: {
openIdIssuer: 'https://login.microsoftonline.com/${tenant().tenantId}/v2.0'
clientId: appRegistration.properties.appId
}
validation: {
defaultAuthorizationPolicy: {
allowedApplications: []
}
}
}
}
login: {
tokenStore: {
enabled: true
}
}
}
}
```
### App Registration
> š” Call `mcp_bicep_get_az_resource_type_schema` with resource type `Microsoft.Graph/applications` to validate properties before generating this resource. The `microsoftGraphV1_0` extension is required ā declare it at the top of the Bicep file.
```bicep
extension microsoftGraphV1_0
resource appRegistration 'Microsoft.Graph/applications@v1.0' = {
displayName: '${name}-app'
web: {
redirectUris: [
'https://${webApp.properties.defaultHostName}/.auth/login/aad/callback'
]
}
}
```
## Option B: MSAL SDK (Full control)
Use when you need custom token validation, API-only auth, or multi-tenant support.
| Language | Source File |
|----------|-------------|
| C# (ASP.NET Core) | [source/dotnet.md](source/dotnet.md) |
| Python (FastAPI) | [source/python.md](source/python.md) |
| Node.js (Express) | [source/nodejs.md](source/nodejs.md) |
## App Settings
| Setting | Value | Purpose |
|---------|-------|---------|
| `AZURE_TENANT_ID` | Entra tenant ID | Identity provider |
| `AZURE_CLIENT_ID` | App registration client ID | Application identity |
## References
- [Easy Auth overview](https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization)
- [Microsoft Identity Web](https://learn.microsoft.com/en-us/entra/msal/dotnet/microsoft-identity-web/)
- [Configure Entra ID auth](https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad)
dotnet.md 1.3 KB
# Auth Recipe ā C# (.NET) ā REFERENCE ONLY
## Microsoft Identity Web (ASP.NET Core)
### NuGet Packages
```xml
<PackageReference Include="Microsoft.Identity.Web" Version="3.*" />
```
### Program.cs (additions)
Add these lines ā do NOT replace the existing file:
```csharp
using Microsoft.Identity.Web;
// Add after builder creation
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);
builder.Services.AddAuthorization();
// Add after app build
app.UseAuthentication();
app.UseAuthorization();
// Protected endpoint
app.MapGet("/api/me", [Authorize] (HttpContext ctx) =>
{
var name = ctx.User.FindFirst("name")?.Value;
return Results.Ok(new { name });
});
```
### appsettings.json
```json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<tenant-id>",
"ClientId": "<client-id>",
"Audience": "api://<client-id>"
}
}
```
> ā ļø The `Audience` must match the Application ID URI of the app registration (typically `api://<client-id>`). Set `AZURE_CLIENT_ID` and `AZURE_TENANT_ID` as app settings in Azure rather than hardcoding them.
## Files to Modify
| File | Action |
|------|--------|
| `Program.cs` | Add authentication middleware + protected endpoints |
| `appsettings.json` | Add AzureAd configuration section |
| `*.csproj` | Add Microsoft.Identity.Web NuGet package |
nodejs.md 2.4 KB
# Auth Recipe ā Node.js (Express) ā REFERENCE ONLY
## JWT Validation with jsonwebtoken + jwks-rsa
### npm Packages
```bash
npm install jsonwebtoken jwks-rsa
```
### Auth Middleware
Add `middleware/auth.js`:
```javascript
const jwt = require("jsonwebtoken");
const jwksClient = require("jwks-rsa");
const client = jwksClient({
jwksUri: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/discovery/v2.0/keys`,
cache: true,
rateLimit: true,
});
// Use APP_ID_URI if set (e.g., "api://<client-id>"); fall back to CLIENT_ID
const AUDIENCE = process.env.AZURE_APP_ID_URI || process.env.AZURE_CLIENT_ID;
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "No token" });
const decoded = jwt.decode(token, { complete: true });
if (!decoded || !decoded.header || !decoded.header.kid) {
return res.status(401).json({ error: "Invalid token" });
}
client.getSigningKey(decoded.header.kid, (err, key) => {
if (err || !key) {
return res.status(401).json({ error: "Invalid token" });
}
jwt.verify(
token,
key.getPublicKey(),
{
algorithms: ["RS256"],
audience: AUDIENCE,
issuer: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0`,
},
(err, payload) => {
if (err) return res.status(401).json({ error: "Invalid token" });
req.user = payload;
next();
}
);
});
}
module.exports = { authMiddleware };
```
> ā ļø The `aud` claim in Entra ID tokens is often the Application ID URI (`api://<client-id>`), not the raw client ID. Set `AZURE_APP_ID_URI` in app settings to match your app registration's exposed API URI.
### Protected Endpoint
Add to `src/index.js`:
```javascript
const { authMiddleware } = require("./middleware/auth");
app.get("/api/me", authMiddleware, (req, res) => {
res.json({ name: req.user?.name, oid: req.user?.oid });
});
```
## App Settings Required
| Setting | Value |
|---------|-------|
| `AZURE_TENANT_ID` | Entra tenant ID |
| `AZURE_CLIENT_ID` | App registration client ID |
| `AZURE_APP_ID_URI` | Application ID URI (e.g., `api://<client-id>`) ā optional, defaults to CLIENT_ID |
## Files to Modify
| File | Action |
|------|--------|
| `middleware/auth.js` | Create ā JWT validation middleware |
| `src/index.js` | Modify ā add protected routes |
| `package.json` | Modify ā add jsonwebtoken, jwks-rsa |
python.md 2.2 KB
# Auth Recipe ā Python (FastAPI) ā REFERENCE ONLY
## JWT Validation with PyJWT
### Requirements
Add to `requirements.txt`:
```
PyJWT[crypto]>=2.8
cryptography
fastapi
uvicorn
```
### Token Validation Middleware
Add `auth.py`:
```python
import os
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from jwt import PyJWKClient
security = HTTPBearer()
TENANT_ID = os.environ["AZURE_TENANT_ID"]
CLIENT_ID = os.environ["AZURE_CLIENT_ID"]
# Use APP_ID_URI if set (e.g., "api://<client-id>"); fall back to CLIENT_ID
AUDIENCE = os.environ.get("AZURE_APP_ID_URI", CLIENT_ID)
JWKS_URL = f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys"
jwks_client = PyJWKClient(JWKS_URL)
async def validate_token(creds: HTTPAuthorizationCredentials = Depends(security)):
try:
signing_key = jwks_client.get_signing_key_from_jwt(creds.credentials)
payload = jwt.decode(
creds.credentials,
signing_key.key,
algorithms=["RS256"],
audience=AUDIENCE,
issuer=f"https://login.microsoftonline.com/{TENANT_ID}/v2.0",
)
return payload
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
```
> ā ļø The `aud` claim in Entra ID tokens is often the Application ID URI (`api://<client-id>`), not the raw client ID. Set `AZURE_APP_ID_URI` in app settings to match your app registration's exposed API URI.
### Protected Endpoint
Add to `main.py`:
```python
from auth import validate_token
@app.get("/api/me")
async def me(user=Depends(validate_token)):
return {"name": user.get("name"), "oid": user.get("oid")}
```
## App Settings Required
| Setting | Value |
|---------|-------|
| `AZURE_TENANT_ID` | Entra tenant ID |
| `AZURE_CLIENT_ID` | App registration client ID |
| `AZURE_APP_ID_URI` | Application ID URI (e.g., `api://<client-id>`) ā optional, defaults to CLIENT_ID |
## Files to Modify
| File | Action |
|------|--------|
| `auth.py` | Create ā JWT validation middleware |
| `main.py` | Modify ā add protected endpoints |
| `requirements.txt` | Modify ā add PyJWT, cryptography |
README.md 3.6 KB
# Cosmos DB Recipe ā REFERENCE ONLY
Adds Azure Cosmos DB (NoSQL) integration to an App Service base template.
## Overview
This recipe composes with a Web API or Web App base template to add Cosmos DB data access. It provides the IaC delta (Cosmos account, database, container, RBAC) and per-language source code using the Cosmos DB SDK.
## Integration Type
| Aspect | Value |
|--------|-------|
| **Database** | Azure Cosmos DB for NoSQL |
| **Auth** | Managed identity (DefaultAzureCredential) |
| **SDK** | Microsoft.Azure.Cosmos (.NET), @azure/cosmos (Node.js), azure-cosmos (Python) |
| **Hosting** | App Service (from base template) |
| **Local Auth** | Disabled (`disableLocalAuth: true`) ā RBAC-only |
## Composition Steps
Apply these steps AFTER `azd init -t <base-template>`:
| # | Step | Details |
|---|------|---------|
| 1 | **Add IaC module** | Add Cosmos DB Bicep module to `infra/app/` |
| 2 | **Wire into main** | Add module reference in `main.bicep` |
| 3 | **Add app settings** | Add Cosmos endpoint + database + container settings |
| 4 | **Add source code** | Add Cosmos client setup from `source/{lang}.md` |
| 5 | **Add packages** | Add Cosmos SDK + Azure Identity packages |
## App Settings
| Setting | Value | Purpose |
|---------|-------|---------|
| `COSMOS_ENDPOINT` | `https://{account}.documents.azure.com:443/` | Cosmos account endpoint |
| `COSMOS_DATABASE_NAME` | `app-db` | Target database |
| `COSMOS_CONTAINER_NAME` | `items` | Target container |
### Bicep App Settings Block
```bicep
appSettings: [
{ name: 'COSMOS_ENDPOINT', value: cosmos.outputs.endpoint }
{ name: 'COSMOS_DATABASE_NAME', value: cosmos.outputs.databaseName }
{ name: 'COSMOS_CONTAINER_NAME', value: cosmos.outputs.containerName }
]
```
> **Note:** No connection string or key is needed. The SDK uses `DefaultAzureCredential` which automatically resolves to the app's managed identity in Azure.
## RBAC Roles Required
| Role | GUID | Scope | Purpose |
|------|------|-------|---------|
| **Cosmos DB Account Reader** | `fbdf93bf-df7d-467e-a4d2-9458aa1360c8` | Cosmos account | Read account metadata |
| **Cosmos DB Built-in Data Contributor** | `00000000-0000-0000-0000-000000000002` | Cosmos account (SQL role) | Read/write data |
> **Important:** Cosmos DB uses its own SQL RBAC system (`sqlRoleAssignments`) for data plane operations, not standard Azure RBAC.
## Networking (when VNET_ENABLED=true)
| Component | Details |
|-----------|---------|
| **Private endpoint** | Cosmos account ā App Service VNet subnet |
| **Private DNS zone** | `privatelink.documents.azure.com` |
## Resources Created
| Resource | Type | Purpose |
|----------|------|---------|
| Cosmos DB Account | `Microsoft.DocumentDB/databaseAccounts` | Serverless NoSQL database |
| SQL Database | `databaseAccounts/sqlDatabases` | Application database |
| Container | `sqlDatabases/containers` | Data container with partition key |
| Role Assignment | `Microsoft.Authorization/roleAssignments` | Control plane access |
| SQL Role Assignment | `databaseAccounts/sqlRoleAssignments` | Data plane access |
## Source Code Examples
| Language | Source File |
|----------|-------------|
| C# (.NET) | [source/dotnet.md](source/dotnet.md) |
| Python | [source/python.md](source/python.md) |
| Node.js | [source/nodejs.md](source/nodejs.md) |
## References
- [Cosmos DB + App Service tutorial](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/tutorial-dotnet-web-app)
- [Passwordless Cosmos DB connections](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/how-to-dotnet-get-started)
- [Cosmos DB RBAC](https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac)
dotnet.md 1.3 KB
# Cosmos DB Recipe ā C# (.NET) ā REFERENCE ONLY
## Cosmos DB SDK Setup
### NuGet Packages
```xml
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.*" />
<PackageReference Include="Azure.Identity" Version="1.13.*" />
```
### Program.cs (additions)
Add these lines ā do NOT replace the existing file:
```csharp
using Azure.Identity;
using Microsoft.Azure.Cosmos;
builder.Services.AddSingleton(_ =>
{
var endpoint = builder.Configuration["COSMOS_ENDPOINT"];
return new CosmosClient(endpoint, new DefaultAzureCredential());
});
```
### CRUD Endpoints
Add to `Program.cs` after `app` is built:
```csharp
app.MapGet("/api/items", async (CosmosClient cosmos) =>
{
var container = cosmos
.GetDatabase(Environment.GetEnvironmentVariable("COSMOS_DATABASE_NAME"))
.GetContainer(Environment.GetEnvironmentVariable("COSMOS_CONTAINER_NAME"));
var query = container.GetItemQueryIterator<dynamic>("SELECT * FROM c");
var results = new List<dynamic>();
while (query.HasMoreResults)
results.AddRange(await query.ReadNextAsync());
return Results.Ok(results);
});
```
## Files to Modify
| File | Action |
|------|--------|
| `Program.cs` | Add CosmosClient registration + endpoints |
| `*.csproj` | Add Microsoft.Azure.Cosmos NuGet package |
nodejs.md 1.2 KB
# Cosmos DB Recipe ā Node.js ā REFERENCE ONLY
## Cosmos SDK Setup
### npm Packages
```bash
npm install @azure/cosmos @azure/identity
```
### Database Module
Create `src/cosmosClient.js`:
```javascript
const { CosmosClient } = require("@azure/cosmos");
const { DefaultAzureCredential } = require("@azure/identity");
const client = new CosmosClient({
endpoint: process.env.COSMOS_ENDPOINT,
aadCredentials: new DefaultAzureCredential(),
});
const container = client
.database(process.env.COSMOS_DATABASE_NAME)
.container(process.env.COSMOS_CONTAINER_NAME);
module.exports = { container };
```
### CRUD Endpoints
Add to `src/index.js`:
```javascript
const { container } = require("./cosmosClient");
app.get("/api/items", async (req, res) => {
const { resources } = await container.items.readAll().fetchAll();
res.json(resources);
});
app.post("/api/items", async (req, res) => {
const { resource } = await container.items.create(req.body);
res.status(201).json(resource);
});
```
## Files to Modify
| File | Action |
|------|--------|
| `src/cosmosClient.js` | Create ā Cosmos client + container reference |
| `src/index.js` | Modify ā add CRUD endpoints |
| `package.json` | Modify ā add @azure/cosmos, @azure/identity |
python.md 1.1 KB
# Cosmos DB Recipe ā Python ā REFERENCE ONLY
## Cosmos SDK Setup
### Requirements
Add to `requirements.txt`:
```
azure-cosmos>=4.7
azure-identity
```
### Database Module
Create `cosmos_client.py`:
```python
import os
from azure.cosmos import CosmosClient
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
client = CosmosClient(os.environ["COSMOS_ENDPOINT"], credential)
database = client.get_database_client(os.environ["COSMOS_DATABASE_NAME"])
container = database.get_container_client(os.environ["COSMOS_CONTAINER_NAME"])
```
### CRUD Endpoints
Add to `main.py`:
```python
from cosmos_client import container
@app.get("/api/items")
async def list_items():
items = list(container.read_all_items())
return items
@app.post("/api/items", status_code=201)
async def create_item(item: dict):
return container.create_item(body=item)
```
## Files to Modify
| File | Action |
|------|--------|
| `cosmos_client.py` | Create ā Cosmos client + container reference |
| `main.py` | Modify ā add CRUD endpoints |
| `requirements.txt` | Modify ā add azure-cosmos, azure-identity |
README.md 3.2 KB
# Azure Cache for Redis Recipe ā REFERENCE ONLY
Adds Azure Cache for Redis integration to an App Service base template.
## Overview
This recipe composes with a Web API or Web App base template to add distributed caching with Azure Cache for Redis. Uses managed identity for passwordless access.
## Integration Type
| Aspect | Value |
|--------|-------|
| **Service** | Azure Cache for Redis |
| **Auth** | Managed identity (Entra ID access policy) |
| **SKU** | Basic C0 (dev) / Standard C1+ (production) |
| **Protocol** | Redis 6.0+ with TLS |
| **Local Auth** | Disabled ā Entra ID only |
## Composition Steps
Apply these steps AFTER `azd init -t <base-template>`:
| # | Step | Details |
|---|------|---------|
| 1 | **Add IaC module** | Add Redis Bicep module to `infra/app/` |
| 2 | **Wire into main** | Add module reference in `main.bicep` |
| 3 | **Add app settings** | Add Redis host name + port settings |
| 4 | **Add source code** | Add cache client setup from examples below |
| 5 | **Add packages** | Add Redis client packages |
## App Settings
| Setting | Value | Purpose |
|---------|-------|---------|
| `REDIS_HOST` | `{name}.redis.cache.windows.net` | Redis host name |
| `REDIS_PORT` | `6380` | TLS port |
### Bicep App Settings Block
```bicep
appSettings: [
{ name: 'REDIS_HOST', value: redis.outputs.hostName }
{ name: 'REDIS_PORT', value: '6380' }
]
```
> **Note:** With managed identity, no access key is needed. The Redis client uses `DefaultAzureCredential` to obtain a token.
## RBAC Roles Required
| Role | GUID | Scope | Purpose |
|------|------|-------|---------|
| **Redis Cache Contributor** | `e0f68234-74aa-48ed-b826-c38b57376e17` | Redis cache | Manage cache |
> For data plane access with Entra ID, configure the Redis access policy to grant the managed identity `Data Owner` or `Data Contributor` access.
## Resources Created
| Resource | Type | Purpose |
|----------|------|---------|
| Redis Cache | `Microsoft.Cache/redis` | Distributed cache |
| Access Policy | `Microsoft.Cache/redis/accessPolicyAssignments` | MI data access |
| Private Endpoint | `Microsoft.Network/privateEndpoints` | VNet-only access (conditional) |
## Source Code Examples
| Language | Source File |
|----------|-------------|
| C# (ASP.NET Core) | [source/dotnet.md](source/dotnet.md) |
| Python | [source/python.md](source/python.md) |
| Node.js | [source/nodejs.md](source/nodejs.md) |
> ā ļø **Token expiry:** Entra ID access tokens expire in ~1 hour. The C# example uses `ConfigureForAzureWithTokenCredentialAsync` which handles automatic token renewal. Python and Node.js examples require a connection factory pattern ā see the source files for a refresh-capable implementation.
## Networking (when VNET_ENABLED=true)
| Component | Details |
|-----------|---------|
| **Private endpoint** | Redis ā App Service VNet subnet |
| **Private DNS zone** | `privatelink.redis.cache.windows.net` |
## References
- [Azure Cache for Redis overview](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-overview)
- [Use Entra ID with Redis](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication)
- [Redis + App Service tutorial](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-web-app-howto)
dotnet.md 1.6 KB
# Redis Recipe ā C# (.NET) ā REFERENCE ONLY
## ASP.NET Core Distributed Cache Setup
### NuGet Packages
```xml
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.*" />
<PackageReference Include="Azure.Identity" Version="1.13.*" />
```
### Program.cs (additions)
Add these lines ā do NOT replace the existing file:
```csharp
using Azure.Identity;
using StackExchange.Redis;
var redisHost = builder.Configuration["REDIS_HOST"];
var configOptions = ConfigurationOptions.Parse($"{redisHost}:6380");
configOptions.Ssl = true;
configOptions.AbortOnConnectFail = false;
// ConfigureForAzureWithTokenCredentialAsync handles automatic token renewal
await configOptions.ConfigureForAzureWithTokenCredentialAsync(
new DefaultAzureCredential());
builder.Services.AddStackExchangeRedisCache(options =>
{
options.ConfigurationOptions = configOptions;
options.InstanceName = "app:";
});
```
> š” `ConfigureForAzureWithTokenCredentialAsync` manages token acquisition and renewal automatically ā no manual token refresh required.
### Usage
```csharp
using Microsoft.Extensions.Caching.Distributed;
app.MapGet("/api/cached", async (IDistributedCache cache) =>
{
var value = await cache.GetStringAsync("my-key");
if (value is null)
{
value = "computed-value";
await cache.SetStringAsync("my-key", value,
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) });
}
return Results.Ok(new { value });
});
```
## Files to Modify
| File | Action |
|------|--------|
| `Program.cs` | Add Redis cache registration |
| `*.csproj` | Add NuGet packages |
nodejs.md 2.2 KB
# Redis Recipe ā Node.js ā REFERENCE ONLY
## ioredis with Token Refresh
### npm Packages
```bash
npm install ioredis @azure/identity
```
### Cache Module
Create `src/cache.js`:
```javascript
const Redis = require("ioredis");
const { DefaultAzureCredential } = require("@azure/identity");
const credential = new DefaultAzureCredential();
const TOKEN_SCOPE = "https://redis.azure.com/.default";
// Redis ACL auth requires both username and token as password
const REDIS_USERNAME = "default";
const TOKEN_REFRESH_MARGIN = 5 * 60 * 1000; // 5 minutes in ms
let _client = null;
let _tokenExpiry = 0;
async function getToken() {
const tok = await credential.getToken(TOKEN_SCOPE);
_tokenExpiry = tok.expiresOnTimestamp;
return tok.token;
}
async function getRedisClient() {
const now = Date.now();
if (_client && now < _tokenExpiry - TOKEN_REFRESH_MARGIN) {
return _client;
}
// Token expired or about to expire ā create a new client with a fresh token
if (_client) {
_client.disconnect();
_client = null;
}
const token = await getToken();
_client = new Redis({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || "6380"),
tls: { servername: process.env.REDIS_HOST },
username: REDIS_USERNAME,
password: token,
lazyConnect: true,
});
await _client.connect();
return _client;
}
module.exports = { getRedisClient };
```
> ā ļø Entra ID tokens expire in ~1 hour. `getRedisClient()` recreates the connection with a fresh token when the current one is within 5 minutes of expiry. Always call `getRedisClient()` before each operation rather than caching the client reference long-term.
### Usage
```javascript
const { getRedisClient } = require("./cache");
app.get("/api/cached", async (req, res) => {
const client = await getRedisClient();
let value = await client.get("my-key");
if (!value) {
value = "computed-value";
await client.setex("my-key", 300, value); // TTL 5 minutes
}
res.json({ value });
});
```
## Files to Modify
| File | Action |
|------|--------|
| `src/cache.js` | Create ā Redis client with token refresh |
| `src/index.js` | Modify ā use getRedisClient() |
| `package.json` | Modify ā add ioredis, @azure/identity |
python.md 2.9 KB
# Redis Recipe ā Python ā REFERENCE ONLY
## Redis Client with Token Refresh
### Requirements
Add to `requirements.txt`:
```
redis>=5.0
azure-identity
```
### Cache Module
Create `cache.py`:
```python
import os
import time
import threading
import redis
from azure.identity import DefaultAzureCredential
# Redis ACL auth requires both username and token as password
_REDIS_USERNAME = "default"
_TOKEN_SCOPE = "https://redis.azure.com/.default"
_TOKEN_REFRESH_MARGIN = 300 # Refresh 5 minutes before expiry
_credential = DefaultAzureCredential()
_lock = threading.RLock() # reentrant ā get_cache() calls _get_token() while holding the lock
_token_cache: dict = {}
def _get_token():
"""Return a valid Entra ID token, refreshing if within expiry margin."""
with _lock:
now = time.time()
if not _token_cache or now >= _token_cache["expires_on"] - _TOKEN_REFRESH_MARGIN:
tok = _credential.get_token(_TOKEN_SCOPE)
_token_cache["token"] = tok.token
_token_cache["expires_on"] = tok.expires_on
return _token_cache["token"]
def create_redis_client() -> redis.Redis:
"""Create a Redis client. Token is refreshed on each call via _get_token()."""
return redis.Redis(
host=os.environ["REDIS_HOST"],
port=int(os.environ.get("REDIS_PORT", 6380)),
ssl=True,
username=_REDIS_USERNAME,
password=_get_token(),
decode_responses=True,
)
def get_cache() -> redis.Redis:
"""Lazy/refreshing accessor ā call this from request handlers instead of holding a module-level client.
Recreates the client when the cached token is within the refresh margin and closes the prior one."""
global _client
with _lock:
now = time.time()
if _client is None or now >= _token_cache.get("expires_on", 0) - _TOKEN_REFRESH_MARGIN:
old = _client
_client = create_redis_client()
if old is not None:
try:
old.close()
except Exception:
pass
return _client
_client: redis.Redis | None = None
```
> ā ļø Entra ID tokens expire in ~1 hour. The `_get_token()` helper refreshes proactively 5 minutes before expiry, and `get_cache()` recreates the underlying client so in-flight pool connections pick up the new token. Always call `get_cache()` from request handlers ā never cache the client at module scope across the token lifetime.
### Usage
```python
from cache import get_cache
def get_cached(key: str):
cache = get_cache()
value = cache.get(key)
if value is None:
value = "computed-value"
cache.setex(key, 300, value) # TTL 5 minutes
return value
```
## Files to Modify
| File | Action |
|------|--------|
| `cache.py` | Create ā Redis client with token refresh |
| `main.py` | Modify ā use cache client |
| `requirements.txt` | Modify ā add redis, azure-identity |
README.md 3.5 KB
# SQL Database Recipe ā REFERENCE ONLY
Adds Azure SQL Database integration to an App Service base template.
## Overview
This recipe composes with a Web API or Web App base template to add Azure SQL Database connectivity. It provides the IaC delta (SQL Server, database, firewall, RBAC) and per-language source code using EF Core, Prisma, or SQLAlchemy.
## Integration Type
| Aspect | Value |
|--------|-------|
| **Database** | Azure SQL Database (Serverless or Provisioned) |
| **Auth** | Managed identity (passwordless) |
| **ORM** | EF Core (.NET), Prisma (Node.js), SQLAlchemy (Python) |
| **Hosting** | App Service (from base template) |
| **Local Auth** | Disabled in Azure (Entra ID only); local dev may use SQL auth |
## Composition Steps
Apply these steps AFTER `azd init -t <base-template>`:
| # | Step | Details |
|---|------|---------|
| 1 | **Add IaC module** | Add SQL Server + Database Bicep module to `infra/app/` |
| 2 | **Wire into main** | Add module reference in `main.bicep` |
| 3 | **Add app settings** | Add SQL connection string (managed identity) |
| 4 | **Add source code** | Add ORM models, DbContext/client setup from `source/{lang}.md` |
| 5 | **Add packages** | Add ORM and SQL client packages |
| 6 | **Run migrations** | Add postprovision hook for DB schema setup |
## App Settings
| Setting | Value | Purpose |
|---------|-------|---------|
| `AZURE_SQL_CONNECTION_STRING` | `Server=tcp:{server}.database.windows.net;Database={db};Authentication=Active Directory Managed Identity;User Id={clientId};` | Passwordless SQL connection |
### Bicep App Settings Block
```bicep
appSettings: [
{
name: 'AZURE_SQL_CONNECTION_STRING'
value: 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${sqlDatabase.name};Authentication=Active Directory Managed Identity;User Id=${managedIdentity.properties.clientId};Encrypt=True;TrustServerCertificate=False;'
}
]
```
> **Note:** The `Authentication=Active Directory Managed Identity` setting tells the SQL client to use the app's managed identity. No passwords are stored.
## RBAC Roles Required
| Role | GUID | Scope | Purpose |
|------|------|-------|---------|
| **SQL DB Contributor** | `9b7fa17d-e63e-47b0-bb0a-15c516ac86ec` | SQL Server | Manage database |
| **Directory Readers** | `88d8e3e3-8f55-4a1e-953a-9b9898b8876b` | Entra ID | Read directory for MI auth |
> **Important:** Data plane access uses SQL-level roles (`db_datareader`, `db_datawriter`), assigned via a postprovision script that runs `ALTER ROLE` statements.
## Resources Created
| Resource | Type | Purpose |
|----------|------|---------|
| SQL Server | `Microsoft.Sql/servers` | Logical SQL server |
| SQL Database | `Microsoft.Sql/servers/databases` | Application database |
| Firewall Rule | `Microsoft.Sql/servers/firewallRules` | Allow Azure services |
| Entra Admin | `Microsoft.Sql/servers/administrators` | Set MI as admin |
## Files
| Path | Description |
|------|-------------|
| [source/dotnet.md](source/dotnet.md) | C# EF Core integration |
| [source/python.md](source/python.md) | Python SQLAlchemy integration |
| [source/nodejs.md](source/nodejs.md) | Node.js Prisma integration |
## References
- [Azure SQL passwordless connections](https://learn.microsoft.com/en-us/azure/azure-sql/database/azure-sql-passwordless-migration)
- [EF Core with Azure SQL](https://learn.microsoft.com/en-us/ef/core/providers/sql-server/)
- [Tutorial: App Service with SQL](https://learn.microsoft.com/en-us/azure/app-service/tutorial-dotnetcore-sqldb-app)
dotnet.md 2.9 KB
# SQL Database ā C# (.NET) ā REFERENCE ONLY
## Entity Framework Core Setup
Add EF Core with Azure SQL and managed identity support to an ASP.NET Core app.
### NuGet Packages
```xml
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.*" />
<PackageReference Include="Azure.Identity" Version="1.13.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.*" />
```
### DbContext
Create `Data/AppDbContext.cs`:
```csharp
using Microsoft.EntityFrameworkCore;
namespace MyApp.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<TodoItem> TodoItems => Set<TodoItem>();
}
public class TodoItem
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public bool IsComplete { get; set; }
}
```
### Program.cs (additions)
Add these lines to `Program.cs` ā do NOT replace the file:
```csharp
using Microsoft.EntityFrameworkCore;
using MyApp.Data;
// Add after builder creation
var connectionString = builder.Configuration.GetConnectionString("AZURE_SQL")
?? builder.Configuration["AZURE_SQL_CONNECTION_STRING"];
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));
// Add health check for SQL
builder.Services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>();
// After app build ā map health checks so App Service probes reflect SQL connectivity
app.MapHealthChecks("/health");
```
### API Endpoints
Add to `Program.cs` after `app` is built:
```csharp
app.MapGet("/api/todos", async (AppDbContext db) =>
await db.TodoItems.ToListAsync());
app.MapGet("/api/todos/{id}", async (int id, AppDbContext db) =>
await db.TodoItems.FindAsync(id) is TodoItem todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/api/todos", async (TodoItem todo, AppDbContext db) =>
{
db.TodoItems.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/api/todos/{todo.Id}", todo);
});
```
### appsettings.json
```json
{
"ConnectionStrings": {
"AZURE_SQL": "Server=localhost;Database=myapp;Trusted_Connection=true;"
}
}
```
> In production, the `AZURE_SQL_CONNECTION_STRING` app setting from Azure overrides this with the managed identity connection string.
### EF Migrations (postprovision hook)
Create `infra/scripts/setup-db.sh`:
```bash
#!/bin/bash
dotnet ef database update --project src/api
```
## Files to Add
| File | Action |
|------|--------|
| `Data/AppDbContext.cs` | Create ā DbContext + entity models |
| `Program.cs` | Modify ā add DbContext registration + endpoints |
| `appsettings.json` | Modify ā add ConnectionStrings section |
## Common Patterns
- Always use `AddHealthChecks().AddDbContextCheck<>()` for SQL health monitoring
- Use `AsNoTracking()` for read-only queries to improve performance
- Apply migrations via postprovision hook, not at app startup
nodejs.md 3.0 KB
# SQL Database ā Node.js ā REFERENCE ONLY
## Prisma + Azure SQL Setup
### npm Packages
```bash
npm install prisma @prisma/client mssql
npm install -D prisma
npx prisma init
```
### Prisma Schema
Update `prisma/schema.prisma`:
```prisma
datasource db {
provider = "sqlserver"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model TodoItem {
id Int @id @default(autoincrement())
title String @db.NVarChar(200)
isComplete Boolean @default(false)
}
```
### Database Client
Create `src/db.js`:
```javascript
const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient();
module.exports = { prisma };
```
### API Endpoints
Add to `src/index.js`:
```javascript
const { prisma } = require("./db");
app.get("/api/todos", async (req, res) => {
const todos = await prisma.todoItem.findMany();
res.json(todos);
});
app.post("/api/todos", async (req, res) => {
const body = req.body || {};
const title = typeof body.title === "string" ? body.title.trim() : "";
if (!title) {
return res.status(400).json({ error: "title is required and must be a non-empty string" });
}
const isComplete = body.isComplete === undefined ? false : body.isComplete;
if (typeof isComplete !== "boolean") {
return res.status(400).json({ error: "isComplete must be a boolean" });
}
const todo = await prisma.todoItem.create({ data: { title, isComplete } });
res.status(201).json(todo);
});
app.get("/api/todos/:id", async (req, res) => {
const todo = await prisma.todoItem.findUnique({
where: { id: parseInt(req.params.id) },
});
if (!todo) return res.status(404).json({ error: "Not found" });
res.json(todo);
});
```
### Connection String
**Azure (managed identity):**
Set as app setting in Bicep ā the format Prisma uses for SQL Server:
```
sqlserver://<server>.database.windows.net:1433;database=<db>;authentication=ActiveDirectoryMsi;clientId=<mi-client-id>
```
```bicep
{ name: 'DATABASE_URL', value: 'sqlserver://${sqlServer.properties.fullyQualifiedDomainName}:1433;database=${sqlDatabase.name};authentication=ActiveDirectoryMsi;clientId=${managedIdentity.properties.clientId}' }
```
**Local development (SQL auth):**
```bash
# .env (local only ā never commit)
DATABASE_URL="sqlserver://localhost:1433;database=myapp;user=<username>;password=<your-strong-password>;trustServerCertificate=true"
```
### EF Migrations (postprovision hook)
Create `infra/scripts/setup-db.sh`:
```bash
#!/bin/bash
npx prisma migrate deploy --schema src/prisma/schema.prisma
```
## Files to Add
| File | Action |
|------|--------|
| `prisma/schema.prisma` | Create ā data model definitions |
| `src/db.js` | Create ā Prisma client instance |
| `src/index.js` | Modify ā add CRUD endpoints |
| `package.json` | Modify ā add prisma, @prisma/client |
## Common Patterns
- Use `prisma.todoItem.findMany()` for read-only queries
- Use `prisma.$disconnect()` in shutdown hooks for clean teardown
- Run `npx prisma generate` after schema changes to regenerate the client
python.md 4.4 KB
# SQL Database ā Python ā REFERENCE ONLY
## SQLAlchemy Setup
Add SQLAlchemy with Azure SQL and managed identity support to a FastAPI app.
### Requirements
Add to `requirements.txt`:
```
sqlalchemy>=2.0
pyodbc
azure-identity
fastapi
uvicorn
```
### Database Configuration
Create `database.py`:
```python
import os
import struct
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from azure.identity import ManagedIdentityCredential
class Base(DeclarativeBase):
pass
def create_db_engine():
"""Create SQLAlchemy engine using managed identity or connection string."""
conn_str = os.environ.get("AZURE_SQL_CONNECTION_STRING")
if conn_str:
# Local dev: AZURE_SQL_CONNECTION_STRING is a SQLAlchemy URL
# e.g. mssql+pyodbc://sa:password@localhost/myapp?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes
return create_engine(conn_str)
# Azure: use managed identity token (refreshed per-connection so pool stays valid past 1 hour)
server = os.environ["AZURE_SQL_SERVER"]
database = os.environ["AZURE_SQL_DATABASE"]
client_id = os.environ.get("AZURE_CLIENT_ID")
credential = ManagedIdentityCredential(client_id=client_id) if client_id else ManagedIdentityCredential()
odbc_conn = (
f"DRIVER={{ODBC Driver 18 for SQL Server}};"
f"SERVER={server};"
f"DATABASE={database};"
f"Encrypt=yes;TrustServerCertificate=no;"
)
engine = create_engine(
"mssql+pyodbc://",
connect_args={"odbc_connect": odbc_conn},
)
@event.listens_for(engine, "do_connect")
def provide_token(dialect, conn_rec, cargs, cparams):
# Fetch a fresh token for every new physical connection ā pool refills won't fail after 1h
token = credential.get_token("https://database.windows.net/.default")
token_bytes = token.token.encode("utf-16-le")
token_struct = struct.pack(f"<I{len(token_bytes)}s", len(token_bytes), token_bytes)
cparams["attrs_before"] = {1256: token_struct} # SQL_COPT_SS_ACCESS_TOKEN
return engine
engine = create_db_engine()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
```
### Models
Create `models.py`:
```python
from sqlalchemy import Column, Integer, String, Boolean
from database import Base
class TodoItem(Base):
__tablename__ = "todo_items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), nullable=False)
is_complete = Column(Boolean, default=False)
```
### API Endpoints
Add to `main.py` ā do NOT replace existing routes:
```python
from fastapi import Depends, HTTPException
from sqlalchemy.orm import Session
from database import SessionLocal, engine, Base
from models import TodoItem
from pydantic import BaseModel
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
class TodoCreate(BaseModel):
title: str
is_complete: bool = False
@app.get("/api/todos")
def list_todos(db: Session = Depends(get_db)):
return db.query(TodoItem).all()
@app.get("/api/todos/{todo_id}")
def get_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(TodoItem).filter(TodoItem.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Not found")
return todo
@app.post("/api/todos", status_code=201)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = TodoItem(**todo.model_dump())
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
```
### Local Development
For local development without managed identity, use SQL authentication:
```python
# .env (local only ā never commit)
AZURE_SQL_CONNECTION_STRING=mssql+pyodbc://<username>:<password>@localhost/myapp?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes
```
## Files to Add
| File | Action |
|------|--------|
| `database.py` | Create ā engine, session, managed identity token |
| `models.py` | Create ā SQLAlchemy ORM models |
| `main.py` | Modify ā add CRUD endpoints + DB dependency |
| `requirements.txt` | Modify ā add sqlalchemy, pyodbc, azure-identity |
## Common Patterns
- Use `Depends(get_db)` for session lifecycle management
- Use `Base.metadata.create_all()` only for dev; use Alembic migrations in production
- Never store SQL passwords in app settings ā use managed identity tokens
selection.md 2.8 KB
# Template Selection Decision Tree ā REFERENCE ONLY
**CRITICAL**: Check for specific scenario indicators IN ORDER before defaulting to Web API.
**Architecture**: All deployments start from a base template per language/scenario. Integrations are applied as [composable recipes](recipes/README.md) on top of the base. See [composition.md](recipes/composition.md) for the merge algorithm.
Cross-reference with [App Service overview](https://learn.microsoft.com/en-us/azure/app-service/overview) and [official AZD gallery templates](https://azure.github.io/awesome-azd/?tags=appservice).
```
1. Is this a full-stack web app with server-side rendering?
Indicators: Razor Pages, MVC views, Next.js SSR, Nuxt, Django templates,
server-rendered HTML, .cshtml, EJS/Pug, Jinja2
āāāŗ YES ā web-app base template (see [web-app.md](web-app.md))
2. Is this a background worker or WebJob?
Indicators: WebJob, IHostedService, BackgroundService, worker,
queue processor, scheduled task (no HTTP endpoints)
āāāŗ YES ā Use Worker Service template (out of scope ā see Functions or Container Apps)
3. Does it use Azure SQL or PostgreSQL?
Indicators: DbContext, EF Core, Prisma, SQLAlchemy, connection string,
Microsoft.EntityFrameworkCore, @prisma/client, psycopg2
āāāŗ YES ā Web API base + sql recipe (IaC + RBAC + source)
Recipe: recipes/sql/ ā
Available
4. Does it use Cosmos DB?
Indicators: CosmosClient, @azure/cosmos, azure-cosmos, CosmosDBConnection,
Microsoft.Azure.Cosmos, container.read_item
āāāŗ YES ā Web API base + cosmos recipe (IaC + RBAC + source)
Recipe: recipes/cosmos/ ā
Available
5. Does it require authentication?
Indicators: [Authorize], Microsoft.Identity.Web, passport-azure-ad,
msal, EasyAuth, /.auth/login, authsettingsV2
āāāŗ YES ā base + auth recipe (Easy Auth or MSAL config)
Recipe: recipes/auth/ ā
Available
6. Does it use Redis caching?
Indicators: IDistributedCache, StackExchange.Redis, ioredis, redis,
azure-cache-redis, aioredis
āāāŗ YES ā base + redis recipe (IaC + RBAC + source)
Recipe: recipes/redis/ ā
Available
7. DEFAULT ā Web API base template by runtime (see [web-api.md](web-api.md))
```
## Base Templates
| Scenario | Template Reference |
|----------|-------------------|
| Full-stack web app with server-side rendering | [web-app.md](web-app.md) |
| REST API / headless backend (default) | [web-api.md](web-api.md) |
## Recipe Types
| Type | IaC Delta? | Examples |
|------|-----------|----------|
| **Full recipe** | Yes ā Bicep module + RBAC + networking | sql, cosmos, redis, auth |
| **Source-only** | No ā only application code patterns | health checks |
## Critical Rules
> See [Critical Rules](recipes/composition.md#critical-rules) in composition.md (canonical).
web-api.md 4.9 KB
# Web API Base Template ā REFERENCE ONLY
Default template for REST API workloads on Azure App Service. Use when no specific integration is detected or as the base for recipe composition.
## Templates by Runtime
| Runtime | AZD Template | Framework |
|---------|-------------|-----------|
| C# (.NET) | `azd init -t todo-csharp` | ASP.NET Core Minimal API |
| Node.js (JS) | `azd init -t todo-nodejs-mongo` | Express.js |
| Node.js (TS) | `azd init -t todo-nodejs-mongo` | Express.js (TypeScript) |
| Python | `azd init -t todo-python-mongo` | FastAPI / Flask |
| Java | `azd init -t todo-java-mongo` | Spring Boot |
**Browse all:** [Awesome AZD App Service](https://azure.github.io/awesome-azd/?tags=appservice)
> ā ļø The AZD templates above include Mongo/Cosmos by default. When using as a pure Web API base, strip the database layer before applying a recipe:
> - Remove `infra/app/db.bicep` (or equivalent database module)
> - Remove Cosmos/Mongo module references from `infra/main.bicep`
> - Remove Cosmos/Mongo packages from the application source
>
> Then apply the appropriate [recipe](recipes/README.md) for your data store.
## Project Structure
```
project-root/
āāā azure.yaml # AZD service config
āāā infra/
ā āāā main.bicep # Main orchestrator
ā āāā main.parameters.json
ā āāā app/
ā āāā web.bicep # App Service + Plan
ā āāā web-network.bicep # VNet integration (conditional)
āāā src/
āāā api/ # Application source
```
## App Service Plan Configuration
```bicep
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: '${name}-plan'
location: location
tags: tags
sku: {
name: 'B1' // Dev/test ā use P1v3+ for production
}
properties: {
reserved: true // Required for Linux
}
}
```
| Environment | Recommended SKU | Notes |
|-------------|----------------|-------|
| Dev/Test | B1 | Basic tier, no autoscale |
| Production | P1v3 | Premium v3, autoscale, VNet integration |
| High-scale | P2v3 / P3v3 | Higher CPU/memory |
## Health Check Endpoint
> **CRITICAL**: Always include a health check endpoint. App Service uses it for instance monitoring.
Configure in App Service:
```bicep
properties: {
siteConfig: {
healthCheckPath: '/health'
}
}
```
### Health check by language
**C# (ASP.NET Core)**
```csharp
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
```
**Node.js (Express)**
```javascript
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
```
**Python (FastAPI)**
```python
@app.get("/health")
async def health():
return {"status": "healthy"}
```
**Java (Spring Boot)**
```java
@GetMapping("/health")
public Map<String, String> health() {
return Map.of("status", "healthy");
}
```
## Dockerfile Reference
All App Service deployments can use container deployment. Each runtime should include a Dockerfile:
**Python (FastAPI)**
```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
**Node.js**
```dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
```
## App Settings (Managed Identity)
> š” Call `mcp_bicep_get_az_resource_type_schema` with resource type `Microsoft.Web/sites` to validate properties before generating this resource.
```bicep
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
properties: {
siteConfig: {
appSettings: [
{ name: 'WEBSITE_HEALTHCHECK_MAXPINGFAILURES', value: '3' }
{ name: 'SCM_DO_BUILD_DURING_DEPLOYMENT', value: 'true' }
]
}
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {}
}
}
tags: {
'azd-service-name': 'api'
}
}
```
> ā ļø **Without `azd-service-name` tag, `azd deploy` fails with:**
> `resource not found: unable to find a resource tagged with 'azd-service-name: api'`
>
> The tag value must match the `services.<name>` key in `azure.yaml`. Use `api` for Web API services.
## Deployment
```bash
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init -t <template> -e "$ENV_NAME" --no-prompt
azd env set AZURE_LOCATION eastus2
azd up --no-prompt
```
**PowerShell:**
```powershell
$ENV_NAME = "$(Split-Path -Leaf (Get-Location) | ForEach-Object { $_.ToLower() -replace '[ _]','-' })-dev"
azd init -t <template> -e $ENV_NAME --no-prompt
azd env set AZURE_LOCATION eastus2
azd up --no-prompt
```
## References
- [App Service overview](https://learn.microsoft.com/en-us/azure/app-service/overview)
- [App Service best practices](https://learn.microsoft.com/en-us/azure/app-service/app-service-best-practices)
- [Health check](https://learn.microsoft.com/en-us/azure/app-service/monitor-instances-health-check)
web-app.md 4.8 KB
# Full-Stack Web App Template ā REFERENCE ONLY
Template for server-side rendered web applications on Azure App Service. Use for MVC, Razor Pages, Next.js SSR, Django, or React+API patterns.
## Templates by Pattern
| Pattern | AZD Template | Framework |
|---------|-------------|-----------|
| React + C# API | `azd init -t todo-csharp` | React SPA + ASP.NET Core API |
| React + Node.js API | `azd init -t todo-nodejs-mongo` | React SPA + Express API |
| React + Python API | `azd init -t todo-python-mongo` | React SPA + FastAPI |
| React + Java API | `azd init -t todo-java-mongo` | React SPA + Spring Boot |
**Browse all:** [Awesome AZD](https://azure.github.io/awesome-azd/?tags=appservice)
> š” **Tip:** For static frontends with API backends, consider Azure Static Web Apps instead. Use App Service when you need full server-side rendering.
## Architecture Patterns
### Pattern A: Single App Service (API + static files)
```
App Service (Linux)
āāā /api/* ā Backend routes
āāā /* ā Static files (React/Vue build output)
```
Best for: Simple apps, MVPs, Razor Pages, Django with templates.
### Pattern B: Separate frontend + backend
```
App Service (frontend) ā React/Next.js SSR
ā
āāāāŗ App Service (backend) ā REST API
```
Best for: Independent scaling, team separation, microservices.
## Project Structure (Single App)
```
project-root/
āāā azure.yaml
āāā infra/
ā āāā main.bicep
ā āāā app/
ā āāā web.bicep
āāā src/
āāā api/ # Backend
ā āāā Program.cs # or app.py / index.js
ā āāā ...
āāā web/ # Frontend (built output served as static)
āāā package.json
āāā src/
```
## azure.yaml (multi-service)
```yaml
name: my-web-app
services:
web:
project: ./src/web
host: appservice
language: js
dist: build
api:
project: ./src/api
host: appservice
language: csharp
```
## Server-Side Rendering Examples
### ASP.NET Core MVC / Razor Pages
```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseStaticFiles();
app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
app.Run();
```
### Next.js (SSR on App Service)
```javascript
// next.config.js
module.exports = {
output: 'standalone', // Required for App Service deployment
};
```
Startup command in App Service:
```bash
node server.js
```
### Django
```python
# settings.py
ALLOWED_HOSTS = ['.azurewebsites.net', 'localhost']
STATIC_ROOT = BASE_DIR / 'staticfiles'
CSRF_TRUSTED_ORIGINS = ['https://*.azurewebsites.net']
```
### Express + React (served from build)
```javascript
const express = require('express');
const path = require('path');
const app = express();
app.use(express.json());
app.use('/api', apiRouter);
app.use(express.static(path.join(__dirname, '../web/build')));
app.get('/health', (req, res) => res.json({ status: 'healthy' }));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../web/build/index.html'));
});
app.listen(process.env.PORT || 3000);
```
## App Service Configuration
```bicep
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
properties: {
siteConfig: {
linuxFxVersion: 'NODE|20-lts' // or DOTNETCORE|8.0, PYTHON|3.12
healthCheckPath: '/health'
appCommandLine: '' // Custom startup command if needed
appSettings: [
{ name: 'SCM_DO_BUILD_DURING_DEPLOYMENT', value: 'true' }
{ name: 'WEBSITE_NODE_DEFAULT_VERSION', value: '~20' }
]
}
}
tags: {
'azd-service-name': 'web'
}
}
```
## Startup Commands by Runtime
| Runtime | Startup Command | Notes |
|---------|----------------|-------|
| ASP.NET Core | (auto-detected) | No command needed |
| Node.js | `node server.js` or `npm start` | Set via `appCommandLine` |
| Python (Django) | `gunicorn myapp.wsgi` | Use gunicorn for production |
| Python (FastAPI) | `uvicorn main:app --host 0.0.0.0` | Use uvicorn for ASGI |
| Next.js SSR | `node server.js` | Use `output: 'standalone'` |
## Deployment
```bash
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init -t <template> -e "$ENV_NAME" --no-prompt
azd env set AZURE_LOCATION eastus2
azd up --no-prompt
```
**PowerShell:**
```powershell
$ENV_NAME = "$(Split-Path -Leaf (Get-Location) | ForEach-Object { $_.ToLower() -replace '[ _]','-' })-dev"
azd init -t <template> -e $ENV_NAME --no-prompt
azd env set AZURE_LOCATION eastus2
azd up --no-prompt
```
## References
- [Deploy to App Service](https://learn.microsoft.com/en-us/azure/app-service/quickstart-dotnetcore)
- [Node.js on App Service](https://learn.microsoft.com/en-us/azure/app-service/quickstart-nodejs)
- [Python on App Service](https://learn.microsoft.com/en-us/azure/app-service/quickstart-python)
README.md 1.4 KB
# Azure Container Apps
Serverless container hosting for microservices, APIs, and background workers.
## When to Use
- Microservices and APIs
- Background processing workers
- Event-driven applications
- Web applications (server-rendered)
- Any containerized workload that doesn't need full Kubernetes
## Service Type in azure.yaml
```yaml
services:
my-api:
host: containerapp
project: ./src/my-api
docker:
path: ./Dockerfile
```
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| Container Apps Environment | Hosting environment |
| Container Registry | Image storage |
| Log Analytics Workspace | Logging |
| Application Insights | Monitoring |
## Common Configurations
| Workload Type | Ingress | Min Replicas | Scaling |
|---------------|---------|--------------|---------|
| API Service | External | 1 (avoid cold starts) | HTTP-based |
| Background Worker | None | 0 (scale to zero) | Queue-based |
| Web Application | External | 1 | HTTP-based |
## References
- [Bicep Patterns](bicep.md)
- [Terraform Patterns](terraform.md)
- [Scaling Patterns](scaling.md)
- [Health Probes](health-probes.md)
- [Environment Variables](environment.md)
- [Day-2 Operations](day2-operations.md)
- [Networking & Ingress](networking.md)
- [Revisions & Traffic Splitting](revisions.md)
bicep.md 4.9 KB
# Container Apps Bicep Patterns
> **ā ļø Container Registry Naming:** If using Azure Container Registry, names must be alphanumeric only (5-50 characters). Use `replace()` to remove hyphens: `replace('cr${environmentName}${resourceSuffix}', '-', '')`
> **ā ļø Two-Phase Deployment (Mandatory):** To avoid a circular dependency when scoping the AcrPull role assignment to a Bicep module, use the two-phase pattern below:
> - **Phase 1:** Deploy ACR and Container App with a public placeholder image and **no** `registries` block.
> - **Phase 2:** Deploy the AcrPull role assignment as a **separate module** using outputs from Phase 1.
>
> The Bicep template does **not** need a `registries` block. `azd deploy` handles the registry/identity link by calling `az containerapp registry set --server <acr-server> --identity system` via the Azure API before updating the container image. If you are not using AZD, you must run this command manually before switching the image to an ACR-hosted image; otherwise the app will hit image pull failures.
## Phase 1: Container App Module (No Registry Link)
```bicep
// Placeholder image allows provisioning before app image exists in ACR.
// No registries block in Bicep ā azd deploy configures the registry/identity link
// (az containerapp registry set --identity system) and updates the image via the Azure API.
// If not using AZD, run that command manually before switching to an ACR-hosted image.
param containerImageName string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
name: appName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
environmentId: containerAppsEnvironment.id
configuration: {
ingress: {
external: true
targetPort: 8080
transport: 'auto'
}
// No registries block in Bicep. azd deploy sets the registry/identity link via
// 'az containerapp registry set --identity system' before pushing the real image.
// Without this step (or its manual equivalent), the app will fail to pull from ACR.
}
template: {
containers: [
{
name: serviceName
image: containerImageName
resources: {
cpu: json('0.5')
memory: '1Gi'
}
}
]
}
}
}
output systemAssignedMIPrincipalId string = containerApp.identity.principalId
```
## Phase 2: AcrPull Role Assignment Module (acr-pull-role.bicep)
Place this in a **separate module file** so neither the ACR module nor the Container App module depends on it, eliminating the circular dependency.
```bicep
// acr-pull-role.bicep
param acrName string
param principalId string
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = {
name: acrName
}
resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(containerRegistry.id, principalId, 'acrpull')
scope: containerRegistry
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'7f951dda-4ed3-4680-a7ca-43fe172d538d'
)
principalId: principalId
principalType: 'ServicePrincipal'
}
}
```
> š” **Tip:** Always set `principalType: 'ServicePrincipal'` for managed identities. This avoids a Graph API lookup and speeds up role assignment propagation.
## Wiring Phase 1 and Phase 2 in main.bicep
```bicep
// Phase 1: ACR and Container App ā neither module depends on the role assignment
module containerRegistry './modules/container-registry.bicep' = {
name: 'containerRegistry'
scope: rg
params: { /* ... */ }
}
module api './modules/container-app.bicep' = {
name: 'api'
scope: rg
params: {
containerImageName: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
// No registries param in Bicep ā azd deploy configures the registry/identity link
// and updates the image via the Azure API after provisioning.
/* ... */
}
}
// Phase 2: Role assignment depends on outputs of both Phase 1 modules,
// but neither Phase 1 module depends on this ā no circular dependency.
module acrPullRole './modules/acr-pull-role.bicep' = {
name: 'acrPullRole'
scope: rg
params: {
acrName: containerRegistry.outputs.name
principalId: api.outputs.systemAssignedMIPrincipalId
}
}
```
## Container Apps Environment
```bicep
resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = {
name: '${resourcePrefix}-env'
location: location
properties: {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalyticsWorkspace.properties.customerId
sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
}
}
}
}
```
day2-operations.md 5.1 KB
# Container Apps Day-2 Operations
Operational tasks for running Container Apps in production: restart, exec, logs, environment updates, and secret rotation.
## Restart and Lifecycle
| Action | Command |
|--------|---------|
| Restart active revision | `az containerapp revision restart -n $APP -g $RG --revision $REV` |
| Scale to zero (stop) | `az containerapp update -n $APP -g $RG --min-replicas 0 --max-replicas <current-max>` |
| Resume (restore scaling) | `az containerapp update -n $APP -g $RG --min-replicas <previous-min> --max-replicas <current-max>` |
| List replicas | `az containerapp replica list -n $APP -g $RG --revision $REV` |
> š” **Tip:** Restarting a revision replaces all running replicas gracefully. No new revision is created.
## Exec into a Container
Open a shell inside a running replica for debugging:
```bash
# Interactive shell
az containerapp exec -n $APP -g $RG --command /bin/sh
# Target a specific replica and container
az containerapp exec -n $APP -g $RG \
--replica $REPLICA_NAME \
--container $CONTAINER_NAME \
--command /bin/sh
```
> ā ļø **Warning:** Exec sessions are for debugging only. Changes to the container filesystem are lost on restart.
## Log Streaming
### Real-time Logs
```bash
# Stream system logs
az containerapp logs show -n $APP -g $RG --type system --follow
# Stream application (console) logs
az containerapp logs show -n $APP -g $RG --type console --follow
# Filter to a specific revision or replica
az containerapp logs show -n $APP -g $RG \
--type console --revision $REV --follow
```
### Log Analytics (KQL)
Query historical logs via the Container Apps environment's Log Analytics workspace:
```kql
// Azure Monitor destination (new environments ā default)
ContainerAppConsoleLogs
| where ContainerAppName == "my-app"
| where TimeGenerated > ago(1h)
| project TimeGenerated, Log, RevisionName
| order by TimeGenerated desc
// Log Analytics destination (legacy environments)
// ContainerAppConsoleLogs_CL
// | where ContainerAppName_s == "my-app"
// | project TimeGenerated, Log_s, RevisionName_s
```
## Environment Variable Updates
Updating environment variables creates a new revision:
```bash
# Set or update env vars
az containerapp update -n $APP -g $RG \
--set-env-vars "DB_HOST=newhost.postgres.database.azure.com" \
"CACHE_TTL=300"
# Remove an env var
az containerapp update -n $APP -g $RG \
--remove-env-vars "OLD_SETTING"
```
### Bicep ā Env Vars with Secret References
```bicep
configuration: {
secrets: [
{ name: 'db-password', value: dbPassword } // or use keyVaultUrl + identity
]
}
template: {
containers: [
{
name: 'api'
image: '${acrName}.azurecr.io/api:latest'
env: [
{ name: 'DB_HOST', value: dbHost }
{ name: 'DB_PASSWORD', secretRef: 'db-password' }
]
}
]
}
```
## Secret Management
### Create and Update Secrets
```bash
# Add a secret (use Key Vault references in production ā avoid plaintext secrets)
az containerapp secret set -n $APP -g $RG \
--secrets "db-password=<secret-value>"
# Reference a Key Vault secret (managed identity required)
az containerapp secret set -n $APP -g $RG \
--secrets "db-password=keyvaultref:https://myvault.vault.azure.net/secrets/db-pwd,identityref:/subscriptions/.../userAssignedIdentities/my-id"
```
> ā ļø **Warning:** Avoid passing plaintext secrets on the command line ā they may appear in shell history and process listings. Prefer Key Vault references. If you must use plaintext, use shell substitution like `--secrets "key=$(cat secret.txt)"` to avoid literals on the command line.
> š” **Tip:** Use Key Vault references instead of plain-text secrets. The Container App pulls the latest value on each new revision or replica start.
### Secret Rotation Workflow
1. Update the secret value in Key Vault
2. Create a new revision to pick up the updated value:
```bash
az containerapp revision copy -n $APP -g $RG
```
3. Verify the new revision is healthy
4. Shift traffic to the new revision
> ā ļø **Warning:** Existing replicas do NOT hot-reload Key Vault references. A new revision or replica restart is required.
## Health Monitoring
| Check | How |
|-------|-----|
| Revision health | `az containerapp revision list -n $APP -g $RG -o table` |
| Replica status | `az containerapp replica list -n $APP -g $RG --revision $REV` |
| System logs | `az containerapp logs show -n $APP -g $RG --type system` |
| Metrics | Azure Monitor ā Container Apps ā Requests, Replicas, CPU, Memory |
## Common Troubleshooting
| Symptom | Likely Cause | Remediation |
|---------|-------------|-------------|
| Replica crash loop | App startup failure | Check console logs; exec into container |
| 0 replicas running | Scale-to-zero + no traffic | Set `minReplicas: 1` or send a request |
| Env var not updating | Old revision still serving traffic | Verify the latest revision exists, then update ingress traffic weights or route to `latestRevision` |
| Secret value stale | Key Vault ref not refreshed | Create or verify the refreshed revision, then shift traffic to that revision |
| High memory/CPU | Resource limits too low | Update `resources.cpu` / `resources.memory` |
environment.md 1.3 KB
# Container Apps Environment Variables
## Standard Environment Variables
```bicep
env: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: applicationInsights.properties.ConnectionString
}
{
name: 'AZURE_CLIENT_ID'
value: managedIdentity.properties.clientId
}
]
```
## Secret References (Key Vault)
Use secrets for sensitive values:
```bicep
configuration: {
secrets: [
{
name: 'database-url'
keyVaultUrl: 'https://myvault.vault.azure.net/secrets/database-url'
identity: managedIdentity.id
}
]
}
template: {
containers: [
{
env: [
{
name: 'DATABASE_URL'
secretRef: 'database-url'
}
]
}
]
}
```
## Common Variables
| Variable | Source | Notes |
|----------|--------|-------|
| `APPLICATIONINSIGHTS_CONNECTION_STRING` | App Insights | Telemetry |
| `AZURE_CLIENT_ID` | Managed Identity | SDK auth |
| `DATABASE_URL` | Key Vault secret | Connection string |
| `REDIS_URL` | Key Vault secret | Cache connection |
## Best Practices
- Never hardcode secrets in Bicep
- Use Key Vault references for all sensitive values
- Use Managed Identity for authentication
- Set `AZURE_CLIENT_ID` for SDK-based auth
health-probes.md 1.2 KB
# Container Apps Health Probes
Always configure health probes for production workloads.
## Liveness Probe
Detects if container is alive. Failure triggers restart.
```bicep
probes: [
{
type: 'liveness'
httpGet: {
path: '/health'
port: 8080
}
initialDelaySeconds: 10
periodSeconds: 30
failureThreshold: 3
}
]
```
## Readiness Probe
Detects if container is ready to receive traffic.
```bicep
probes: [
{
type: 'readiness'
httpGet: {
path: '/ready'
port: 8080
}
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
}
]
```
## Startup Probe
For slow-starting containers. Delays other probes until startup succeeds.
```bicep
probes: [
{
type: 'startup'
httpGet: {
path: '/health'
port: 8080
}
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 // 30 * 10s = 5 min max startup
}
]
```
## Recommendations
| Probe | Path | Initial Delay | Period |
|-------|------|---------------|--------|
| Liveness | `/health` | 10s | 30s |
| Readiness | `/ready` | 5s | 10s |
| Startup | `/health` | 0s | 10s |
networking.md 5.1 KB
# Container Apps Networking
VNet integration, ingress configuration, custom domains, and TLS for Container Apps.
## Ingress Modes
| Mode | Visibility | Use Case |
|------|-----------|----------|
| External | Internet-accessible | Public APIs, web apps |
| Internal | Not internet-accessible; reachable within the environment and VNet (if VNet-injected) | Microservices, back-end APIs |
| Disabled | No HTTP ingress | Background workers, queue processors |
### Bicep ā External Ingress
```bicep
configuration: {
ingress: {
external: true
targetPort: 8080
transport: 'auto'
allowInsecure: false
}
}
```
### Bicep ā Internal Ingress
```bicep
configuration: {
ingress: {
external: false
targetPort: 8080
}
}
```
> š” **Tip:** Internal apps get a `*.internal.<env-default-domain>` FQDN. This is accessible from within the Container Apps environment and, when the environment is VNet-injected, also from the VNet.
## VNet Integration
Container Apps run inside an environment that can be injected into a VNet subnet.
### Subnet Requirements
| Requirement | Workload Profiles (default) | Consumption-only (legacy) |
|------------|---------------------------|--------------------------|
| Minimum subnet size | `/27` (32 addresses) | `/23` (512 addresses) |
| Delegation | `Microsoft.App/environments` | `Microsoft.App/environments` |
| Dedicated | Subnet must be exclusive to the Container Apps environment | Same |
### Bicep ā VNet-Integrated Environment
```bicep
resource subnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = {
parent: vnet
name: 'container-apps-subnet'
properties: {
addressPrefix: '10.0.16.0/27'
delegations: [
{
name: 'Microsoft.App.environments'
properties: { serviceName: 'Microsoft.App/environments' }
}
]
}
}
resource env 'Microsoft.App/managedEnvironments@2024-03-01' = {
name: envName
location: location
properties: {
vnetConfiguration: {
infrastructureSubnetId: subnet.id
internal: false // true for internal-only environment
}
}
}
```
> ā ļø **Warning:** VNet configuration is set at environment creation and cannot be changed afterward. Plan your network topology before creating the environment.
## Custom Domains
### Steps
1. Add a CNAME or A record pointing to the Container App's FQDN or static IP
2. Bind the custom domain to the Container App
3. Configure a managed or custom TLS certificate
```bash
# Register custom domain (hostname only ā no cert provisioned yet)
az containerapp hostname add -n $APP -g $RG --hostname app.contoso.com
# Bind managed certificate (provisions and attaches TLS cert)
az containerapp hostname bind -n $APP -g $RG \
--hostname app.contoso.com \
--environment $ENV_NAME \
--validation-method CNAME
```
### DNS Configuration
| Record Type | Name | Value |
|------------|------|-------|
| CNAME | `app.contoso.com` | `<app-name>.<region>.azurecontainerapps.io` |
| TXT (verification) | `asuid.app.contoso.com` | `<verification-id>` |
| A (apex domain) | `contoso.com` | Environment static IP |
> š” **Tip:** Use `az containerapp show -n $APP -g $RG --query properties.configuration.ingress.fqdn` to get the target FQDN for DNS records.
## TLS Configuration
### Managed Certificates
Azure automatically provisions and renews TLS certificates for custom domains ā no manual cert management required.
> ā ļø **Prerequisites:** Managed certificates require the app to be externally reachable with valid **public** DNS (CNAME or HTTP validation). They do **not** work for internal environments or apps behind private DNS. For private/internal scenarios, bring your own certificate via `az containerapp ssl upload`.
## IP Restrictions
> ā ļø **Warning:** IP restriction rules are evaluated in **array order** (first match wins). The `priority` field does not exist in the Container Apps API ā order your rules carefully in the array.
Allow-only rules implicitly deny all traffic not matching any rule. Deny-only rules implicitly allow all other traffic.
```bicep
configuration: {
ingress: {
external: true
targetPort: 8080
ipSecurityRestrictions: [
{
name: 'allow-office'
action: 'Allow'
ipAddressRange: '203.0.113.0/24'
description: 'Office network'
}
{
name: 'allow-vpn'
action: 'Allow'
ipAddressRange: '198.51.100.0/24'
description: 'VPN gateway'
}
]
}
}
```
## Network Topology Summary
| Topology | Environment `internal` | Ingress `external` | Access |
|----------|----------------------|-------------------|--------|
| Public app | `false` | `true` | Internet + VNet |
| Internal microservice | `false` | `false` | Same environment; VNet if environment is VNet-injected |
| Fully private (VNet-wide) | `true` | `true` | VNet only (no public IP); accessible from anywhere in the VNet |
| Fully private (env-only) | `true` | `false` | VNet only (no public IP); accessible only within the Container Apps environment |
> ā ļø **Warning:** An internal environment has no public IP. You need VPN, ExpressRoute, or a jump box to reach apps in an internal environment.
revisions.md 4.9 KB
# Container Apps Revision Management
Revisions are immutable snapshots of a Container App version. Use them for blue/green deployments, canary releases, and instant rollback.
## Revision Modes
| Mode | Behavior | Use Case |
|------|----------|----------|
| `Single` | New revision replaces old immediately | Simple apps, dev/test |
| `Multiple` | Multiple revisions run simultaneously with traffic splitting | Production blue/green, canary |
## Setting Revision Mode (Bicep)
```bicep
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
name: appName
location: location
properties: {
configuration: {
activeRevisionsMode: 'Multiple'
ingress: {
external: true
targetPort: 8080
traffic: [
{ latestRevision: true, weight: 100 }
]
}
}
}
}
```
> š” **Tip:** In Bicep/ARM deployments, you typically can't predictably target a specific new revision name. Use `latestRevision: true` in Bicep for initial deployment, then configure traffic splitting or labels via CLI after the new revision is created.
> ā ļø **Warning:** For blue/green workflows, you must first pin traffic to a named revision before deploying a new one. With `latestRevision: true, weight: 100`, new revisions automatically receive all traffic ā there is no validation window.
## Traffic Splitting Patterns
### Blue/Green Deployment
Pin traffic to the current revision, deploy a new one, validate, then switch:
```bash
# Deploy new revision and capture its name from the update output
NEW_REV=$(az containerapp update -n $APP -g $RG --image $NEW_IMAGE \
--query properties.latestRevisionName -o tsv)
# Test the new revision directly via its revision-specific URL
az containerapp revision list -n $APP -g $RG -o table
# Switch 100% traffic to the new revision
az containerapp ingress traffic set -n $APP -g $RG \
--revision-weight "$NEW_REV=100"
```
### Canary Release
Gradually shift traffic to validate the new revision under load:
| Phase | Current | Canary | Duration |
|-------|---------|--------|----------|
| 1 | 90% | 10% | 15 min |
| 2 | 50% | 50% | 30 min |
| 3 | 0% | 100% | ā |
```bash
# List revisions to identify current stable and new canary
az containerapp revision list -n $APP -g $RG -o table
STABLE_REV=<stable-revision-name>
CANARY_REV=<canary-revision-name>
az containerapp ingress traffic set -n $APP -g $RG \
--revision-weight "$STABLE_REV=90" "$CANARY_REV=10"
```
### Label-Based Routing
Use labels instead of revision names for stable references:
```bash
# Assign labels
az containerapp revision label add -n $APP -g $RG \
--label stable --revision "$APP--v1"
az containerapp revision label add -n $APP -g $RG \
--label canary --revision "$APP--v2"
# Route by label
az containerapp ingress traffic set -n $APP -g $RG \
--label-weight stable=80 canary=20
```
> š” **Tip:** Label-based routing lets you swap revision targets without changing traffic rules.
## Rollback
Revert instantly by redirecting all traffic to a known-good revision (e.g., one labeled `stable`):
```bash
# List active revisions and identify the known-good one
az containerapp revision list -n $APP -g $RG -o table
# Roll back using label-based routing (preferred)
az containerapp ingress traffic set -n $APP -g $RG \
--label-weight stable=100
# Or roll back to a specific revision by name
az containerapp ingress traffic set -n $APP -g $RG \
--revision-weight "<known-good-revision-name>=100"
```
## Revision Lifecycle
| Action | Command |
|--------|---------|
| List revisions | `az containerapp revision list -n $APP -g $RG` |
| Show revision details | `az containerapp revision show -n $APP -g $RG --revision $REV` |
| Activate a revision | `az containerapp revision activate -n $APP -g $RG --revision $REV` |
| Deactivate a revision | `az containerapp revision deactivate -n $APP -g $RG --revision $REV` |
| Restart a revision | `az containerapp revision restart -n $APP -g $RG --revision $REV` |
> ā ļø **Warning:** Deactivated revisions cannot receive traffic. Reactivate before routing traffic to them.
## Terraform Traffic Config
```hcl
resource "azurerm_container_app" "app" {
name = var.app_name
container_app_environment_id = azurerm_container_app_environment.env.id
resource_group_name = azurerm_resource_group.rg.name
revision_mode = "Multiple"
ingress {
external_enabled = true
target_port = 8080
traffic_weight {
label = "stable"
revision_suffix = "v1"
percentage = 80
}
traffic_weight {
label = "canary"
revision_suffix = "v2"
percentage = 20
}
}
}
```
## Recommendations
| Scenario | Revision Mode | Traffic Strategy |
|----------|---------------|------------------|
| Dev/Test | Single | N/A ā auto-replace |
| Prod API | Multiple | Blue/green with instant swap |
| High-risk change | Multiple | Canary (10% ā 50% ā 100%) |
| Feature flags | Multiple | Label-based routing |
scaling.md 1.4 KB
# Container Apps Scaling Patterns
## HTTP-based Scaling
Best for APIs and web applications:
```bicep
scale: {
minReplicas: 1
maxReplicas: 10
rules: [
{
name: 'http-scaling'
http: {
metadata: {
concurrentRequests: '100'
}
}
}
]
}
```
## Queue-based Scaling
Best for background workers:
```bicep
scale: {
minReplicas: 0
maxReplicas: 30
rules: [
{
name: 'queue-scaling'
azureQueue: {
queueName: 'orders'
queueLength: 10
auth: [
{
secretRef: 'storage-connection'
triggerParameter: 'connection'
}
]
}
}
]
}
```
## Service Bus Scaling
```bicep
scale: {
minReplicas: 0
maxReplicas: 20
rules: [
{
name: 'servicebus-scaling'
custom: {
type: 'azure-servicebus'
metadata: {
queueName: 'myqueue'
messageCount: '5'
}
auth: [
{
secretRef: 'servicebus-connection'
triggerParameter: 'connection'
}
]
}
}
]
}
```
## Recommendations
| Workload | Min Replicas | Max Replicas | Rule Type |
|----------|--------------|--------------|-----------|
| Production API | 1 | 10-20 | HTTP |
| Dev/Test API | 0 | 5 | HTTP |
| Background Worker | 0 | 30+ | Queue/Event |
| Scheduled Job | 0 | 1 | KEDA cron |
terraform.md 6.1 KB
# Container Apps Terraform Patterns
> **ā ļø Container Registry Naming:** ACR names must be alphanumeric only (5-50 characters). Use Terraform's `replace()` function when constructing the value passed to `azurecaf_name`, or otherwise strip hyphens manually.
> **ā ļø Two-Phase Deployment (Mandatory):** To avoid the chicken-and-egg problem where Terraform tries to create a Container App referencing an ACR image that doesn't exist yet:
> - **Phase 1 (`terraform apply`):** Deploy ACR and Container App with a **public placeholder image** and **no `registry` block**.
> - **Phase 2 (post-apply CLI):** Build/push the app image to ACR, configure the registry/identity link, then update the Container App image.
>
> This mirrors the [Bicep two-phase pattern](bicep.md). Without it, `terraform apply` fails with `ContainerAppOperationError` because the image doesn't exist in ACR yet.
> **ā ļø ACR Authentication ā Managed Identity Only:** Do **not** use `admin_enabled = true` on `azurerm_container_registry` or add a `registry` block with `username`/`password_secret_name` to the Container App. Admin credentials are a security risk and leak secrets into Terraform state. Always use **managed identity** with an `AcrPull` role assignment, and configure the registry link via `az containerapp registry set --identity system` in Phase 2.
## Phase 1: Container App Resource (No Registry Block)
```hcl
# Placeholder image allows provisioning before the app image exists in ACR.
# No registry block in Terraform during Phase 1 ā the registry/identity link is
# configured via CLI after provisioning (see Phase 2 below).
# Do NOT add a registry block with username/password_secret_name ā use managed identity.
resource "azurerm_container_app" "api" {
name = azurecaf_name.container_app.result
container_app_environment_id = azurerm_container_app_environment.env.id
resource_group_name = azurerm_resource_group.rg.name
revision_mode = "Single"
identity {
type = "SystemAssigned"
}
tags = merge(var.tags, {
"azd-service-name" = "api"
})
template {
min_replicas = 1
max_replicas = 3
container {
name = "api"
image = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"
cpu = 0.25
memory = "0.5Gi"
}
}
ingress {
external_enabled = true
target_port = 8080
traffic_weight {
latest_revision = true
percentage = 100
}
}
# Phase 2 updates the image and configures the registry/identity link via CLI.
# Prevent Terraform from reverting those changes on subsequent applies.
# Do NOT add a registry block here ā use `az containerapp registry set --identity system`.
lifecycle {
ignore_changes = [
template[0].container[0].image,
registry,
]
}
}
```
## AcrPull Role Assignment
Deploy the `AcrPull` role assignment as a **separate resource** that depends on the Container App (to read its system-assigned identity principal ID). Neither the ACR nor the Container App depends on this resource, so there is no circular dependency.
```hcl
resource "azurerm_role_assignment" "api_acr_pull" {
scope = azurerm_container_registry.acr.id
role_definition_name = "AcrPull"
principal_id = azurerm_container_app.api.identity[0].principal_id
principal_type = "ServicePrincipal"
}
```
> š” **Tip:** Always set `principal_type = "ServicePrincipal"` for managed identities. This skips the Graph API lookup and speeds up role assignment propagation.
## Phase 2: Post-Apply Deployment (CLI)
After `terraform apply` succeeds, run these commands to build the real image and switch the Container App to it:
```bash
ACR_NAME=$(terraform output -raw acr_name)
ACR_SERVER=$(terraform output -raw acr_login_server)
APP_NAME=$(terraform output -raw container_app_name)
RG_NAME=$(terraform output -raw resource_group_name)
# 1. Build and push the application image to ACR
az acr build --registry $ACR_NAME --image myapp:latest ./src/api
# 2. Configure the registry/identity link (managed identity, no passwords)
az containerapp registry set \
--name $APP_NAME \
--resource-group $RG_NAME \
--server $ACR_SERVER \
--identity system
# 3. Update the Container App to use the real image
az containerapp update \
--name $APP_NAME \
--resource-group $RG_NAME \
--image $ACR_SERVER/myapp:latest
```
**PowerShell:**
```powershell
$AcrName = terraform output -raw acr_name
$AcrServer = terraform output -raw acr_login_server
$AppName = terraform output -raw container_app_name
$RgName = terraform output -raw resource_group_name
# 1. Build and push the application image to ACR
az acr build --registry $AcrName --image myapp:latest ./src/api
# 2. Configure the registry/identity link (managed identity, no passwords)
az containerapp registry set `
--name $AppName `
--resource-group $RgName `
--server $AcrServer `
--identity system
# 3. Update the Container App to use the real image
az containerapp update `
--name $AppName `
--resource-group $RgName `
--image "$AcrServer/myapp:latest"
```
> ā ļø **Warning:** Step 2 requires the `AcrPull` role assignment to have propagated (1ā5 minutes after `terraform apply`). If the image pull fails, wait and retry. See the **azure-deploy** skill's `references/pre-deploy-checklist.md` (Container Apps + ACR pre-deploy RBAC health check).
## Terraform Outputs
Export the values needed by Phase 2:
```hcl
output "acr_name" {
value = azurerm_container_registry.acr.name
}
output "acr_login_server" {
value = azurerm_container_registry.acr.login_server
}
output "container_app_name" {
value = azurerm_container_app.api.name
}
output "resource_group_name" {
value = azurerm_resource_group.rg.name
}
output "api_url" {
value = "https://${azurerm_container_app.api.ingress[0].fqdn}"
}
```
## Container Apps Environment
```hcl
resource "azurerm_container_app_environment" "env" {
name = azurecaf_name.container_app_env.result
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
log_analytics_workspace_id = azurerm_log_analytics_workspace.logs.id
}
```
README.md 1.5 KB
# Azure Cosmos DB
Globally distributed, multi-model database for low-latency data at scale.
## When to Use
- Global distribution requirements
- Multi-model data (document, graph, key-value)
- Variable and unpredictable throughput
- Low-latency reads/writes at scale
- Flexible schema requirements
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| None required | Cosmos DB is fully managed |
| Key Vault | Store connection strings (recommended) |
## Capacity Modes
| Mode | Use Case | Billing |
|------|----------|---------|
| **Serverless** | Variable/low traffic, dev/test | Per request |
| **Provisioned** | Predictable workloads | Per RU/s |
| **Autoscale** | Variable but predictable peaks | Per max RU/s |
## Consistency Levels
| Level | Latency | Consistency |
|-------|---------|-------------|
| Strong | Highest | Linearizable |
| Bounded Staleness | High | Bounded |
| Session | Medium | Session-scoped |
| Consistent Prefix | Low | Prefix ordering |
| Eventual | Lowest | Eventually consistent |
Recommendation: Use **Session** for most applications.
## Environment Variables
| Variable | Value |
|----------|-------|
| `COSMOS_CONNECTION_STRING` | Primary connection string (Key Vault reference) |
| `COSMOS_ENDPOINT` | Account endpoint URL |
| `COSMOS_DATABASE` | Database name |
## References
- [Bicep Patterns](bicep.md)
- [Partition Key Selection](partitioning.md)
- [SDK Connection Patterns](sdk.md)
bicep.md 2.0 KB
# Cosmos DB Bicep Patterns
## Account
```bicep
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = {
name: '${resourcePrefix}-cosmos-${uniqueHash}'
location: location
kind: 'GlobalDocumentDB'
properties: {
databaseAccountOfferType: 'Standard'
locations: [
{
locationName: location
failoverPriority: 0
isZoneRedundant: false
}
]
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
}
capabilities: [
{
name: 'EnableServerless'
}
]
}
}
```
## Database
```bicep
resource cosmosDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-04-15' = {
parent: cosmosAccount
name: 'appdb'
properties: {
resource: {
id: 'appdb'
}
}
}
```
## Container
```bicep
resource cosmosContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-04-15' = {
parent: cosmosDatabase
name: 'items'
properties: {
resource: {
id: 'items'
partitionKey: {
paths: ['/partitionKey']
kind: 'Hash'
}
indexingPolicy: {
indexingMode: 'consistent'
includedPaths: [
{ path: '/*' }
]
}
}
}
}
```
## Autoscale Container
```bicep
resource cosmosContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-04-15' = {
parent: cosmosDatabase
name: 'items'
properties: {
resource: {
id: 'items'
partitionKey: {
paths: ['/partitionKey']
kind: 'Hash'
}
}
options: {
autoscaleSettings: {
maxThroughput: 4000
}
}
}
}
```
## Global Distribution
```bicep
properties: {
locations: [
{
locationName: 'East US'
failoverPriority: 0
}
{
locationName: 'West US'
failoverPriority: 1
}
]
enableMultipleWriteLocations: true
}
```
partitioning.md 1.1 KB
# Cosmos DB Partition Key Selection
## Good Partition Keys
A good partition key should have:
- **High cardinality** - Many distinct values
- **Even data distribution** - No hot partitions
- **Even request distribution** - Balanced workload
- **Used in most queries** - Enables efficient routing
## Examples by Scenario
| Scenario | Partition Key | Reason |
|----------|---------------|--------|
| User-centric data | `/userId` | Queries typically filter by user |
| Multi-tenant apps | `/tenantId` | Isolates tenant data |
| E-commerce orders | `/customerId` | Orders queried by customer |
| IoT telemetry | `/deviceId` | High cardinality, even distribution |
## Hierarchical Partition Keys
For complex scenarios, use hierarchical keys:
```bicep
partitionKey: {
paths: ['/tenantId', '/userId']
kind: 'MultiHash'
}
```
## Anti-Patterns
Avoid these partition key choices:
| Bad Choice | Problem |
|------------|---------|
| Timestamp | Creates hot partitions |
| Boolean values | Only 2 partitions |
| Low cardinality enums | Uneven distribution |
| Random GUID | Can't query efficiently |
sdk.md 1.6 KB
# Cosmos DB SDK Connection Patterns
## Node.js
```javascript
const { CosmosClient } = require("@azure/cosmos");
const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING);
const database = client.database("appdb");
const container = database.container("items");
// Query example
const { resources } = await container.items
.query("SELECT * FROM c WHERE c.userId = @userId", {
parameters: [{ name: "@userId", value: userId }]
})
.fetchAll();
```
## Python
```python
from azure.cosmos import CosmosClient
import os
client = CosmosClient.from_connection_string(os.environ["COSMOS_CONNECTION_STRING"])
database = client.get_database_client("appdb")
container = database.get_container_client("items")
# Query example
items = container.query_items(
query="SELECT * FROM c WHERE c.userId = @userId",
parameters=[{"name": "@userId", "value": user_id}]
)
```
## .NET
```csharp
using Microsoft.Azure.Cosmos;
var client = new CosmosClient(Environment.GetEnvironmentVariable("COSMOS_CONNECTION_STRING"));
var database = client.GetDatabase("appdb");
var container = database.GetContainer("items");
// Query example
var query = new QueryDefinition("SELECT * FROM c WHERE c.userId = @userId")
.WithParameter("@userId", userId);
var iterator = container.GetItemQueryIterator<dynamic>(query);
```
## Best Practices
| Practice | Reason |
|----------|--------|
| Reuse client instances | Connection pooling |
| Use parameterized queries | SQL injection prevention |
| Set appropriate timeouts | Handle transient failures |
| Enable diagnostics in dev | Debug RU consumption |
README.md 4.9 KB
# Durable Task Scheduler
Build reliable, fault-tolerant workflows using durable execution with Azure Durable Task Scheduler.
## When to Use
- Long-running workflows requiring state persistence
- Distributed transactions with compensating actions (saga pattern)
- Multi-step orchestrations with checkpointing
- Fan-out/fan-in parallel processing
- Workflows requiring human interaction or external events
- Stateful entities (aggregators, counters, state machines)
- Multi-agent AI orchestration
- Data processing pipelines
## Framework Selection
| Framework | Best For | Hosting |
|-----------|----------|---------|
| **Durable Functions** | Serverless event-driven apps | Azure Functions |
| **Durable Task SDKs** | Any compute (containers, VMs) | Azure Container Apps, Azure Kubernetes Service, App Service, VMs |
> **š” TIP**: Use Durable Functions for serverless with built-in triggers. Use Durable Task SDKs for hosting flexibility.
## Quick Start - Local Emulator
```bash
# Start the emulator (see https://mcr.microsoft.com/v2/dts/dts-emulator/tags/list for available versions)
docker pull mcr.microsoft.com/dts/dts-emulator:latest
docker run -d -p 8080:8080 -p 8082:8082 --name dts-emulator mcr.microsoft.com/dts/dts-emulator:latest
# Dashboard available at http://localhost:8082
```
## Workflow Patterns
| Pattern | Use When |
|---------|----------|
| **Function Chaining** | Sequential steps, each depends on previous |
| **Fan-Out/Fan-In** | Parallel processing with aggregated results |
| **Async HTTP APIs** | Long-running operations with HTTP polling |
| **Monitor** | Periodic polling with configurable timeouts |
| **Human Interaction** | Workflow pauses for external input/approval |
| **Saga** | Distributed transactions with compensation |
| **Durable Entities** | Stateful objects (counters, accounts) |
## Connection & Authentication
| Environment | Connection String |
|-------------|-------------------|
| Local Development (Emulator) | `Endpoint=http://localhost:8080;Authentication=None;TaskHub=default` |
| Azure (System-Assigned MI) | `Endpoint=https://<scheduler>.durabletask.io;Authentication=ManagedIdentity;TaskHub=default` |
| Azure (User-Assigned MI) | `Endpoint=https://<scheduler>.durabletask.io;Authentication=ManagedIdentity;ClientID=<uami-client-id>;TaskHub=default` |
> **ā ļø NOTE**: Durable Task Scheduler uses identity-based authentication only ā no connection strings with keys. When using a User-Assigned Managed Identity (UAMI), you must include the `ClientID` in the connection string.
## Troubleshooting
| Error | Cause | Fix |
|-------|-------|-----|
| **403 PermissionDenied** on gRPC call (e.g., `client.start_new()`) | Function App managed identity lacks RBAC on the Durable Task Scheduler resource, or IP allowlist blocks traffic | 1. Assign `Durable Task Data Contributor` role (`0ad04412-c4d5-4796-b79c-f76d14c8d402`) to the identity (SAMI or UAMI) scoped to the Durable Task Scheduler resource. For UAMI, also ensure the connection string includes `ClientID=<uami-client-id>`. 2. Ensure the scheduler's `ipAllowlist` includes `0.0.0.0/0` (an empty list denies all traffic). 3. RBAC propagation can take up to 10 minutes ā restart the Function App after assigning roles. |
| **Connection refused** to emulator | Emulator container not running or wrong port | Verify container is running: `docker ps` and confirm port 8080 is mapped |
| **403 despite correct RBAC** | Scheduler IP allowlist is empty (denies all) | Set `ipAllowlist: ['0.0.0.0/0']` in Bicep or update via CLI: `az durabletask scheduler update --ip-allowlist '0.0.0.0/0'` |
| **TaskHub not found** | Task hub not provisioned or name mismatch | Ensure the `TaskHub` parameter in the `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` matches the provisioned task hub name |
| **403 Forbidden** on DTS dashboard | Deploying user lacks RBAC on the scheduler | Assign `Durable Task Data Contributor` role to your own user identity (not just the Function App MI) scoped to the scheduler resource ā see [Bicep Patterns](bicep.md) for the dashboard role assignment snippet |
## References
- [.NET](dotnet.md) ā packages, setup, examples, determinism, retry, SDK
- [Python](python.md) ā packages, setup, examples, determinism, retry, SDK
- [Java](java.md) ā dependencies, setup, examples, determinism, retry, SDK
- [JavaScript](javascript.md) ā packages, setup, examples, determinism, retry, SDK
- [Bicep Patterns](bicep.md) ā scheduler, task hub, RBAC, CLI provisioning
- [Official Documentation](https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler)
- [Durable Functions Overview](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview)
- [Sample Repository](https://github.com/Azure-Samples/Durable-Task-Scheduler)
- [Choosing an Orchestration Framework](https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/choose-orchestration-framework)
bicep.md 4.8 KB
# Durable Task Scheduler ā Bicep Patterns
Bicep templates for provisioning the Durable Task Scheduler, task hubs, and RBAC role assignments.
## Scheduler + Task Hub
```bicep
// Parameters ā define these at file level or pass from a parent module
param schedulerName string
param location string = resourceGroup().location
@allowed(['Consumption', 'Dedicated'])
@description('Use Consumption for quickstarts/variable workloads, Dedicated for high-demand/predictable throughput')
param skuName string = 'Consumption'
resource scheduler 'Microsoft.DurableTask/schedulers@2025-11-01' = {
name: schedulerName
location: location
properties: {
sku: { name: skuName }
ipAllowlist: ['0.0.0.0/0'] // Required: empty list denies all traffic
}
}
resource taskHub 'Microsoft.DurableTask/schedulers/taskHubs@2025-11-01' = {
parent: scheduler
name: 'default'
}
```
## SKU Selection
| SKU | Best For |
|-----|----------|
| **Consumption** | quickstarts, variable or bursty workloads, pay-per-use |
| **Dedicated** | High-demand workloads, predictable throughput requirements |
> **š” TIP**: Start with `Consumption` for development and variable workloads. Switch to `Dedicated` when you need consistent, high-throughput performance.
> **ā ļø WARNING**: The scheduler's `ipAllowlist` **must** include at least one entry (e.g., `['0.0.0.0/0']` for allow-all). An empty array `[]` denies **all** traffic, causing 403 errors on gRPC calls even with correct RBAC.
## RBAC ā Durable Task Data Contributor
The Function App's managed identity **must** have the `Durable Task Data Contributor` role on the scheduler resource. Without it, the app receives **403 PermissionDenied** on gRPC calls.
```bicep
// Assumes the UAMI principal ID is passed from the base template's identity module
param functionAppPrincipalId string
var durableTaskDataContributorRoleId = '0ad04412-c4d5-4796-b79c-f76d14c8d402'
resource durableTaskRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(scheduler.id, functionAppPrincipalId, durableTaskDataContributorRoleId)
scope: scheduler
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', durableTaskDataContributorRoleId)
principalId: functionAppPrincipalId
principalType: 'ServicePrincipal'
}
}
```
## RBAC ā Dashboard Access for Developers
To allow developers to view orchestration status and history in the [DTS dashboard](https://portal.azure.com), assign the same `Durable Task Data Contributor` role to the deploying user's identity. Without this, the dashboard returns **403 Forbidden**.
```bicep
// Accept the deploying user's principal ID (azd auto-populates this from AZURE_PRINCIPAL_ID)
param principalId string = ''
resource dashboardRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(principalId)) {
name: guid(scheduler.id, principalId, durableTaskDataContributorRoleId)
scope: scheduler
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', durableTaskDataContributorRoleId)
principalId: principalId
principalType: 'User'
}
}
```
> **š” TIP**: This is the same role used for the Function App's managed identity, but assigned with `principalType: 'User'` to the developer. See the [sample repo](https://github.com/Azure-Samples/Durable-Task-Scheduler/blob/main/samples/infra/main.bicep) for a full example.
## Connection String App Setting
Include these entries in the Function App resource's `siteConfig.appSettings` array:
```bicep
// UAMI client ID from base template identity module - REQUIRED for UAMI auth
param uamiClientId string
{
name: 'DURABLE_TASK_SCHEDULER_CONNECTION_STRING'
value: 'Endpoint=${scheduler.properties.endpoint};TaskHub=${taskHub.name};Authentication=ManagedIdentity;ClientID=${uamiClientId}'
}
```
> **ā ļø IMPORTANT**: The base templates use User Assigned Managed Identity (UAMI). You **must** include `ClientID=<uami-client-id>` in the connection string. Without it, the Durable Task SDK cannot resolve the correct identity.
> **ā ļø WARNING**: Always use `scheduler.properties.endpoint` to get the scheduler URL. Do **not** construct it manually ā the endpoint includes a hash suffix and region (e.g., `https://myscheduler-abc123.westus2.durabletask.io`).
## Provision via CLI
> **š” TIP**: When hosting Durable Functions, use a **Flex Consumption** plan (`FC1` SKU) rather than the legacy Consumption plan (`Y1`). Flex Consumption supports identity-based storage connections natively and handles deployment artifacts correctly.
```bash
# Install the durabletask CLI extension (if not already installed)
az extension add --name durabletask
# Create scheduler (consumption SKU for getting started)
az durabletask scheduler create \
--resource-group myResourceGroup \
--name my-scheduler \
--location eastus \
--sku consumption
```
dotnet.md 6.1 KB
# Durable Task Scheduler ā .NET
## Learn More
- [Durable Task Scheduler documentation](https://learn.microsoft.com/azure/durable-task-scheduler/)
- [Durable Functions .NET isolated worker guide](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-dotnet-isolated-overview)
## Durable Functions Setup
### Required NuGet Packages
```xml
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.14.1" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.4.0" />
<PackageReference Include="Azure.Identity" Version="1.17.1" />
</ItemGroup>
```
> **š” Finding latest versions**: Search [nuget.org](https://www.nuget.org/) for each package name to find the current stable version. Look for the `Microsoft.Azure.Functions.Worker.Extensions.DurableTask` and `Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged` packages.
### host.json
```json
{
"version": "2.0",
"extensions": {
"durableTask": {
"storageProvider": {
"type": "azureManaged",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
},
"hubName": "default"
}
}
}
```
> **š” NOTE**: .NET isolated uses the `DurableTask.AzureManaged` NuGet package, which registers the `azureManaged` storage provider type. Other runtimes (Python, Java, JavaScript) use extension bundles and require `durabletask-scheduler` instead ā see the respective language files. All runtimes use the same `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` environment variable.
### local.settings.json
```json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
}
}
```
## Minimal Example
```csharp
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
public static class DurableFunctionsApp
{
[Function("HttpStart")]
public static async Task<HttpResponseData> HttpStart(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(MyOrchestration));
return await client.CreateCheckStatusResponseAsync(req, instanceId);
}
[Function(nameof(MyOrchestration))]
public static async Task<string> MyOrchestration([OrchestrationTrigger] TaskOrchestrationContext context)
{
var result1 = await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo");
var result2 = await context.CallActivityAsync<string>(nameof(SayHello), "Seattle");
return $"{result1}, {result2}";
}
[Function(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name) => $"Hello {name}!";
}
```
## Workflow Patterns
### Fan-Out/Fan-In
```csharp
[Function(nameof(FanOutFanIn))]
public static async Task<string[]> FanOutFanIn([OrchestrationTrigger] TaskOrchestrationContext context)
{
string[] cities = { "Tokyo", "Seattle", "London", "Paris", "Berlin" };
// Fan-out: schedule all in parallel
var tasks = cities.Select(city => context.CallActivityAsync<string>(nameof(SayHello), city));
// Fan-in: wait for all
return await Task.WhenAll(tasks);
}
```
### Human Interaction
```csharp
[Function(nameof(ApprovalWorkflow))]
public static async Task<string> ApprovalWorkflow([OrchestrationTrigger] TaskOrchestrationContext context)
{
await context.CallActivityAsync(nameof(SendApprovalRequest), context.GetInput<string>());
// Wait for approval event with timeout
using var cts = new CancellationTokenSource();
var approvalTask = context.WaitForExternalEvent<bool>("ApprovalEvent");
var timeoutTask = context.CreateTimer(context.CurrentUtcDateTime.AddDays(3), cts.Token);
var winner = await Task.WhenAny(approvalTask, timeoutTask);
if (winner == approvalTask)
{
cts.Cancel();
return await approvalTask ? "Approved" : "Rejected";
}
return "Timed out";
}
```
## Orchestration Determinism
| ā NEVER | ā
ALWAYS USE |
|----------|--------------|
| `DateTime.Now` | `context.CurrentUtcDateTime` |
| `Guid.NewGuid()` | `context.NewGuid()` |
| `Random` | Pass random values from activities |
| `Task.Delay()`, `Thread.Sleep()` | `context.CreateTimer()` |
| Direct I/O, HTTP, database | `context.CallActivityAsync()` |
### Replay-Safe Logging
```csharp
[Function(nameof(MyOrchestration))]
public static async Task<string> MyOrchestration([OrchestrationTrigger] TaskOrchestrationContext context)
{
ILogger logger = context.CreateReplaySafeLogger(nameof(MyOrchestration));
logger.LogInformation("Started"); // Only logs once, not on replay
return await context.CallActivityAsync<string>(nameof(MyActivity), "input");
}
```
## Error Handling & Retry
```csharp
var retryOptions = new TaskOptions
{
Retry = new RetryPolicy(
maxNumberOfAttempts: 3,
firstRetryInterval: TimeSpan.FromSeconds(5),
backoffCoefficient: 2.0,
maxRetryInterval: TimeSpan.FromMinutes(1))
};
var input = context.GetInput<string>();
try
{
await context.CallActivityAsync<string>(nameof(UnreliableService), input, retryOptions);
}
catch (TaskFailedException ex)
{
context.SetCustomStatus(new { Error = ex.Message });
await context.CallActivityAsync(nameof(CompensationActivity), input);
}
```
## Durable Task SDK (Non-Functions)
For applications running outside Azure Functions (containers, VMs, Azure Container Apps, Azure Kubernetes Service):
```csharp
var connectionString = "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// Worker
builder.Services.AddDurableTaskWorker()
.AddTasks(registry => registry.AddAllGeneratedTasks())
.UseDurableTaskScheduler(connectionString);
// Client
var client = DurableTaskClientBuilder.UseDurableTaskScheduler(connectionString).Build();
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync("MyOrchestration", input);
```
java.md 7.7 KB
# Durable Task Scheduler ā Java
## Learn More
- [Durable Task Scheduler documentation](https://learn.microsoft.com/azure/durable-task-scheduler/)
- [Durable Functions Java guide](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview?tabs=java)
## Durable Functions Setup
### Required Maven Dependencies
```xml
<dependencies>
<dependency>
<groupId>com.microsoft.azure.functions</groupId>
<artifactId>azure-functions-java-library</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>com.microsoft</groupId>
<artifactId>durabletask-azure-functions</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
```
> **š” Finding latest versions**: Search [Maven Central](https://central.sonatype.com/) for `durabletask-azure-functions` (group: `com.microsoft`) to find the current stable version.
### host.json
```json
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "durabletask-scheduler",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
```
### local.settings.json
```json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "java",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
}
}
```
## Minimal Example
```java
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;
import com.microsoft.durabletask.*;
import com.microsoft.durabletask.azurefunctions.*;
public class DurableFunctionsApp {
@FunctionName("HttpStart")
public HttpResponseMessage httpStart(
@HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION)
HttpRequestMessage<Void> request,
@DurableClientInput(name = "durableContext") DurableClientContext durableContext) {
DurableTaskClient client = durableContext.getClient();
String instanceId = client.scheduleNewOrchestrationInstance("MyOrchestration");
return durableContext.createCheckStatusResponse(request, instanceId);
}
@FunctionName("MyOrchestration")
public String myOrchestration(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String result1 = ctx.callActivity("SayHello", "Tokyo", String.class).await();
String result2 = ctx.callActivity("SayHello", "Seattle", String.class).await();
return result1 + ", " + result2;
}
@FunctionName("SayHello")
public String sayHello(@DurableActivityTrigger(name = "name") String name) {
return "Hello " + name + "!";
}
}
```
## Workflow Patterns
### Fan-Out/Fan-In
```java
@FunctionName("FanOutFanIn")
public List<String> fanOutFanIn(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
String[] cities = {"Tokyo", "Seattle", "London", "Paris", "Berlin"};
List<Task<String>> parallelTasks = new ArrayList<>();
// Fan-out: schedule all activities in parallel
for (String city : cities) {
parallelTasks.add(ctx.callActivity("SayHello", city, String.class));
}
// Fan-in: wait for all to complete
List<String> results = new ArrayList<>();
for (Task<String> task : parallelTasks) {
results.add(task.await());
}
return results;
}
```
### Human Interaction
```java
@FunctionName("ApprovalWorkflow")
public String approvalWorkflow(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
ctx.callActivity("SendApprovalRequest", ctx.getInput(String.class)).await();
// Wait for approval event with timeout
Task<Boolean> approvalTask = ctx.waitForExternalEvent("ApprovalEvent", Boolean.class);
Task<Void> timeoutTask = ctx.createTimer(Duration.ofDays(3));
Task<?> winner = ctx.anyOf(approvalTask, timeoutTask).await();
if (winner == approvalTask) {
return approvalTask.await() ? "Approved" : "Rejected";
}
return "Timed out";
}
```
## Orchestration Determinism
| ā NEVER | ā
ALWAYS USE |
|----------|--------------|
| `System.currentTimeMillis()` | `ctx.getCurrentInstant()` |
| `UUID.randomUUID()` | Pass random values from activities |
| `Thread.sleep()` | `ctx.createTimer()` |
| Direct I/O, HTTP, database | `ctx.callActivity()` |
### Replay-Safe Logging
```java
private static final java.util.logging.Logger logger =
java.util.logging.Logger.getLogger("MyOrchestration");
@FunctionName("MyOrchestration")
public String myOrchestration(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
// Use isReplaying to avoid duplicate logs
if (!ctx.getIsReplaying()) {
logger.info("Started"); // Only logs once, not on replay
}
return ctx.callActivity("MyActivity", "input", String.class).await();
}
```
## Error Handling & Retry
```java
@FunctionName("WorkflowWithRetry")
public String workflowWithRetry(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
TaskOptions retryOptions = new TaskOptions(new RetryPolicy(
3, // maxNumberOfAttempts
Duration.ofSeconds(5) // firstRetryInterval
));
try {
return ctx.callActivity("UnreliableService", ctx.getInput(String.class),
retryOptions, String.class).await();
} catch (TaskFailedException ex) {
ctx.setCustomStatus(Map.of("Error", ex.getMessage()));
ctx.callActivity("CompensationActivity", ctx.getInput(String.class)).await();
return "Compensated";
}
}
```
## Durable Task SDK (Non-Functions)
For applications running outside Azure Functions (containers, VMs, Azure Container Apps, Azure Kubernetes Service):
```java
import com.microsoft.durabletask.*;
import com.microsoft.durabletask.azuremanaged.DurableTaskSchedulerWorkerExtensions;
import com.microsoft.durabletask.azuremanaged.DurableTaskSchedulerClientExtensions;
import java.time.Duration;
public class App {
public static void main(String[] args) throws Exception {
String connectionString = "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// Worker
DurableTaskGrpcWorker worker = DurableTaskSchedulerWorkerExtensions
.createWorkerBuilder(connectionString)
.addOrchestration(new TaskOrchestrationFactory() {
@Override public String getName() { return "MyOrchestration"; }
@Override public TaskOrchestration create() {
return ctx -> {
String result = ctx.callActivity("SayHello",
ctx.getInput(String.class), String.class).await();
ctx.complete(result);
};
}
})
.addActivity(new TaskActivityFactory() {
@Override public String getName() { return "SayHello"; }
@Override public TaskActivity create() {
return ctx -> "Hello " + ctx.getInput(String.class) + "!";
}
})
.build();
worker.start();
// Client
DurableTaskClient client = DurableTaskSchedulerClientExtensions
.createClientBuilder(connectionString).build();
String instanceId = client.scheduleNewOrchestrationInstance("MyOrchestration", "World");
OrchestrationMetadata result = client.waitForInstanceCompletion(
instanceId, Duration.ofSeconds(30), true);
System.out.println("Output: " + result.readOutputAs(String.class));
worker.stop();
}
}
```
javascript.md 5.6 KB
# Durable Task Scheduler ā JavaScript
## Learn More
- [Durable Task Scheduler documentation](https://learn.microsoft.com/azure/durable-task-scheduler/)
- [Durable Functions JavaScript guide](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview?tabs=javascript)
## Durable Functions Setup
### Required npm Packages
```json
{
"dependencies": {
"@azure/functions": "^4.0.0",
"durable-functions": "^3.0.0"
}
}
```
> **š” Finding latest versions**: Run `npm view durable-functions version` or check [npmjs.com/package/durable-functions](https://www.npmjs.com/package/durable-functions) for the latest stable release.
### host.json
```json
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "durabletask-scheduler",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
```
### local.settings.json
```json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
}
}
```
## Minimal Example
```javascript
const { app } = require("@azure/functions");
const df = require("durable-functions");
// Activity
df.app.activity("sayHello", {
handler: (city) => `Hello ${city}!`,
});
// Orchestrator
df.app.orchestration("myOrchestration", function* (context) {
const result1 = yield context.df.callActivity("sayHello", "Tokyo");
const result2 = yield context.df.callActivity("sayHello", "Seattle");
return `${result1}, ${result2}`;
});
// HTTP Starter
app.http("HttpStart", {
route: "orchestrators/{orchestrationName}",
methods: ["POST"],
authLevel: "function",
extraInputs: [df.input.durableClient()],
handler: async (request, context) => {
const client = df.getClient(context);
const instanceId = await client.startNew(request.params.orchestrationName);
return client.createCheckStatusResponse(request, instanceId);
},
});
```
## Workflow Patterns
### Fan-Out/Fan-In
```javascript
df.app.orchestration("fanOutFanIn", function* (context) {
const cities = ["Tokyo", "Seattle", "London", "Paris", "Berlin"];
// Fan-out: schedule all activities in parallel
const tasks = cities.map((city) => context.df.callActivity("sayHello", city));
// Fan-in: wait for all to complete
const results = yield context.df.Task.all(tasks);
return results;
});
```
### Human Interaction
```javascript
df.app.orchestration("approvalWorkflow", function* (context) {
yield context.df.callActivity("sendApprovalRequest", context.df.getInput());
// Wait for approval event with timeout
const expiration = new Date(context.df.currentUtcDateTime);
expiration.setDate(expiration.getDate() + 3);
const approvalTask = context.df.waitForExternalEvent("ApprovalEvent");
const timeoutTask = context.df.createTimer(expiration);
const winner = yield context.df.Task.any([approvalTask, timeoutTask]);
if (winner === approvalTask) {
return approvalTask.result ? "Approved" : "Rejected";
}
return "Timed out";
});
```
## Orchestration Determinism
| ā NEVER | ā
ALWAYS USE |
|----------|--------------|
| `new Date()` | `context.df.currentUtcDateTime` |
| `Math.random()` | Pass random values from activities |
| `setTimeout()` | `context.df.createTimer()` |
| Direct I/O, HTTP, database | `context.df.callActivity()` |
### Replay-Safe Logging
```javascript
df.app.orchestration("myOrchestration", function* (context) {
if (!context.df.isReplaying) {
console.log("Started"); // Only logs once, not on replay
}
const result = yield context.df.callActivity("myActivity", "input");
return result;
});
```
## Error Handling & Retry
```javascript
df.app.orchestration("workflowWithRetry", function* (context) {
const retryOptions = new df.RetryOptions(5000, 3); // firstRetryInterval, maxAttempts
retryOptions.backoffCoefficient = 2.0;
retryOptions.maxRetryIntervalInMilliseconds = 60000;
try {
const result = yield context.df.callActivityWithRetry(
"unreliableService",
retryOptions,
context.df.getInput()
);
return result;
} catch (ex) {
context.df.setCustomStatus({ error: ex.message });
yield context.df.callActivity("compensationActivity", context.df.getInput());
return "Compensated";
}
});
```
## Durable Task SDK (Non-Functions)
For applications running outside Azure Functions (containers, VMs, Azure Container Apps, Azure Kubernetes Service):
```javascript
const { createAzureManagedWorkerBuilder, createAzureManagedClient } = require("@microsoft/durabletask-js-azuremanaged");
const connectionString = "Endpoint=http://localhost:8080;Authentication=None;TaskHub=default";
// Activity
const sayHello = async (_ctx, name) => `Hello ${name}!`;
// Orchestrator
const myOrchestration = async function* (ctx, name) {
const result = yield ctx.callActivity(sayHello, name);
return result;
};
async function main() {
// Worker
const worker = createAzureManagedWorkerBuilder(connectionString)
.addOrchestrator(myOrchestration)
.addActivity(sayHello)
.build();
await worker.start();
// Client
const client = createAzureManagedClient(connectionString);
const instanceId = await client.scheduleNewOrchestration("myOrchestration", "World");
const state = await client.waitForOrchestrationCompletion(instanceId, true, 30);
console.log("Output:", state.serializedOutput);
await client.stop();
await worker.stop();
}
main().catch(console.error);
```
python.md 6.4 KB
# Durable Task Scheduler ā Python
## Learn More
- [Durable Task Scheduler documentation](https://learn.microsoft.com/azure/durable-task-scheduler/)
- [Durable Functions Python guide](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview?tabs=python)
## Durable Functions Setup
### Required Packages
```txt
# requirements.txt
azure-functions
azure-functions-durable
azure-identity
```
> **š” Finding latest versions**: Run `pip index versions azure-functions-durable` or check [pypi.org/project/azure-functions-durable](https://pypi.org/project/azure-functions-durable/) for the latest stable release.
### host.json
```json
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "durabletask-scheduler",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
```
> **š” NOTE**: Python uses extension bundles, so the storage provider type is `durabletask-scheduler`. .NET isolated uses the NuGet package directly and requires `azureManaged` instead ā see [dotnet.md](dotnet.md).
### local.settings.json
```json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
}
}
```
## Minimal Example
```python
import azure.functions as func
import azure.durable_functions as df
my_app = df.DFApp(http_auth_level=func.AuthLevel.FUNCTION)
# HTTP Starter
@my_app.route(route="orchestrators/{function_name}", methods=["POST"])
@my_app.durable_client_input(client_name="client")
async def http_start(req: func.HttpRequest, client):
function_name = req.route_params.get('function_name')
instance_id = await client.start_new(function_name)
return client.create_check_status_response(req, instance_id)
# Orchestrator
@my_app.orchestration_trigger(context_name="context")
def my_orchestration(context: df.DurableOrchestrationContext):
result1 = yield context.call_activity("say_hello", "Tokyo")
result2 = yield context.call_activity("say_hello", "Seattle")
return f"{result1}, {result2}"
# Activity
@my_app.activity_trigger(input_name="name")
def say_hello(name: str) -> str:
return f"Hello {name}!"
```
## Workflow Patterns
### Fan-Out/Fan-In
```python
@my_app.orchestration_trigger(context_name="context")
def fan_out_fan_in(context: df.DurableOrchestrationContext):
cities = ["Tokyo", "Seattle", "London", "Paris", "Berlin"]
# Fan-out: schedule all in parallel
parallel_tasks = []
for city in cities:
task = context.call_activity("say_hello", city)
parallel_tasks.append(task)
# Fan-in: wait for all
results = yield context.task_all(parallel_tasks)
return results
```
### Human Interaction
```python
import datetime
@my_app.orchestration_trigger(context_name="context")
def approval_workflow(context: df.DurableOrchestrationContext):
yield context.call_activity("send_approval_request", context.get_input())
# Wait for approval event with timeout
timeout = context.current_utc_datetime + datetime.timedelta(days=3)
approval_task = context.wait_for_external_event("ApprovalEvent")
timeout_task = context.create_timer(timeout)
winner = yield context.task_any([approval_task, timeout_task])
if winner == approval_task:
approved = approval_task.result
return "Approved" if approved else "Rejected"
return "Timed out"
```
## Orchestration Determinism
| ā NEVER | ā
ALWAYS USE |
|----------|--------------|
| `datetime.now()` | `context.current_utc_datetime` |
| `uuid.uuid4()` | `context.new_uuid()` |
| `random.random()` | Pass random values from activities |
| `time.sleep()` | `context.create_timer()` |
| Direct I/O, HTTP, database | `context.call_activity()` |
### Replay-Safe Logging
```python
import logging
@my_app.orchestration_trigger(context_name="context")
def my_orchestration(context: df.DurableOrchestrationContext):
# Check if replaying to avoid duplicate logs
if not context.is_replaying:
logging.info("Started") # Only logs once, not on replay
result = yield context.call_activity("my_activity", "input")
return result
```
## Error Handling & Retry
```python
retry_options = df.RetryOptions(
first_retry_interval_in_milliseconds=5000,
max_number_of_attempts=3,
backoff_coefficient=2.0,
max_retry_interval_in_milliseconds=60000
)
@my_app.orchestration_trigger(context_name="context")
def workflow_with_retry(context: df.DurableOrchestrationContext):
try:
result = yield context.call_activity_with_retry(
"unreliable_service",
retry_options,
context.get_input()
)
return result
except Exception as ex:
context.set_custom_status({"error": str(ex)})
yield context.call_activity("compensation_activity", context.get_input())
return "Compensated"
```
## Durable Task SDK (Non-Functions)
For applications running outside Azure Functions (containers, VMs, Azure Container Apps, Azure Kubernetes Service):
```python
import asyncio
from durabletask.azuremanaged.worker import DurableTaskSchedulerWorker
# Activity function
def say_hello(ctx, name: str) -> str:
return f"Hello {name}!"
# Orchestrator function
def my_orchestration(ctx, name: str) -> str:
result = yield ctx.call_activity('say_hello', input=name)
return result
async def main():
with DurableTaskSchedulerWorker(
host_address="http://localhost:8080",
secure_channel=False,
taskhub="default"
) as worker:
worker.add_activity(say_hello)
worker.add_orchestrator(my_orchestration)
worker.start()
# Client
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
client = DurableTaskSchedulerClient(
host_address="http://localhost:8080",
taskhub="default",
token_credential=None,
secure_channel=False
)
instance_id = client.schedule_new_orchestration("my_orchestration", input="World")
result = client.wait_for_orchestration_completion(instance_id, timeout=30)
print(f"Output: {result.serialized_output}")
if __name__ == "__main__":
asyncio.run(main())
```
README.md 1.1 KB
# Azure Event Grid
Serverless event routing for event-driven architectures.
## When to Use
- Event-driven architectures
- Reactive programming patterns
- Decoupled event routing
- Near real-time event delivery
- Fan-out to multiple subscribers
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| None required | Event Grid is serverless |
| Key Vault | Store topic keys |
## Event Sources
| Type | Description |
|------|-------------|
| System Topics | Azure resource events (Storage, Key Vault, etc.) |
| Custom Topics | Your application events |
| Event Domains | Multi-tenant event management |
## Event Schemas
| Schema | Use Case |
|--------|----------|
| Event Grid Schema | Azure native format |
| CloudEvents 1.0 | CNCF standard, cross-platform |
## Environment Variables
| Variable | Value |
|----------|-------|
| `EVENTGRID_TOPIC_ENDPOINT` | Topic endpoint URL |
| `EVENTGRID_TOPIC_KEY` | Topic access key (Key Vault) |
## References
- [Bicep Patterns](bicep.md)
- [Subscriptions](subscriptions.md)
bicep.md 1.7 KB
# Event Grid - Bicep Patterns
## Custom Topic
```bicep
resource eventGridTopic 'Microsoft.EventGrid/topics@2023-12-15-preview' = {
name: '${resourcePrefix}-egt-${uniqueHash}'
location: location
properties: {
inputSchema: 'EventGridSchema'
publicNetworkAccess: 'Enabled'
}
}
```
## System Topic (Azure Resource Events)
```bicep
resource storageSystemTopic 'Microsoft.EventGrid/systemTopics@2023-12-15-preview' = {
name: '${resourcePrefix}-storage-topic'
location: location
properties: {
source: storageAccount.id
topicType: 'Microsoft.Storage.StorageAccounts'
}
}
```
## Event Domain
```bicep
resource eventDomain 'Microsoft.EventGrid/domains@2023-12-15-preview' = {
name: '${resourcePrefix}-domain'
location: location
properties: {
inputSchema: 'EventGridSchema'
}
}
```
## Publishing Events
### Node.js
```javascript
const { EventGridPublisherClient, AzureKeyCredential } = require("@azure/eventgrid");
const client = new EventGridPublisherClient(
process.env.EVENTGRID_TOPIC_ENDPOINT,
"EventGrid",
new AzureKeyCredential(process.env.EVENTGRID_TOPIC_KEY)
);
await client.send([{
eventType: "Order.Created",
subject: "/orders/12345",
dataVersion: "1.0",
data: { orderId: "12345" }
}]);
```
### Python
```python
from azure.eventgrid import EventGridPublisherClient, EventGridEvent
from azure.core.credentials import AzureKeyCredential
client = EventGridPublisherClient(
os.environ["EVENTGRID_TOPIC_ENDPOINT"],
AzureKeyCredential(os.environ["EVENTGRID_TOPIC_KEY"])
)
client.send([EventGridEvent(
event_type="Order.Created",
subject="/orders/12345",
data={"orderId": "12345"},
data_version="1.0"
)])
```
subscriptions.md 1.7 KB
# Event Grid - Subscriptions
## Event Subscription
```bicep
resource eventGridSubscription 'Microsoft.EventGrid/topics/eventSubscriptions@2023-12-15-preview' = {
parent: eventGridTopic
name: 'order-processor-subscription'
properties: {
destination: {
endpointType: 'WebHook'
properties: {
endpointUrl: 'https://my-api.azurecontainerapps.io/webhooks/orders'
}
}
filter: {
includedEventTypes: [
'Order.Created'
'Order.Updated'
]
}
retryPolicy: {
maxDeliveryAttempts: 30
eventTimeToLiveInMinutes: 1440
}
}
}
```
## Destination Types
### Webhook
```bicep
destination: {
endpointType: 'WebHook'
properties: {
endpointUrl: 'https://my-api.example.com/events'
}
}
```
### Azure Function
```bicep
destination: {
endpointType: 'AzureFunction'
properties: {
resourceId: functionApp.id
}
}
```
### Service Bus Queue
```bicep
destination: {
endpointType: 'ServiceBusQueue'
properties: {
resourceId: '${serviceBus.id}/queues/events'
}
}
```
### Event Hub
```bicep
destination: {
endpointType: 'EventHub'
properties: {
resourceId: eventHub.id
}
}
```
## Filtering
### Event Type Filter
```bicep
filter: {
includedEventTypes: [
'Order.Created'
'Order.Shipped'
]
}
```
### Subject Filter
```bicep
filter: {
subjectBeginsWith: '/orders/priority'
subjectEndsWith: '.json'
}
```
### Advanced Filter
```bicep
filter: {
advancedFilters: [
{
operatorType: 'NumberGreaterThan'
key: 'data.amount'
value: 100
}
]
}
```
README.md 1.4 KB
# Azure AI Foundry
Azure AI Foundry (formerly Azure OpenAI) for building AI-powered applications with models like GPT-4o, GPT-4, and embeddings.
> **š” For detailed AI guidance**, invoke the **`microsoft-foundry`** skill. It provides model catalog access, RAG patterns, agent creation, and evaluation workflows.
## When to Use
- Chat and conversational AI applications
- Text generation and completion
- Code generation assistants
- Document analysis and summarization
- Embeddings for search and RAG
- Multi-modal applications (vision + text)
## Service Type in azure.yaml
```yaml
services:
my-ai-service:
host: containerapp # AI services typically deployed via Container Apps
project: ./src/ai-service
```
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| Azure AI Foundry account | Model hosting |
| Model deployment | Specific model (GPT-4o, GPT-4, etc.) |
| Key Vault | Store API keys securely |
| Application Insights | Monitor usage and costs |
## Model Selection
| Model | Best For | Context Window |
|-------|----------|----------------|
| GPT-4o | General purpose, vision, latest | 128K |
| GPT-4 | Complex reasoning | 32K |
| GPT-3.5-Turbo | Cost-effective, simple tasks | 16K |
| text-embedding-ada-002 | Embeddings for RAG/search | 8K |
## References
- [Region Availability](region-availability.md)
region-availability.md 0.8 KB
# Foundry Region Availability
ā ļø **Very limited ā varies by model**
| Region | GPT-4o | GPT-4 | GPT-3.5 | Embeddings |
|--------|:------:|:-----:|:-------:|:----------:|
| `eastus` | ā
| ā
| ā
| ā
|
| `eastus2` | ā
| ā
| ā
| ā
|
| `westus` | ā ļø | ā ļø | ā
| ā
|
| `westus3` | ā
| ā ļø | ā
| ā
|
| `southcentralus` | ā
| ā
| ā
| ā
|
| `swedencentral` | ā
| ā
| ā
| ā
|
| `westeurope` | ā ļø | ā
| ā
| ā
|
> Check https://learn.microsoft.com/azure/ai-services/openai/concepts/models for current model availability.
## Recommended Regions
| Need | Recommended Region |
|------|--------------------|
| Full model availability | `eastus`, `eastus2`, `swedencentral` |
| Europe compliance | `swedencentral`, `westeurope` |
| With SWA | `eastus2` (only overlap) |
README.md 3.8 KB
# Azure Functions
Serverless compute for event-driven workloads, APIs, and scheduled tasks.
> **ā ļø MANDATORY: Use Composition Algorithm**
>
> **NEVER synthesize Bicep or Terraform from scratch for Azure Functions.**
>
> You MUST follow the base + recipe composition workflow:
> 1. Load [selection.md](templates/selection.md) ā decision tree for choosing base template + recipe
> 2. Follow [composition.md](templates/recipes/composition.md) ā the algorithm for fetching and composing
>
> This ensures proven IaC patterns, correct RBAC, and Flex Consumption defaults.
## When to Use
- Event-driven workloads
- Scheduled tasks (cron jobs)
- HTTP APIs with variable traffic
- Message/queue processing
- Real-time file processing
- MCP servers for AI agents
- Real-time streaming and event processing
- Orchestrations and workflows (Durable Functions)
## Service Type in azure.yaml
```yaml
services:
my-function:
host: function
project: ./src/my-function
```
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| Storage Account | Function runtime state |
| App Service Plan | Hosting (Consumption or Premium) |
| Application Insights | Monitoring |
## Hosting Plans
**Use Flex Consumption for new deployments** (all AZD templates default to Flex).
| Plan | Use Case | Scaling | VNET | Slots |
|------|----------|---------|------|-------|
| **Flex Consumption** ā | Default for new projects | Auto, pay-per-execution | ā
| ā |
| Consumption Windows (Y1) | Legacy/maintenance, Windows-only features | Auto, scale to zero | ā | ā
1 staging slot |
| Consumption Linux (Y1) | Legacy/maintenance | Auto, scale to zero | ā | ā |
| Premium (EP1-EP3) | No cold starts, longer execution, slots | Auto, min instances | ā
| ā
20 slots |
| Dedicated | Predictable load, existing App Service | Manual or auto | ā
| ā
varies by SKU |
> ā ļø **Deployment Slots Guidance:**
> - **Windows Consumption (Y1)** supports 1 staging slot ā valid for existing apps or specific Windows requirements.
> Prefer **Elastic Premium (EP1)** or **Dedicated** for new apps requiring slots, as Consumption cold starts affect swap reliability.
> - **Linux Consumption and Flex Consumption** do **not** support deployment slots.
> - For new projects needing slots: use **Elastic Premium** or an **App Service Plan (Standard+)**.
## Runtime Stacks
> **ā ļø ALWAYS QUERY OFFICIAL DOCUMENTATION FOR VERSIONS**
>
> Do NOT use hardcoded versions. Query for latest GA versions before generating code:
>
> **Primary Source:** [Azure Functions Supported Languages](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages)
>
> Use the azure-documentation MCP tool to fetch current supported versions:
> ```yaml
> intent: "Azure Functions supported language runtime versions"
> learn: true
> ```
### Version Selection Priority
1. **Latest GA** ā For new projects (best features, longest support window)
2. **LTS** ā For enterprise/compliance requirements
3. **User-specified** ā When explicitly requested
| Language | FUNCTIONS_WORKER_RUNTIME | linuxFxVersion |
|----------|-------------------------|----------------|
| Node.js | `node` | `Node\|<version>` |
| Python | `python` | `Python\|<version>` |
| .NET | `dotnet-isolated` | `DOTNET-ISOLATED\|<version>` |
| Java | `java` | `Java\|<version>` |
| PowerShell | `powershell` | `PowerShell\|<version>` |
## References
- **[Selection Guide](templates/selection.md)** ā Start here: decision tree for base + recipe
- **[Composition Algorithm](templates/recipes/composition.md)** ā How to fetch and compose templates
- [AZD Templates](templates/README.md) ā Template overview
- [Bicep Patterns](bicep.md)
- [Terraform Patterns](terraform.md)
- [Durable Functions](durable.md)
- [Aspire + Container Apps](aspire-containerapps.md)
aspire-containerapps.md 3.3 KB
# Azure Functions on Azure Container Apps (Aspire)
When .NET Aspire deploys Azure Functions via `azd`, Functions run as containerized workloads on Azure Container Apps. **File-based secret storage is required** when using identity-based storage access.
> ā ļø **Critical:** When Azure Functions use identity-based storage (e.g., `AzureWebJobsStorage__blobServiceUri`), you **must** set `AzureWebJobsSecretStorageType=Files`.
## Proactive Configuration in AppHost
**Best Practice:** Add this setting in your AppHost BEFORE running `azd up`:
```csharp
var functions = builder.AddAzureFunctionsProject<Projects.Functions>("functions")
.WithHostStorage(storage)
.WithEnvironment("AzureWebJobsSecretStorageType", "Files") // Required for Container Apps
// ... other configuration
```
This ensures the environment variable is automatically included in the generated infrastructure.
## Container Apps Bicep Configuration
When Aspire generates infrastructure, the Functions container app should include this environment variable. If you need to customize the generated Bicep or create it manually, the configuration looks like this:
> **Note:** This example shows partial configuration. Assumes `containerAppEnv`, `storageAccount`, and `appInsights` resources are defined elsewhere in your Bicep templates.
```bicep
resource functionsContainerApp 'Microsoft.App/containerApps@2024-03-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
environmentId: containerAppEnv.id
configuration: {
ingress: {
external: true
targetPort: 8080
}
}
template: {
containers: [
{
name: 'functions-app'
image: containerImage
env: [
{
name: 'AzureWebJobsStorage__blobServiceUri'
value: storageAccount.properties.primaryEndpoints.blob
}
{
name: 'AzureWebJobsSecretStorageType'
value: 'Files' // Required for Container Apps with identity-based storage
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsights.properties.ConnectionString
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'dotnet-isolated'
}
]
}
]
}
}
}
```
## Why This Is Required
- Identity-based storage URIs (e.g., `AzureWebJobsStorage__blobServiceUri`) work for runtime operations
- However, Functions' internal secret/key management does not support these identity-based URIs
- File-based secret storage is mandatory for Container Apps deployments with identity-based storage
## Common Error Without This Setting
```
System.InvalidOperationException: Secret initialization from Blob storage failed due to missing both
an Azure Storage connection string and a SAS connection uri.
```
## When to Use This Configuration
- Deploying Azure Functions to Container Apps via .NET Aspire
- Using `AddAzureFunctionsProject` with `WithHostStorage` in your AppHost
- Using identity-based storage access (no connection strings)
- Setting environment variables like `AzureWebJobsStorage__blobServiceUri`
bicep.md 12.2 KB
# Functions Bicep Patterns ā REFERENCE ONLY
> ā **DO NOT COPY THIS CODE DIRECTLY**
>
> This file contains **reference patterns** for understanding Azure Functions Bicep structure.
> **You MUST use the composition algorithm** to generate infrastructure:
>
> 1. Load `templates/selection.md` to choose the correct base template
> 2. Follow `templates/recipes/composition.md` for the exact algorithm
> 3. Use `functions_template_get` MCP tool to list and fetch templates and write `functionFiles[]` + `projectFiles[]` directly ā NEVER hand-write Bicep/Terraform and use `azd init -t <template>`/`func init`/`func new` as fallback when composing multiple recipes and required templates are not found
>
> Hand-writing Bicep from these patterns will result in missing RBAC, incorrect managed identity configuration, and security vulnerabilities.
## Flex Consumption (Recommended)
**Use Flex Consumption for new deployments with managed identity (no connection strings).**
```bicep
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: '${resourcePrefix}func${uniqueHash}'
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
allowSharedKeyAccess: false // Enforce managed identity
}
}
resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
parent: storageAccount
name: 'default'
}
resource deploymentContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
parent: blobService
name: 'deploymentpackage'
}
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: 'appi-${uniqueHash}'
location: location
kind: 'web'
properties: {
Application_Type: 'web'
}
}
resource functionAppPlan 'Microsoft.Web/serverfarms@2024-04-01' = {
name: 'plan-${uniqueHash}'
location: location
sku: {
name: 'FC1'
tier: 'FlexConsumption'
}
properties: {
reserved: true
}
}
resource functionApp 'Microsoft.Web/sites@2024-04-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
kind: 'functionapp,linux'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: functionAppPlan.id
httpsOnly: true
functionAppConfig: {
deployment: {
storage: {
type: 'blobContainer'
value: '${storageAccount.properties.primaryEndpoints.blob}deploymentpackage'
authentication: {
type: 'SystemAssignedIdentity'
}
}
}
scaleAndConcurrency: {
maximumInstanceCount: 100
instanceMemoryMB: 2048
}
runtime: {
name: 'python' // or 'node', 'dotnet-isolated'
version: '<version>' // Query latest GA: https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages
}
}
siteConfig: {
appSettings: [
{
name: 'AzureWebJobsStorage__blobServiceUri'
value: storageAccount.properties.primaryEndpoints.blob
}
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: appInsights.properties.ConnectionString
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'python'
}
]
}
}
}
// Grant Function App access to Storage for runtime
resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(storageAccount.id, functionApp.id, 'Storage Blob Data Owner')
scope: storageAccount
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b')
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
```
> š” **Key Points:**
>
> - Use `AzureWebJobsStorage__blobServiceUri` instead of connection string
> - Set `allowSharedKeyAccess: false` for enhanced security
> - Use `SystemAssignedIdentity` for deployment authentication
> - Grant `Storage Blob Data Owner` role for full access to blobs, queues, and tables
## Consumption Plan (Legacy)
> ā **DO NOT USE** ā Y1/Dynamic SKU is deprecated for new deployments.
> **ALWAYS use Flex Consumption (FC1)** for all new Azure Functions.
> The Y1 example below is only for reference when migrating legacy apps.
**ā ļø Not recommended for new deployments. Use Flex Consumption instead.**
> š” **OS and Slots Matter for Consumption:**
>
> - **Linux Consumption** (`kind: 'functionapp,linux'`, `reserved: true`): Does **not** support deployment slots.
> - **Windows Consumption** (`kind: 'functionapp'`, no `reserved`): Supports **1 staging slot** (2 total including production).
> If a user specifically needs Windows Consumption with a slot, that is supported ā use the Windows pattern below.
> For new apps needing slots, prefer **Elastic Premium (EP1)** for better performance and no cold-start issues.
### Linux Consumption (no slot support)
```bicep
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: '${resourcePrefix}func${uniqueHash}'
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
}
resource functionAppPlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${resourcePrefix}-funcplan-${uniqueHash}'
location: location
sku: { name: 'Y1', tier: 'Dynamic' }
properties: { reserved: true }
}
resource functionApp 'Microsoft.Web/sites@2022-09-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
kind: 'functionapp,linux'
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: functionAppPlan.id
httpsOnly: true
siteConfig: {
linuxFxVersion: 'Node|20'
appSettings: [
{ name: 'AzureWebJobsStorage', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'node' }
{ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: appInsights.properties.ConnectionString }
]
}
}
}
```
### Windows Consumption (supports 1 staging slot)
> ā ļø **Windows Consumption is not recommended for new projects** ā consider Flex Consumption or Elastic Premium.
> Use this pattern only for existing Windows apps or when Windows-specific features are required.
```bicep
resource functionAppPlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${resourcePrefix}-funcplan-${uniqueHash}'
location: location
sku: { name: 'Y1', tier: 'Dynamic' }
// No 'reserved: true' for Windows
}
resource functionApp 'Microsoft.Web/sites@2022-09-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
kind: 'functionapp' // Windows (no 'linux' suffix)
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: functionAppPlan.id
httpsOnly: true
siteConfig: {
appSettings: [
{ name: 'WEBSITE_NODE_DEFAULT_VERSION', value: '~20' }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'node' }
{ name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'WEBSITE_CONTENTSHARE', value: '${toLower(serviceName)}-prod' }
{ name: 'AzureWebJobsStorage', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: applicationInsights.properties.ConnectionString }
]
}
}
}
// 1 staging slot is supported on Windows Consumption
resource stagingSlot 'Microsoft.Web/sites/slots@2022-09-01' = {
parent: functionApp
name: 'staging'
location: location
kind: 'functionapp'
properties: {
serverFarmId: functionAppPlan.id
siteConfig: {
appSettings: [
{ name: 'WEBSITE_NODE_DEFAULT_VERSION', value: '~20' }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'node' }
{ name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'WEBSITE_CONTENTSHARE', value: '${toLower(serviceName)}-staging' } // MUST differ from production
{ name: 'AzureWebJobsStorage', value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}' }
{ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: applicationInsights.properties.ConnectionString }
]
}
}
}
// Sticky settings ā do not swap WEBSITE_CONTENTSHARE between slots
resource slotConfigNames 'Microsoft.Web/sites/config@2022-09-01' = {
parent: functionApp
name: 'slotConfigNames'
properties: {
appSettingNames: [
'WEBSITE_CONTENTSHARE'
'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
]
}
}
```
## Service Bus Integration (Managed Identity)
```bicep
resource serviceBusNamespace 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' existing = {
name: serviceBusNamespaceName
}
resource functionApp 'Microsoft.Web/sites@2024-04-01' = {
// ... (Function App definition from above)
properties: {
// ... (other properties)
siteConfig: {
appSettings: [
// Storage with managed identity
{
name: 'AzureWebJobsStorage__blobServiceUri'
value: storageAccount.properties.primaryEndpoints.blob
}
// Service Bus with managed identity
{
name: 'SERVICEBUS__fullyQualifiedNamespace'
value: '${serviceBusNamespace.name}.servicebus.windows.net'
}
{
name: 'SERVICEBUS_QUEUE_NAME'
value: serviceBusQueueName
}
// Other settings...
]
}
}
}
// Grant Service Bus Data Receiver role for triggers
resource serviceBusReceiverRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(serviceBusNamespace.id, functionApp.id, 'Azure Service Bus Data Receiver')
scope: serviceBusNamespace
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0')
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
// Grant Service Bus Data Sender role (if function sends messages)
resource serviceBusSenderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(serviceBusNamespace.id, functionApp.id, 'Azure Service Bus Data Sender')
scope: serviceBusNamespace
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39')
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
```
> š” **Key Points:**
>
> - Use `SERVICEBUS__fullyQualifiedNamespace` (double underscore) for managed identity
> - Grant `Service Bus Data Receiver` role for reading messages
> - Grant `Service Bus Data Sender` role for sending messages (if needed)
> - Role assignments automatically enable connection via managed identity
## Premium Plan (No Cold Starts)
```bicep
resource functionAppPlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${resourcePrefix}-funcplan-${uniqueHash}'
location: location
sku: { name: 'EP1', tier: 'ElasticPremium' }
properties: {
reserved: true
minimumElasticInstanceCount: 1
}
}
```
## Functions on Azure Container Apps (Aspire)
> ā ļø **Important for .NET Aspire:** When deploying Azure Functions to Azure Container Apps with identity-based storage, you must configure `AzureWebJobsSecretStorageType=Files`.
See [aspire-containerapps.md](aspire-containerapps.md) for complete guidance on Functions running on Azure Container Apps and configuration examples.
durable.md 1.8 KB
# Durable Functions
Long-running orchestrations with checkpointing and state management.
## Storage Backend
Durable Functions requires a storage backend to persist orchestration state, queues, and history.
| Backend | Recommendation | Notes |
|---------|---------------|-------|
| **Durable Task Scheduler** | **ā
Required** | Fully managed Azure service. Best performance, lowest latency, built-in monitoring. Supports all languages (.NET, Python, Java, JavaScript). |
| Azure Storage (default) | ā Do not use | Legacy backend. Higher latency, harder to monitor, no built-in RBAC. Not permitted for new projects. |
| MSSQL | Specialized | SQL Server backend. Only when SQL infrastructure already exists and user explicitly requests it. |
> **ā MANDATORY**: You **MUST** use **Durable Task Scheduler** as the storage backend for ALL new Durable Functions projects. Do NOT use Azure Storage as the backend ā it is legacy and not permitted. Generate the DTS Bicep module (`Microsoft.DurableTask/schedulers` + `taskHubs`), RBAC role assignment (Durable Task Data Contributor), and `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` app setting. See the [Durable Task Scheduler reference](../durable-task-scheduler/README.md) for setup, language-specific guidance, and Bicep templates.
## When to Use
- Multi-step workflows
- Fan-out/fan-in patterns
- Human interaction workflows
- Long-running processes
## Get Durable Templates
Use the [Azure Functions Templates workflow](templates/README.md) to discover and fetch durable samples:
**MCP Tool:** `functions_template_get(language)` ā filter `resource == "durable"`
**Fallback:** CDN manifest ā filter `resource == "durable"`
Available patterns: Fan-out/Fan-in, Order Processing (with Bicep), Distributed Tracing, Large Payload, Saga, AI Travel Planner, PDF Summarizer, .NET Aspire integration.
terraform.md 13.3 KB
# Functions Terraform Patterns ā REFERENCE ONLY
> ā **DO NOT COPY THIS CODE DIRECTLY**
>
> This file contains **reference patterns** for understanding Azure Functions Terraform structure.
> **You MUST use the composition algorithm** to generate infrastructure:
>
> 1. Load `templates/selection.md` to choose the correct base template
> 2. Follow `templates/recipes/composition.md` for the exact algorithm
> 3. Use `functions_template_get` MCP tool (with `infrastructure: "terraform"`) to list and fetch templates and write `functionFiles[]` + `projectFiles[]` directly ā NEVER hand-write Bicep/Terraform and use `azd init -t <template>`/`func init`/`func new` as fallback when composing multiple recipes and required templates are not found
>
> Hand-writing Terraform from these patterns will result in missing RBAC, incorrect managed identity configuration, and security vulnerabilities.
## Flex Consumption (Recommended)
**Use Flex Consumption for new deployments with managed identity (no connection strings).**
> **ā ļø IMPORTANT**: Flex Consumption requires **azurerm provider v4.2 or later**.
```hcl
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.2"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_storage_account" "function_storage" {
name = "${var.resource_prefix}func${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
shared_access_key_enabled = false # Enforce managed identity
}
resource "azurerm_storage_container" "deployment_package" {
name = "deploymentpackage"
storage_account_id = azurerm_storage_account.function_storage.id
container_access_type = "private"
}
resource "azurerm_application_insights" "function_insights" {
name = "appi-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
application_type = "web"
}
resource "azurerm_service_plan" "function_plan" {
name = "plan-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
os_type = "Linux"
sku_name = "FC1"
}
resource "azurerm_linux_function_app" "function_app" {
name = "${var.resource_prefix}-${var.service_name}-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
service_plan_id = azurerm_service_plan.function_plan.id
storage_account_name = azurerm_storage_account.function_storage.name
storage_uses_managed_identity = true
https_only = true
identity {
type = "SystemAssigned"
}
function_app_config {
deployment {
storage {
type = "blob_container"
value = "${azurerm_storage_account.function_storage.primary_blob_endpoint}${azurerm_storage_container.deployment_package.name}"
authentication {
type = "SystemAssignedIdentity"
}
}
}
scale_and_concurrency {
maximum_instance_count = 100
instance_memory_mb = 2048
}
runtime {
name = "python" # or "node", "dotnet-isolated"
version = "3.11"
}
}
site_config {
application_insights_connection_string = azurerm_application_insights.function_insights.connection_string
application_stack {
python_version = "3.11" # Adjust based on runtime
}
}
app_settings = {
"AzureWebJobsStorage__blobServiceUri" = azurerm_storage_account.function_storage.primary_blob_endpoint
"FUNCTIONS_EXTENSION_VERSION" = "~4"
"FUNCTIONS_WORKER_RUNTIME" = "python"
}
}
# Grant Function App access to Storage for runtime
resource "azurerm_role_assignment" "function_storage_access" {
scope = azurerm_storage_account.function_storage.id
role_definition_name = "Storage Blob Data Owner"
principal_id = azurerm_linux_function_app.function_app.identity[0].principal_id
}
```
> š” **Key Points:**
> - Use `AzureWebJobsStorage__blobServiceUri` instead of connection string
> - Set `shared_access_key_enabled = false` for enhanced security
> - Use `storage_uses_managed_identity = true` for deployment authentication
> - Grant `Storage Blob Data Owner` role for full access to blobs, queues, and tables
> - Requires azurerm provider **v4.2 or later**
### Using Azure Verified Module
For production deployments, use the official Azure Verified Module:
```hcl
module "function_app" {
source = "Azure/avm-res-web-site/azurerm"
version = "~> 0.0"
name = "${var.resource_prefix}-${var.service_name}-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
kind = "functionapp"
os_type = "Linux"
sku_name = "FC1"
function_app_storage_account_name = azurerm_storage_account.function_storage.name
function_app_storage_uses_managed_identity = true
site_config = {
application_insights_connection_string = azurerm_application_insights.function_insights.connection_string
application_stack = {
python_version = "3.11"
}
}
app_settings = {
"AzureWebJobsStorage__blobServiceUri" = azurerm_storage_account.function_storage.primary_blob_endpoint
"FUNCTIONS_EXTENSION_VERSION" = "~4"
"FUNCTIONS_WORKER_RUNTIME" = "python"
}
identity = {
type = "SystemAssigned"
}
}
```
> š” **Example Reference:** [HashiCorp Flex Consumption Example](https://registry.terraform.io/modules/Azure/avm-res-web-site/azurerm/latest/examples/flex_consumption)
## Consumption Plan (Legacy)
> ā **DO NOT USE** ā Y1/Dynamic SKU is deprecated for new deployments.
> **ALWAYS use Flex Consumption (FC1)** for all new Azure Functions.
> The Y1 example below is only for reference when migrating legacy apps.
**ā ļø Not recommended for new deployments. Use Flex Consumption instead.**
> š” **OS and Slots Matter for Consumption:**
> - **Linux Consumption** (`os_type = "Linux"`): Does **not** support deployment slots.
> - **Windows Consumption** (`os_type = "Windows"`): Supports **1 staging slot** (2 total including production).
> If a user specifically needs Windows Consumption with a slot, that is supported ā use the Windows pattern below.
> For new apps needing slots, prefer **Elastic Premium (EP1)** for better performance and no cold-start issues.
### Linux Consumption (no slot support)
```hcl
resource "azurerm_storage_account" "function_storage" {
name = "${var.resource_prefix}func${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_service_plan" "function_plan" {
name = "${var.resource_prefix}-funcplan-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
os_type = "Linux"
sku_name = "Y1"
}
resource "azurerm_linux_function_app" "function_app" {
name = "${var.resource_prefix}-${var.service_name}-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
service_plan_id = azurerm_service_plan.function_plan.id
https_only = true
storage_account_name = azurerm_storage_account.function_storage.name
storage_account_access_key = azurerm_storage_account.function_storage.primary_access_key
site_config {
application_insights_connection_string = azurerm_application_insights.function_insights.connection_string
application_stack {
python_version = "3.11"
}
}
app_settings = {
"FUNCTIONS_EXTENSION_VERSION" = "~4"
"FUNCTIONS_WORKER_RUNTIME" = "python"
}
identity {
type = "SystemAssigned"
}
}
```
### Windows Consumption (supports 1 staging slot)
> ā ļø **Windows Consumption is not recommended for new projects** ā consider Flex Consumption or Elastic Premium.
> Use this pattern only for existing Windows apps or when Windows-specific features are required.
```hcl
resource "azurerm_service_plan" "function_plan" {
name = "${var.resource_prefix}-funcplan-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
os_type = "Windows"
sku_name = "Y1"
}
resource "azurerm_windows_function_app" "function_app" {
name = "${var.resource_prefix}-${var.service_name}-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
service_plan_id = azurerm_service_plan.function_plan.id
https_only = true
storage_account_name = azurerm_storage_account.function_storage.name
storage_account_access_key = azurerm_storage_account.function_storage.primary_access_key
site_config {
application_insights_connection_string = azurerm_application_insights.function_insights.connection_string
application_stack {
node_version = "~20"
}
}
app_settings = {
"FUNCTIONS_EXTENSION_VERSION" = "~4"
"FUNCTIONS_WORKER_RUNTIME" = "node"
"WEBSITE_CONTENTSHARE" = "${lower(var.service_name)}-prod" # must differ per slot; Azure Files share names are lowercase
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.function_storage.primary_connection_string
}
sticky_settings {
app_setting_names = ["WEBSITE_CONTENTSHARE", "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING"]
}
identity {
type = "SystemAssigned"
}
}
# 1 staging slot is supported on Windows Consumption
resource "azurerm_windows_function_app_slot" "staging" {
name = "staging"
function_app_id = azurerm_windows_function_app.function_app.id
storage_account_name = azurerm_storage_account.function_storage.name
storage_account_access_key = azurerm_storage_account.function_storage.primary_access_key
site_config {
application_insights_connection_string = azurerm_application_insights.function_insights.connection_string
application_stack {
node_version = "~20"
}
}
app_settings = {
"FUNCTIONS_EXTENSION_VERSION" = "~4"
"FUNCTIONS_WORKER_RUNTIME" = "node"
"WEBSITE_CONTENTSHARE" = "${var.service_name}-staging" # MUST differ from production
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.function_storage.primary_connection_string
}
}
```
## Service Bus Integration (Managed Identity)
```hcl
data "azurerm_servicebus_namespace" "example" {
name = var.servicebus_namespace_name
resource_group_name = var.servicebus_resource_group
}
resource "azurerm_linux_function_app" "function_app" {
# ... (Function App definition from above)
app_settings = {
# Storage with managed identity
"AzureWebJobsStorage__blobServiceUri" = azurerm_storage_account.function_storage.primary_blob_endpoint
# Service Bus with managed identity
"SERVICEBUS__fullyQualifiedNamespace" = "${data.azurerm_servicebus_namespace.example.name}.servicebus.windows.net"
"SERVICEBUS_QUEUE_NAME" = var.servicebus_queue_name
# Other settings...
"FUNCTIONS_EXTENSION_VERSION" = "~4"
"FUNCTIONS_WORKER_RUNTIME" = "python"
"APPLICATIONINSIGHTS_CONNECTION_STRING" = azurerm_application_insights.function_insights.connection_string
}
}
# Grant Service Bus Data Receiver role for triggers
resource "azurerm_role_assignment" "servicebus_receiver" {
scope = data.azurerm_servicebus_namespace.example.id
role_definition_name = "Azure Service Bus Data Receiver"
principal_id = azurerm_linux_function_app.function_app.identity[0].principal_id
}
# Grant Service Bus Data Sender role (if function sends messages)
resource "azurerm_role_assignment" "servicebus_sender" {
scope = data.azurerm_servicebus_namespace.example.id
role_definition_name = "Azure Service Bus Data Sender"
principal_id = azurerm_linux_function_app.function_app.identity[0].principal_id
}
```
> š” **Key Points:**
> - Use `SERVICEBUS__fullyQualifiedNamespace` (double underscore) for managed identity
> - Grant `Service Bus Data Receiver` role for reading messages
> - Grant `Service Bus Data Sender` role for sending messages (if needed)
> - Role assignments automatically enable connection via managed identity
## Premium Plan (No Cold Starts)
```hcl
resource "azurerm_service_plan" "function_plan" {
name = "${var.resource_prefix}-funcplan-${var.unique_hash}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
os_type = "Linux"
sku_name = "EP1" # EP1, EP2, or EP3
}
resource "azurerm_linux_function_app" "function_app" {
# ... (rest of configuration similar to Flex Consumption)
site_config {
# Premium-specific settings
always_on = true
pre_warmed_instance_count = 1
elastic_instance_minimum = 1
application_stack {
python_version = "3.11"
}
}
}
```
README.md 6.7 KB
# Azure Functions Templates
Dynamic template selection for Azure Functions projects.
## Prerequisites: Check MCP Availability
Before proceeding, verify Azure MCP Server and Functions tools are available:
```
functions_template_get(language: "python")
```
| Result | Action |
|--------|--------|
| ā
Returns template list | Use **Primary Path: MCP Tools** below |
| ā Tool not found / Error | Jump to **[Fallback (MCP Unavailable)](#fallback-mcp-unavailable)** |
---
## Primary Path: MCP Tools
**Use Azure MCP tools** ā they provide complete, up-to-date AZD templates with IaC, RBAC, and managed identity.
### Step 1: Discover Templates
```
functions_template_get(language: "<python|csharp|typescript|javascript|java|powershell>")
```
Returns template list with metadata:
- `templateName` ā pass to generate call
- `description` ā use for selection (describes trigger, bindings, security features)
- `resource` ā filter by trigger type (http, cosmos, timer, eventhub, blob, sql, mcp, ai)
- `infrastructure` ā prefer `bicep` (default), use `terraform` if user requests
### Step 2: Select Template
Use the intentāresource mapping in [selection.md](selection.md) to map user intent to a `resource` filter value.
> **Default behavior:** When user intent cannot be determined or no trigger type is known, use `http` as the default.
**Single-template optimization:** If description mentions BOTH trigger AND binding user needs, fetch that one template only.
**IaC selection:**
- Default: `infrastructure: "bicep"`
- If user says "terraform": `infrastructure: "terraform"`
### Step 3: Generate Project
```
functions_template_get(
language: "<language>",
template: "<selected-template-name>"
)
```
### Step 4: Write All Files
> Write files directly from the MCP tool output ā CLI scaffolding tools produce incomplete projects missing IaC, RBAC, and managed identity configuration. NEVER hand-write Bicep/Terraform and use `azd init -t <template>`/`func init`/`func new` as fallback when composing multiple recipes and required templates are not found in MCP.
Each array entry contains `{ path, content }`. For every entry in BOTH arrays:
1. **Create directories** ā extract the parent directory from each `path` (e.g., `infra/` from `infra/main.bicep`, `.azure/` from `.azure/config.json`). Create all unique parent directories first.
2. **Write the file** ā use the `path` as the file path and `content` as the file body. Write every file exactly as specified.
| Array | Contains | Action |
|-------|----------|--------|
| `functionFiles[]` | Function source code, infra, and config files | Write all ā these are the trigger/binding code and IaC |
| `projectFiles[]` | host.json, dependencies, settings files | Write all ā these are runtime configuration |
**PRESERVE generated IaC security patterns** ā keep RBAC, managed identity, and security config intact. When composing multiple templates, merge additively (see [composition.md](recipes/composition.md)).
> **Skip content verification** ā MCP template files are pre-validated. After writing, do not `view`/`cat` files unless you customized them. Check success via exit code and file count only.
### Step 5: Deploy
```bash
azd env set AZURE_LOCATION <region>
azd up --no-prompt
```
---
## Recipe Composition (Multiple Templates)
When user needs trigger + bindings not in a single template:
1. **Discover** all templates for language
2. **Select trigger template** as base (has complete project structure)
3. **Select binding templates** for additional integrations
4. **Fetch all** templates (parallel calls)
5. **Compose**:
- Use trigger template as base
- Extract binding patterns from binding templates
- Merge IaC resources and RBAC roles
- Add user's custom logic
6. **Trim** unused demo code from samples
> **AzureWebJobsStorage exception**: Always keep storage account + RBAC ā runtime requires it.
---
## Fallback (MCP Unavailable)
If MCP tools are unavailable, download the CDN manifest which points to the same GitHub repos:
### Step 1: Fetch Manifest
```
GET https://cdn.functions.azure.com/public/templates-manifest/manifest.json
```
> ā ļø **If manifest fetch fails** (any error):
> 1. Fall back to the source manifest at `https://github.com/Azure/azure-functions-templates/blob/dev/Functions.Templates/Template-Manifest/manifest.json`
> 2. If both sources are unreachable, fall back to known-good repos: `Azure-Samples/functions-quickstart-*` keyed by language + resource (e.g., `functions-quickstart-python-http-azd`)
> 3. If all fallbacks fail, report the error to the user and ask them to retry later
### Step 2: Filter Templates
Each template entry contains:
| Field | Description | Example |
|-------|-------------|---------|
| `language` | Programming language | `Python`, `TypeScript`, `JavaScript`, `Java`, `CSharp`, `PowerShell` |
| `resource` | Trigger type (see [selection.md](selection.md)) | `http`, `cosmos`, `timer`, `eventhub`, `servicebus`, `blob`, `sql`, `mcp`, `durable` |
| `iac` | Infrastructure type | `bicep`, `terraform` |
| `repositoryUrl` | GitHub repo with complete project | `https://github.com/Azure-Samples/functions-quickstart-python-http-azd` |
| `folderPath` | Path within repo | `.` or `src/api` |
Filter: `language == <user-lang> AND resource == <mapped-resource> AND iac == <user-iac>`
### Step 3: Download Template
**If `folderPath` is `.` (root):** ZIP download + unzip
```
GET https://github.com/{owner}/{repo}/archive/refs/heads/main.zip
unzip main.zip -d <project-dir>
```
**If `folderPath` is a subfolder:** Fetch tree + raw file downloads
```
1. GET https://api.github.com/repos/{owner}/{repo}/git/trees/main?recursive=1
2. Filter tree entries where path starts with {folderPath}/
3. For each file:
GET https://raw.githubusercontent.com/{owner}/{repo}/main/{path}
```
**If downloads fail:** Fall back to git clone
```bash
git clone <repositoryUrl> --depth 1
# If folderPath != ".", copy only that folder
```
The downloaded content is the **same** as MCP `functionFiles[]` + `projectFiles[]`:
- Source code (function triggers, bindings)
- IaC (Bicep/Terraform with RBAC, managed identity)
- azure.yaml (azd configuration)
- host.json, dependencies
### Step 4: Deploy
```bash
azd up --no-prompt
```
---
## References
- [Composition Details](recipes/composition.md) ā recipe algorithm
- [Selection Guide](selection.md) ā intentāresource mapping
- [Recipes Index](recipes/README.md) ā all available recipes
- [Base Template Eval](base/eval/summary.md) ā HTTP base evaluation results
**Browse all:** [Awesome AZD Functions](https://azure.github.io/awesome-azd/?tags=functions)
selection.md 4.4 KB
# Template Selection Guide
Map user intent to MCP template `resource` filter.
> **NEVER hardcode template names** ā names can change. Always use `functions_template_get(language)` to discover available templates, then filter by `resource` field.
## Intent ā Resource Mapping
| User Says | Code Indicators (existing projects) | Resource Filter | Recipe Reference |
|-----------|-------------------------------------|-----------------|------------------|
| "HTTP", "REST", "API", "webhook" | `HttpTrigger`, `@app.route`, `req: HttpRequest` | `http` | ā |
| "timer", "schedule", "cron", "periodic" | `TimerTrigger`, `@app.schedule`, `TimerInfo` | `timer` | [recipes/timer/README.md](recipes/timer/README.md) |
| "Cosmos", "CosmosDB", "document DB" | `CosmosDBTrigger`, `@app.cosmos_db`, `cosmos_db_input` | `cosmos` | [recipes/cosmosdb/README.md](recipes/cosmosdb/README.md) |
| "Event Hub", "streaming", "events" | `EventHubTrigger`, `@app.event_hub`, `event_hub_output` | `eventhub` | [recipes/eventhubs/README.md](recipes/eventhubs/README.md) |
| "Service Bus", "queue", "message" | `ServiceBusTrigger`, `@app.service_bus_queue` | `servicebus` | [recipes/servicebus/README.md](recipes/servicebus/README.md) |
| "blob", "file", "storage trigger" | `BlobTrigger`, `@app.blob`, `blob_input`, `blob_output` | `blob` | [recipes/blob-eventgrid/README.md](recipes/blob-eventgrid/README.md) |
| "SQL", "database trigger" | `SqlTrigger`, `@app.sql`, `sql_input`, `SqlOutput` | `sql` | [recipes/sql/README.md](recipes/sql/README.md) |
| "MCP", "MCP server", "tools" | `McpToolTrigger`, `@app.mcp_tool`, `mcp` in project | `mcp` | [recipes/mcp/README.md](recipes/mcp/README.md) |
| "durable", "workflow", "orchestration" | `DurableOrchestrationTrigger`, `orchestrator`, `durable_functions` | `durable` | [recipes/durable/README.md](recipes/durable/README.md) |
| "AI", "agent", "chatbot", "OpenAI" | `openai`, `AzureOpenAI`, `langchain`, `semantic_kernel` | `http` | Scan description for "AI", "agent", "Copilot SDK" |
| **No specific trigger / intent unclear** | ā | `http` (default) | ā |
## Selection Algorithm
```
1. DISCOVER: functions_template_get(language) ā template list
2. DETECT (existing code): Scan for code indicators above ā map to resource
3. MATCH (new projects): Scan template descriptions for user intent
4. FILTER: resource == mapped_resource AND infrastructure == user_iac_choice
5. PREFER: AZD-enabled (infrastructure: "bicep" or "terraform")
6. SELECT: Template whose description best matches user intent
7. DEFAULT: If intent unclear or no trigger specified ā use `http`
```
## Output: Working Function App
MCP templates return **complete, deployable projects** ā each array entry has `{ path, content }`:
| Array | Contents | Action |
|-------|----------|--------|
| `functionFiles[]` | Function source code (triggers, bindings, business logic), infra and other files | Create directories from `path`, write `content` to each file |
| `projectFiles[]` | settings.json, host.json, dependencies files | Create directories from `path`, write `content` to each file |
> Write files from the array output above. NEVER hand-write Bicep/Terraform and use `azd init -t <template>`/`func init`/`func new` as fallback when composing multiple recipes and required templates are not found.
For deployment steps, see [README.md](README.md#step-5-deploy).
## Default Behavior
**When user intent is ambiguous or no specific trigger is mentioned**, default to HTTP:
- User says "create a function" with no trigger ā HTTP
- User describes business logic without specifying trigger ā HTTP
- Intent cannot be determined from context ā HTTP
HTTP is the safest default because it's the most common trigger type and provides a simple request/response pattern that works for most use cases.
## IaC Selection
| User Says | Filter By |
|-----------|-----------|
| Nothing specified | `infrastructure: "bicep"` (default) |
| "terraform", "use terraform" | `infrastructure: "terraform"` |
| "bicep", "use bicep" | `infrastructure: "bicep"` |
## Non-AZD Fallback
If specific trigger/binding has no AZD template:
1. Use non-AZD template for **function code** only
2. Find related AZD template as **IaC reference**:
| Non-AZD Resource | Related AZD Reference |
|-----------------|----------------------|
| RabbitMQ, Kafka | EventHub AZD |
| SendGrid, Twilio, Webhook | HTTP AZD |
| IoT Hub | EventHub AZD |
python.md 0.7 KB
# Base HTTP Template - Python Eval
## Test Summary
| Test | Status | Notes |
|------|--------|-------|
| Code Syntax | ā
PASS | AST parse successful |
| Function Routes | ā
PASS | /api/hello, /api/health defined |
| v2 Model | ā
PASS | Uses `func.FunctionApp()` decorator model |
| Health Endpoint | ā
PASS | Anonymous auth, JSON response |
## Code Validation
```python
# Validated syntax and structure
import ast
with open('function_app.py') as f:
ast.parse(f.read())
# ā
Code syntax valid
```
## Test Date
2025-02-18
## Template Source
Generated from `functions_template_get(language: "python", template: "http-trigger-python-azd")` MCP tool output
## Verdict
**PASS** - Base HTTP template code validates correctly for Python v2 model.
summary.md 1.8 KB
# Base HTTP Template - Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 5 (Bicep + TF) | [ā
](python.md) | ā
Verified |
| TypeScript | 2 (Bicep) | [ā
](typescript.md) | ā
Verified |
| JavaScript | 2 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 4 (Bicep + TF) | ā | š AZD template exists |
| Java | 2 (Bicep) | ā | š AZD template exists |
| PowerShell | 1 (Bicep) | ā | š AZD template exists |
> ā ļø **Eval cost note:** Each language Ć trigger eval requires ~5 min of agent runtime. Full matrix (6 languages Ć 9 triggers) = ~4.5 hours of CI. Python is verified end-to-end; other languages are confirmed available in the [functions template manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json) (70 templates, 6 languages). Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | HTTP templates found for all languages |
| IaC Included | ā
PASS | Bicep/Terraform infra/ included in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls per language, templates retrieved and applied |
## Results
| Test | Python | TypeScript |
|------|--------|------------|
| Syntax Valid | ā
| ā
|
| Health Endpoint | ā
| ā
|
| HTTP Trigger | ā
| ā
|
| Code Indicator | ā
`app.route` | ā
`app.http` |
| Template Scaffolded | `http-trigger-python-azd` | `http-trigger-typescript-azd` |
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- Base HTTP template provides the foundation for all recipes
- All recipes compose on top of this base
## Test Date
2026-04-22
typescript.md 1.1 KB
# Base HTTP Template - TypeScript Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "typescript")` returns list | ā
PASS |
| Template scaffolded | `http-trigger-typescript-azd` | ā
PASS |
| Has trigger code | `app.http` trigger in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "typescript")
2. Agent scans templateList for HTTP trigger templates
3. Agent selects: http-trigger-typescript-azd
4. Agent calls: functions_template_get(language: "typescript", template: "http-trigger-typescript-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Code Indicators Verified
- `app.http` trigger pattern (V4 model)
- TypeScript compilation successful (`npm install` + `tsc`)
- HTTP trigger with request/response handling
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete TypeScript HTTP trigger with IaC and V4 programming model.
README.md 1.1 KB
# Function Template Recipes
Composable templates for Azure Functions integrations.
## Recipe Index
For intent-to-resource mapping and selection algorithm, see [selection.md](../selection.md).
| Recipe | Resource |
|--------|----------|
| [cosmosdb](cosmosdb/README.md) | `cosmos` |
| [eventhubs](eventhubs/README.md) | `eventhub` |
| [servicebus](servicebus/README.md) | `servicebus` |
| [timer](timer/README.md) | `timer` |
| [durable](durable/README.md) | `durable` |
| [mcp](mcp/README.md) | `mcp` |
| [sql](sql/README.md) | `sql` |
| [blob-eventgrid](blob-eventgrid/README.md) | `blob` |
## Composition
See [composition.md](composition.md) for merging multiple templates.
## Common Patterns
| Pattern | Description |
|---------|-------------|
| [Error Handling](common/error-handling.md) | Try/catch + logging patterns |
| [Health Check](common/health-check.md) | Health endpoint for monitoring |
| [Node.js Entry Point](common/nodejs-entry-point.md) | `src/index.js` requirements |
| [.NET Entry Point](common/dotnet-entry-point.md) | `Program.cs` requirements |
composition.md 11.0 KB
# Composition Algorithm
Combine multiple templates into a single deployable project.
## Azure MCP Path
```
INPUT: language, user_requirements
OUTPUT: Complete project ready for `azd up`
1. DISCOVER
functions_template_get(language) ā template list with descriptions
2. CHECK SINGLE-TEMPLATE MATCH
If one template's description covers ALL requirements ā use it alone
3. SELECT TEMPLATES
- Trigger template (REQUIRED) ā base project with IaC
- Binding templates (OPTIONAL) ā extract patterns only
4. FETCH TEMPLATES
- Single match: 1 call
- Multiple: parallel calls
5. COMPOSE
- Use trigger template as BASE (functionFiles + projectFiles)
- EXTRACT binding patterns from binding templates
- MERGE IaC resources and RBAC roles
- ADD user's custom business logic
6. TRIM unused demo code (keep AzureWebJobsStorage)
7. WRITE files ā for each entry { path, content } in functionFiles[] AND projectFiles[]:
a. Create parent directories from path
b. Write content to path
NEVER hand-write Bicep/Terraform and use azd init -t/func init/func new as fallback when composing multiple recipes and required templates are not found
8. DEPLOY: azd up --no-prompt
```
## Fallback Path (Azure MCP Unavailable)
```
INPUT: language, user_requirements
OUTPUT: Complete project ready for `azd up`
1. FETCH MANIFEST
GET https://cdn.functions.azure.com/public/templates-manifest/manifest.json
If fetch fails ā fall back to: https://github.com/Azure/azure-functions-templates/blob/dev/Functions.Templates/Template-Manifest/manifest.json
If both fail ā fall back to known-good Azure-Samples/functions-quickstart-* repos by language+resource
If all fail ā report error and ask user to retry later
2. FILTER TEMPLATES
Filter by: language, resource (from selection.md), iac
3. CHECK SINGLE-TEMPLATE MATCH
If one template covers ALL requirements ā use it alone
4. SELECT TEMPLATES
- Trigger template (REQUIRED) ā base project
- Binding templates (OPTIONAL) ā extract patterns only
5. DOWNLOAD TEMPLATES
For each template:
- If folderPath == "." ā ZIP download + unzip
- If folderPath != "." ā fetch tree + raw github url file downloads
- Fallback: git clone --depth 1
6. COMPOSE
- Use trigger template as BASE
- EXTRACT binding patterns from binding templates
- MERGE IaC resources, RBAC roles and settings, README and other files.
- ADD user's custom business logic
7. TRIM unused demo code (keep AzureWebJobsStorage)
8. WRITE all files
9. DEPLOY: azd up --no-prompt
```
## Example (MCP)
**User:** "HTTP function that writes to Cosmos DB"
```
1. Discover: functions_template_get(language: "python") ā returns template list
2. Check: No single template description mentions BOTH HTTP trigger AND Cosmos output
3. Select from discovered list:
- Template with resource: "http" (trigger, base)
- Template with resource: "cosmos" and description mentioning "output" (binding)
4. Fetch both templates by templateName from discovery results
5. Compose:
- Base: HTTP template (has IaC, azure.yaml)
- Extract: Cosmos output binding + RBAC from cosmos template
- Merge: Add Cosmos module to infra/main.bicep
6. Trim: Remove HTTP demo response code
7. Write files
8. Deploy
```
## Example (Fallback)
**User:** "HTTP function that writes to Cosmos DB"
```
1. Fetch: GET manifest.json from CDN
2. Filter: language=Python, resource=http OR resource=cosmos, iac=bicep
3. Check: No single template covers both
4. Select:
- http-trigger-python-azd (trigger, base) ā repositoryUrl + folderPath
- cosmos-trigger-python-azd (binding) ā repositoryUrl + folderPath
5. Download: ZIP download both repos (folderPath = ".")
6. Compose:
- Base: HTTP template (has infra/, azure.yaml)
- Extract: Cosmos IaC module + RBAC from cosmos template
- Merge: Add cosmos.bicep to infra/app/, wire into main.bicep
7. Trim: Remove demo code
8. Write files
9. Deploy
```
## Critical Rules
1. **NEVER hardcode template names** ā always discover/fetch manifest first
2. **PRESERVE generated IaC patterns** ā keep RBAC roles, managed identity config, and security settings intact when merging
3. **ALWAYS keep AzureWebJobsStorage** ā runtime requires it
4. **ALWAYS use `--no-prompt`** ā the agent must never elicit user input during azd commands
5. **ALWAYS include ALL THREE UAMI settings for every binding** ā see UAMI Configuration below
6. **ALWAYS wait for RBAC propagation** ā use two-phase deploy if 403 errors occur
7. **NEVER enable `allowSharedKeyAccess: true`** ā correct solution is waiting for RBAC, not disabling security
## IaC Merge Guidelines
When composing multiple templates:
| Action | Allowed | Not Allowed |
|--------|---------|-------------|
| Add resource modules from binding templates | ā
| |
| Add RBAC role assignments from binding templates | ā
| |
| Merge environment variables | ā
| |
| Remove RBAC roles | | ā |
| Change managed identity to connection strings | | ā |
| Remove security configurations | | ā |
| Modify resource SKUs without user request | | ā |
**Merge = additive combination, not modification of security patterns.**
## UAMI Configuration (CRITICAL)
Templates use User Assigned Managed Identity (UAMI). ALL service bindings require explicit `credential` and `clientId` app settings. Missing these causes 500/401/403 errors at runtime.
**Required pattern for EVERY service binding:**
```bicep
appSettings: {
// Endpoint (varies by service)
EventHubConnection__fullyQualifiedNamespace: eventhubs.outputs.fullyQualifiedNamespace
// UAMI credentials - REQUIRED, prefix vary by example
EventHubConnection__credential: 'managedidentity'
EventHubConnection__clientId: apiUserAssignedIdentity.outputs.clientId
}
```
**Validation Checklist (MANDATORY before deploy):**
| Setting Pattern | Required | Example |
|-----------------|:--------:|---------|
| `{Connection}__fullyQualifiedNamespace` or `{Connection}__accountEndpoint` | ā
| `EventHubConnection__fullyQualifiedNamespace` |
| `{Connection}__credential` | ā
| `'managedidentity'` (exact case) |
| `{Connection}__clientId` | ā
| `apiUserAssignedIdentity.outputs.clientId` |
> ā **STOP if any check fails.** The function WILL fail at runtime with 500/Unauthorized errors.
## Language-Specific Entry Points
### Node.js (JavaScript/TypeScript)
> ā **Do NOT delete `src/index.js` (JS) or `src/index.ts` (TS).**
> This file contains `app.setup()` which initializes the Functions runtime.
> Without it, functions deploy but return 404 on all endpoints.
> ā **Glob pattern REQUIRED in package.json:**
>
> ```json
> { "main": "src/{index.js,functions/*.js}" }
> ```
>
> Using `"main": "src/index.js"` alone results in 404 on ALL endpoints.
> ā **package.json must be at project ROOT** (same level as `azure.yaml`), NOT inside `src/`.
> š¦ **TypeScript:** Run `npm run build` before deployment.
> Use: `"main": "dist/src/{index.js,functions/*.js}"`
### C# (.NET)
> ā **Do NOT replace `Program.cs` from the base template.**
> The base template uses `ConfigureFunctionsWebApplication()` with App Insights integration.
> Recipes only ADD trigger function files (`.cs`) and package references (`.csproj`).
## Deployment Strategy
**Option A: Single command** (fast, may fail on first deploy due to RBAC propagation)
```bash
azd up --no-prompt
```
**Option B: Two-phase** (recommended for reliability)
```bash
azd provision --no-prompt # Create resources + RBAC assignments
sleep 60 # Wait for RBAC propagation (Azure AD needs 30-60s)
azd deploy --no-prompt # Deploy code (RBAC now active)
```
## Terraform-Specific Requirements
> ā ļø **CRITICAL**: All Terraform must use `sku_name = "FC1"` (Flex Consumption). **NEVER use Y1/Dynamic.**
### Runtime Versions
> ā ļø **ALWAYS QUERY OFFICIAL DOCUMENTATION** ā Do NOT use hardcoded versions.
>
> **Primary Source:** [Azure Functions Supported Languages](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages)
>
> Query for latest GA/LTS versions before generating IaC.
| Language | `function_runtime` | Version Source |
|----------|-------------------|----------------|
| C# (.NET) | `dotnet-isolated` | Latest LTS from docs |
| TypeScript/JS | `node` | Latest LTS from docs |
| Python | `python` | Latest GA from docs |
| Java | `java` | Latest LTS from docs |
| PowerShell | `powershell` | Latest GA from docs |
### Flex Consumption (FC1) Requires azapi
> ā ļø Use `azapi_resource` instead of `azurerm_linux_function_app` for FC1.
> The AzureRM provider doesn't support FC1's `functionAppConfig` block.
```hcl
resource "azapi_resource" "function_app" {
type = "Microsoft.Web/sites@2023-12-01"
name = "func-${local.name}"
location = azurerm_resource_group.rg.location
parent_id = azurerm_resource_group.rg.id
body = {
kind = "functionapp,linux"
properties = {
serverFarmId = azapi_resource.plan.id
functionAppConfig = {
runtime = { name = "node", version = "22" }
scaleAndConcurrency = { maximumInstanceCount = 100, instanceMemoryMB = 2048 }
deployment = {
storage = {
type = "blobContainer"
value = "${azurerm_storage_account.storage.primary_blob_endpoint}deploymentpackage"
authentication = {
type = "UserAssignedIdentity"
userAssignedIdentityResourceId = azurerm_user_assigned_identity.api.id
}
}
}
}
}
}
depends_on = [time_sleep.rbac_propagation]
}
```
### RBAC Propagation Delay
Azure RBAC takes 30-60s to propagate. Terraform's `depends_on` only waits for resource creation, not RBAC propagation.
**Solution 1: Add `time_sleep` resource**
```hcl
resource "time_sleep" "rbac_propagation" {
depends_on = [azurerm_role_assignment.storage_blob_owner]
create_duration = "60s"
}
resource "azapi_resource" "function_app" {
depends_on = [time_sleep.rbac_propagation]
# ...
}
```
**Solution 2: Create deployment container explicitly**
```hcl
resource "azurerm_storage_container" "deployment" {
name = "deploymentpackage"
storage_account_id = azurerm_storage_account.storage.id
container_access_type = "private"
depends_on = [azurerm_role_assignment.storage_blob_owner]
}
```
> ā ļø **Common Failures Without These Fixes:**
>
> - `403 Forbidden` ā RBAC not yet propagated
> - `404 Container Not Found` ā deployment container not created
> - `Tag Not Found: azd-service-name` ā Azure resource tags take time to be queryable
### Required: azd-service-name Tag
```hcl
tags = {
"azd-service-name" = "api" # MUST match service name in azure.yaml
}
```
> ā ļø **Without this tag, `azd deploy` fails with:**
> `resource not found: unable to find a resource tagged with 'azd-service-name: api'`
### Disabled Local Auth (Policy Required)
Azure Policy often enforces RBAC-only authentication. Always disable local auth (connection strings, SAS keys) and use managed identity instead.
```hcl
# Storage
shared_access_key_enabled = false
# Service Bus
local_auth_enabled = false
# Event Hubs
local_authentication_enabled = false
# Cosmos DB
local_authentication_disabled = true
```
README.md 1.6 KB
# Blob Storage with Event Grid Recipe
Blob trigger via Event Grid for high-scale, low-latency blob processing.
## Template Selection
Resource filter: `blob`
Discover templates via MCP or CDN manifest where `resource == "blob"` and `language` matches user request.
## Why Event Grid?
| Aspect | Polling Trigger | Event Grid Source |
|--------|-----------------|-------------------|
| **Latency** | 10s-60s | Sub-second |
| **Scale** | Limited | High-scale |
## Troubleshooting
### "Unauthorized" or "Forbidden" Errors
**Cause:** Missing UAMI credential settings for Storage.
**Solution:** Ensure these settings are present in app configuration (prefix must match the connection name used in your function code, default: `AzureWebJobsStorage`):
- `<ConnectionName>__blobServiceUri` (e.g., `https://<account>.blob.core.windows.net`)
- `<ConnectionName>__credential` (value: `managedidentity`)
- `<ConnectionName>__clientId`
See [Blob Storage trigger connections](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-trigger#connections) for identity-based config ā refer to the **"Connections"** section on that page for managed identity app settings.
### Blob Events Not Triggering
**Cause:** Event Grid subscription not created or filtering incorrectly.
**Solution:** Verify the Event Grid system topic and subscription exist. Check the blob container prefix filter matches the expected path.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
python.md 1.2 KB
# blob-eventgrid Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "blob"` finds matches | ā
PASS |
| Template scaffolded | `blob-eventgrid-trigger-python-azd` | ā
PASS |
| Has trigger code | `@app.blob_trigger` decorator in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Appropriate role assignment | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "blob" ā blob-eventgrid-trigger-python-azd
4. Agent calls: functions_template_get(language: "python", template: "blob-eventgrid-trigger-python-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Notes
- Template names may vary - use `resource` field or `description` to match
- Never hardcode template names - always discover via list call first
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete blob-eventgrid trigger with IaC, RBAC, and UAMI binding.
summary.md 2.5 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 1 (Bicep) | ā
| ā
Verified |
| TypeScript | 1 (Bicep) | ā | š AZD template exists |
| JavaScript | 1 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 1 (Bicep) | ā | š AZD template exists |
| Java | 1 (Bicep) | ā | š AZD template exists |
| PowerShell | 1 (Bicep) | ā | š AZD template exists |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Templates found via resource filter |
| IaC Included | ā
PASS | EventGrid + Storage Bicep in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls, template `blob-eventgrid-trigger-python-azd` retrieved and applied |
## IaC Validation
| IaC Type | File | Syntax | Policy Compliant | Status |
|----------|------|--------|------------------|--------|
| Bicep | blob.bicep | ā
| ā
| PASS |
| Terraform | blob.tf | ā
| ā
| PASS |
## Deployment Validation
| Test | Status | Details |
|------|--------|---------|
| AZD Template Init | ā
PASS | `functions-quickstart-python-azd-eventgrid-blob` |
| AZD Provision | ā
PASS | Resources created in `rg-blob-eval` |
| AZD Deploy | ā
PASS | Function deployed to `func-mtgqcoepn4p3w` |
| HTTP Response | ā
PASS | HTTP 200 from function endpoint |
| Event Grid Topic | ā
PASS | `eventgridpdftopic` created |
| Storage Account | ā
PASS | RBAC-only storage provisioned |
## Results
| Test | Python |
|------|--------|
| Health | ā
|
| Blob trigger | ā
|
| EventGrid event | ā
|
| Copy to processed | ā
|
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- Dedicated AZD templates available for all 6 languages
- Uses Event Grid for reliable blob event delivery
## IaC Features
| Feature | Bicep | Terraform |
|---------|-------|-----------|
| Storage Account (RBAC-only) | ā
| ā
|
| Event Grid System Topic | ā
| ā
|
| Event Grid Subscription | ā
| ā
|
| RBAC Assignment | ā
| ā
|
| Private Endpoint (VNet) | ā
| ā
|
| Azure Policy Compliance | ā
| ā
|
## Test Date
2026-04-22
dotnet-entry-point.md 1.9 KB
# C# (.NET) Entry Point (DO NOT MODIFY)
The base Azure Functions template includes a properly configured `Program.cs` that should NOT be modified or replaced by recipes.
> ā **CRITICAL**: Do NOT replace or modify `Program.cs` from the base template.
## Base Template Program.cs
The official `functions-quickstart-dotnet-azd` template uses:
```csharp
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var host = new HostBuilder()
.ConfigureFunctionsWebApplication() // ASP.NET Core integration
.ConfigureServices(services =>
{
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();
})
.Build();
host.Run();
```
## Why This Matters
| Feature | ConfigureFunctionsWebApplication | ConfigureFunctionsWorkerDefaults |
|---------|----------------------------------|----------------------------------|
| ASP.NET Core integration | ā
Yes | ā No |
| IActionResult return types | ā
Yes | ā No |
| [FromBody] model binding | ā
Yes | ā No |
| App Insights integration | ā
Built-in | ā Manual setup |
| Modern HTTP handling | ā
Yes | ā ļø Limited |
## What Recipes Should Provide
Recipes only need to add:
1. **Trigger function files** (`.cs` files with `[Function]` attributes)
2. **Package references** (`.csproj` additions for extensions)
3. **App settings** (connection strings, configuration)
All triggers use attribute-based binding ā no Program.cs modifications needed:
- `[HttpTrigger]`, `[TimerTrigger]`, `[CosmosDBTrigger]`
- `[ServiceBusTrigger]`, `[EventHubTrigger]`, `[BlobTrigger]`
- `[DurableClient]`, `[SqlTrigger]`
## Common Mistake
ā **WRONG** ā Recipe overwrites Program.cs with outdated version:
```csharp
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults() // OLD PATTERN
.Build();
```
ā
**CORRECT** ā Recipe leaves Program.cs untouched, only adds function files.
error-handling.md 2.2 KB
# Error Handling Patterns
> **MANDATORY**: All function implementations MUST include proper error handling with logging.
## Python
```python
import logging
try:
# Your function logic here
result = process_data(data)
logging.info(f"Success: processed {item_id}")
except Exception as error:
logging.error(f"Error processing {item_id}: {error}")
raise # Re-raise to trigger retry/dead-letter
```
## TypeScript
```typescript
try {
// Your function logic here
const result = await processData(data);
context.log(`Success: processed ${itemId}`);
} catch (error) {
context.error(`Error processing ${itemId}:`, error);
throw error; // Re-raise to trigger retry/dead-letter
}
```
## JavaScript
```javascript
try {
// Your function logic here
const result = await processData(data);
context.log(`Success: processed ${itemId}`);
} catch (error) {
context.error(`Error processing ${itemId}:`, error);
throw error; // Re-raise to trigger retry/dead-letter
}
```
## C# (.NET)
```csharp
try
{
// Your function logic here
var result = await ProcessDataAsync(data);
_logger.LogInformation($"Success: processed {itemId}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error processing {itemId}");
throw; // Re-raise to trigger retry/dead-letter
}
```
## Java
```java
try {
// Your function logic here
Result result = processData(data);
context.getLogger().info("Success: processed " + itemId);
} catch (Exception e) {
context.getLogger().severe("Error processing " + itemId + ": " + e.getMessage());
throw e; // Re-raise to trigger retry/dead-letter
}
```
## PowerShell
```powershell
try {
# Your function logic here
$result = Process-Data -Data $data
Write-Host "Success: processed $itemId"
}
catch {
Write-Error "Error processing $itemId : $_"
throw # Re-raise to trigger retry/dead-letter
}
```
## Key Principles
1. **Always log before throwing** - Enables debugging from logs
2. **Re-throw exceptions** - Allows Functions runtime to handle retry/dead-letter
3. **Include context in logs** - Item ID, operation name, relevant metadata
4. **Use appropriate log levels** - Info for success, Error for failures
health-check.md 2.7 KB
# Health Check Endpoint
> **RECOMMENDED**: Add a health endpoint for monitoring and load balancer probes.
## Python
```python
@app.route(route="health", methods=["GET"], auth_level=func.AuthLevel.ANONYMOUS)
def health(req: func.HttpRequest) -> func.HttpResponse:
return func.HttpResponse(
'{"status":"healthy"}',
mimetype="application/json",
status_code=200
)
```
## TypeScript
```typescript
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
app.http('health', {
methods: ['GET'],
authLevel: 'anonymous',
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
return {
status: 200,
jsonBody: { status: 'healthy' }
};
}
});
```
## JavaScript
```javascript
const { app } = require('@azure/functions');
app.http('health', {
methods: ['GET'],
authLevel: 'anonymous',
handler: async () => ({
status: 200,
jsonBody: { status: 'healthy' }
})
});
```
## C# (.NET)
```csharp
public class Health
{
[Function("health")]
public HttpResponseData Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req)
{
var response = req.CreateResponse();
response.Headers.Add("Content-Type", "application/json");
response.WriteString("{\"status\":\"healthy\"}");
return response;
}
}
```
## Java
```java
@FunctionName("health")
public HttpResponseMessage health(
@HttpTrigger(name = "req", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
return request.createResponseBuilder(HttpStatus.OK)
.header("Content-Type", "application/json")
.body("{\"status\":\"healthy\"}")
.build();
}
```
## PowerShell
**health/function.json:**
```json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "Request",
"methods": ["get"]
},
{
"type": "http",
"direction": "out",
"name": "Response"
}
]
}
```
**health/run.ps1:**
```powershell
param($Request, $TriggerMetadata)
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = '{"status":"healthy"}'
ContentType = 'application/json'
})
```
## Usage Notes
- **Auth level**: Use `anonymous` for load balancer probes
- **Response**: Return JSON with at least `{"status":"healthy"}`
- **Extended checks**: Can add database connectivity, dependency checks
- **Route**: Standard route is `/api/health`
nodejs-entry-point.md 4.0 KB
# Node.js Entry Point (REQUIRED)
Azure Functions Node.js v4 programming model requires an entry point file that initializes the runtime.
> ā **CRITICAL**: Without this file, functions will deploy but return 404 on all endpoints.
## Project Structure (CRITICAL)
The project structure MUST be:
```
project-root/ # ā azure.yaml project: "."
āāā azure.yaml
āāā package.json # ā MUST be at ROOT, not in src/
āāā host.json
āāā src/
ā āāā index.js # ā Entry point (app.setup)
ā āāā functions/
ā āāā myFunction.js # ā Functions auto-discovered
ā āāā ...
āāā infra/
```
> ā **CRITICAL**: `package.json` MUST be at project root, NOT inside `src/`.
> The `azure.yaml` must have `project: .` (not `project: ./src/`).
## JavaScript: src/index.js
**This file MUST exist and MUST NOT be removed when replacing trigger files.**
```javascript
const { app } = require('@azure/functions');
app.setup({
enableHttpStream: true,
});
```
## TypeScript: src/index.ts
**This file MUST exist and MUST NOT be removed when replacing trigger files.**
```typescript
import { app } from '@azure/functions';
app.setup({
enableHttpStream: true,
});
```
## package.json Configuration
The `package.json` MUST be at the project root (same level as `azure.yaml`).
> ā **CRITICAL: The glob pattern is REQUIRED for function discovery!**
> Using `"main": "src/index.js"` alone will result in 404 on all endpoints.
> You MUST use the glob pattern that includes function files.
### JavaScript (REQUIRED pattern)
```json
{
"main": "src/{index.js,functions/*.js}",
"scripts": {
"start": "func start"
}
}
```
### TypeScript (REQUIRED pattern)
```json
{
"main": "dist/src/{index.js,functions/*.js}",
"scripts": {
"build": "tsc",
"prestart": "npm run build",
"start": "func start"
}
}
```
> **Why the Glob Pattern is Required**: The Azure Functions Node.js v4 runtime uses the `main` field to determine which files to load. Unlike CommonJS where `require()` chains work, the runtime needs ALL function files explicitly listed in the glob pattern. Without the glob, only `index.js` loads and functions in `src/functions/` are never registered.
>
> ā **WRONG**: `"main": "src/index.js"` ā Functions not discovered, 404 on all routes
> ā
**CORRECT**: `"main": "src/{index.js,functions/*.js}"` ā All functions discovered
## azure.yaml Configuration
```yaml
services:
api:
project: . # ā ROOT directory, not ./src/
language: js # or ts for TypeScript
host: function
```
> ā **CRITICAL**: Use `project: .` ā NOT `project: ./src/`. The runtime expects `package.json` at the project root.
## Build Requirements (TypeScript only)
Before deployment, TypeScript must be compiled:
```bash
npm run build
```
This outputs JavaScript to `dist/` which is what Azure Functions actually runs.
## Common Mistakes
| Mistake | Symptom | Fix |
|---------|---------|-----|
| **Using `"main": "src/index.js"` without glob** | **404 on all endpoints** | **Use `"main": "src/{index.js,functions/*.js}"`** |
| Missing `src/index.js` | 404 on all endpoints | Add the entry point file with `app.setup()` |
| Deleting `src/index.js` when replacing triggers | 404 after recipe applied | Keep index.js, only replace function files |
| `package.json` in `src/` instead of root | 404, functions not found | Move `package.json` to project root |
| `project: ./src/` in azure.yaml | Deployment fails or 404 | Use `project: .` |
| Missing `npm run build` for TypeScript | 404 or old code runs | Run build before deploy |
> ā **#1 CAUSE OF 404 ERRORS**: Using `"main": "src/index.js"` instead of the glob pattern.
> The glob `src/{index.js,functions/*.js}` is **REQUIRED** ā it's not optional!
## Terraform vs Bicep: Source Code is IDENTICAL
The Node.js source code and `package.json` are **exactly the same** for both IaC types.
Only the `infra/` folder differs:
- Bicep: `infra/*.bicep`
- Terraform: `infra/*.tf`
> ā ļø If you find yourself changing imports or source code because of IaC choice, something is wrong. The application code should be IaC-agnostic.
README.md 1.5 KB
# Cosmos DB Recipe
Cosmos DB change feed trigger with managed identity authentication.
## Template Selection
Resource filter: `cosmos`
Discover templates via MCP or CDN manifest where `resource == "cosmos"` and `language` matches user request.
## Troubleshooting
### "Forbidden" on Data Operations
**Cause:** Cosmos DB uses **two separate RBAC systems** ā Azure RBAC (control plane) and Cosmos SQL RBAC via `sqlRoleAssignments` (data plane). The MCP template configures both, but if the SQL role assignment is missing, data reads/writes will fail even if Azure RBAC is correctly assigned.
**Solution:** Verify the `sqlRoleAssignments` resource exists in the Bicep/Terraform output. Check the function app has the `Cosmos DB Built-in Data Contributor` SQL role.
### UAMI Connection Issues
**Cause:** Missing managed identity credential settings.
**Solution:** Ensure all three settings are present in app configuration:
- `COSMOS_CONNECTION__accountEndpoint`
- `COSMOS_CONNECTION__credential` (value: `managedidentity`)
- `COSMOS_CONNECTION__clientId`
See [Cosmos DB trigger connections](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-cosmosdb-v2-trigger#connections) for identity-based config ā refer to the **"Connections"** section on that page for managed identity app settings.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
| [eval/typescript.md](eval/typescript.md) | TypeScript evaluation results |
python.md 1.2 KB
# Cosmos DB Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "cosmos"` finds matches | ā
PASS |
| Template scaffolded | `cosmos-trigger-python-azd` | ā
PASS |
| Has trigger code | `@app.cosmos_db_trigger` decorator in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Cosmos DB Data Contributor role | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "cosmos" ā cosmos-trigger-python-azd
4. Agent calls: functions_template_get(language: "python", template: "cosmos-trigger-python-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Notes
- Template names may vary - use `resource` field or `description` to match
- Never hardcode template names - always discover via list call first
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete Cosmos DB trigger with IaC, RBAC, and UAMI binding.
summary.md 1.8 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 1 (Bicep) | [ā
](python.md) | ā
Verified |
| TypeScript | 1 (Bicep) | [ā
](typescript.md) | ā
Verified |
| C# (.NET) | 1 (Bicep) | ā | š AZD template exists |
| Java | 3 (Bicep) | ā | š AZD template exists |
| JavaScript | ā | ā | ā ļø No AZD template |
| PowerShell | ā | ā | ā ļø No AZD template |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). JavaScript and PowerShell have no Cosmos DB AZD template. Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Cosmos templates found via resource filter |
| IaC Included | ā
PASS | Cosmos Bicep module + RBAC in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls per language, templates retrieved and applied |
## Results
| Test | Python | TypeScript |
|------|--------|------------|
| Health | ā
| ā
|
| Trigger fires | ā
| ā
|
| Change detected | ā
| ā
|
| Code Indicator | ā
`cosmos_db_trigger` | ā
`app.cosmosDB` |
| Extra Indicator (IaC) | ā
`Microsoft.DocumentDB` | ā
`Microsoft.DocumentDB` |
| Template Scaffolded | `cosmos-trigger-python-azd` | `cosmos-trigger-typescript-azd` |
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- Cosmos DB requires dual RBAC: Azure control plane + SQL data plane
- See README for RBAC troubleshooting
## Test Date
2026-04-22
typescript.md 1.3 KB
# Cosmos DB Recipe - TypeScript Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "typescript")` returns list | ā
PASS |
| Filter by resource | `resource == "cosmos"` finds matches | ā
PASS |
| Template scaffolded | `cosmos-trigger-typescript-azd` | ā
PASS |
| Has trigger code | `app.cosmosDB` trigger binding in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Cosmos DB Data Contributor role | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "typescript")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "cosmos" ā cosmos-trigger-typescript-azd
4. Agent calls: functions_template_get(language: "typescript", template: "cosmos-trigger-typescript-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Code Indicators Verified
- `app.cosmosDB` trigger binding (V4 model)
- TypeScript compilation successful (`npm install` + `tsc`)
- Cosmos DB with managed identity and proper RBAC assignments
- Infrastructure uses `Microsoft.DocumentDB`
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete TypeScript Cosmos DB trigger with IaC, RBAC, and UAMI binding.
README.md 1.8 KB
# Durable Functions Recipe
Orchestration workflows with Durable Task Scheduler as the backend.
## Template Selection
Resource filter: `durable`
Discover templates via MCP or CDN manifest where `resource == "durable"` and `language` matches user request.
## Key Concept
Uses **Durable Task Scheduler** (DTS) ā a fully managed backend for state persistence. Do NOT use Azure Storage queues/tables.
See [Durable Task Scheduler reference](../../../../durable-task-scheduler/README.md) for backend configuration details.
## Troubleshooting
### Orchestration Fails to Start
**Cause:** Durable Task Scheduler (DTS) backend not provisioned or connection misconfigured.
**Solution:** Verify the DTS resource exists and the function app has the `Durable Task Scheduler Worker` role. Do NOT use Azure Storage queues/tables as backend.
### UAMI Connection Issues
**Cause:** Missing or incorrect Durable Task Scheduler connection string.
**Solution:** DTS uses a connection string format (not the `__` suffix pattern). Set these app settings:
- `DURABLE_TASK_SCHEDULER_CONNECTION_STRING`: `Endpoint=<scheduler-endpoint>;Authentication=ManagedIdentity;ClientID=<client-id>`
- `TASKHUB_NAME`: name of the task hub
For system-assigned identity, omit `ClientID`:
`Endpoint=<scheduler-endpoint>;Authentication=ManagedIdentity`
The identity must have the **Durable Task Data Contributor** role on the scheduler or task hub resource. See [DTS identity auth](https://learn.microsoft.com/en-us/azure/durable-task/scheduler/durable-task-scheduler-identity) for identity-based config ā refer to the **"Configure managed identity"** section for connection string and role assignment details.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
python.md 1.2 KB
# durable Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "durable"` finds matches | ā
PASS |
| Template scaffolded | `durable-functions-python-azd` | ā
PASS |
| Has trigger code | `@app.orchestration_trigger` decorator in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Appropriate role assignment | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "durable" ā durable-functions-python-azd
4. Agent calls: functions_template_get(language: "python", template: "durable-functions-python-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Notes
- Template names may vary - use `resource` field or `description` to match
- Never hardcode template names - always discover via list call first
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete durable trigger with IaC, RBAC, and UAMI binding.
summary.md 1.6 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 3 (Bicep) | ā
| ā
Verified |
| TypeScript | 1 (Bicep) | ā | š AZD template exists |
| JavaScript | 1 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 8 (Bicep) | ā | š AZD template exists |
| Java | 1 (Bicep) | ā | š AZD template exists |
| PowerShell | ā | ā | ā ļø No AZD template |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). PowerShell has no Durable Functions AZD template. Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Templates found via resource filter |
| IaC Included | ā
PASS | Durable Task Scheduler Bicep in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls, template retrieved and applied |
## Results
| Test | Python |
|------|--------|
| Health | ā
|
| Orchestration starts | ā
|
| Activities complete | ā
|
| Status query works | ā
|
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- Uses Durable Task Scheduler (NOT Storage queues/tables)
- See [Durable Task Scheduler docs](../../../../../durable-task-scheduler/README.md)
## Test Date
2026-04-22
README.md 1.2 KB
# Event Hubs Recipe
Event Hubs streaming trigger with managed identity authentication.
## Template Selection
Resource filter: `eventhub`
Discover templates via MCP or CDN manifest where `resource == "eventhub"` and `language` matches user request.
## Troubleshooting
### "Unauthorized" or "Forbidden" Errors
**Cause:** Missing UAMI credential settings for Event Hubs.
**Solution:** Ensure all three settings are present in app configuration:
- `EventHubConnection__fullyQualifiedNamespace`
- `EventHubConnection__credential` (value: `managedidentity`)
- `EventHubConnection__clientId`
See [Event Hubs trigger connections](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-hubs-trigger#connections) for identity-based config ā refer to the **"Connections"** section on that page for managed identity app settings.
### Events Not Arriving
**Cause:** Consumer group or checkpoint storage misconfigured.
**Solution:** Verify the Event Hubs consumer group exists and the function has `Azure Event Hubs Data Receiver` role on the namespace.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
python.md 1.2 KB
# eventhubs Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "eventhub"` finds matches | ā
PASS |
| Template scaffolded | `eventhub-trigger-python-azd` | ā
PASS |
| Has trigger code | `@app.event_hub_message_trigger` decorator in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Appropriate role assignment | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "eventhub" ā eventhub-trigger-python-azd
4. Agent calls: functions_template_get(language: "python", template: "eventhub-trigger-python-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Notes
- Template names may vary - use `resource` field or `description` to match
- Never hardcode template names - always discover via list call first
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete eventhubs trigger with IaC, RBAC, and UAMI binding.
summary.md 1.4 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 1 (Bicep) | ā
| ā
Verified |
| TypeScript | 1 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 1 (Bicep) | ā | š AZD template exists |
| Java | 1 (Bicep) | ā | š AZD template exists |
| JavaScript | ā | ā | ā ļø No AZD template |
| PowerShell | ā | ā | ā ļø No AZD template |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). JavaScript and PowerShell have no Event Hubs AZD template. Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Templates found via resource filter |
| IaC Included | ā
PASS | Event Hubs Bicep + RBAC in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls, template `eventhub-trigger-python-azd` retrieved and applied |
## Results
| Test | Python |
|------|--------|
| Health | ā
|
| Event received | ā
|
| Batch processing | ā
|
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- UAMI configuration included in template IaC
## Test Date
2026-04-22
README.md 1.9 KB
# MCP (Model Context Protocol) Recipe
MCP tool endpoints for AI agent integration via the JSON-RPC 2.0 protocol.
## Template Selection
Resource filter: `mcp`
Discover templates via MCP or CDN manifest where `resource == "mcp"` and `language` matches user request.
## Protocol
MCP uses the **JSON-RPC 2.0** protocol. Two template types with different transport support:
- **Extension-based** (`mcp-server-remote-*`) supports both Streamable HTTP and SSE transports
- **Self-hosted** (`mcp-sdk-hosting-*`) ā supports Streamable HTTP only
See [MCP Specification](https://modelcontextprotocol.io/) for protocol details.
## Troubleshooting
### Transport Mismatch
**Cause:** Client and server using different transports ā SSE client gets `404`/`405`, HTTP client gets unexpected `text/event-stream`.
**Solution:** Extension-based templates support both Streamable HTTP and SSE. Self-hosted templates support Streamable HTTP only. In VS Code `mcp.json`, set `"type": "sse"` or `"type": "http"` to match the server.
### Missing App Settings After Deploy
**Cause:** Required app settings not configured on the function app.
**Solution:** Ensure protected resource metadata settings are present. For C# self-hosted servers, verify `host.json` `arguments` points to the compiled DLL path.
See [MCP extension trigger and bindings](https://learn.microsoft.com/azure/azure-functions/functions-bindings-mcp) for extension-based servers, [Self-hosted MCP servers](https://learn.microsoft.com/en-us/azure/azure-functions/self-hosted-mcp-servers) for self-hosted architecture, and [MCP tutorial troubleshooting](https://learn.microsoft.com/en-us/azure/azure-functions/functions-mcp-tutorial?tabs=self-hosted#troubleshooting) for self-hosted deployment issues.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
python.md 1.2 KB
# MCP Server Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "mcp"` finds matches | ā
PASS |
| Template scaffolded | `mcp-server-remote-python` | ā
PASS |
| Has trigger code | HTTP trigger with JSON-RPC handler in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "mcp" ā mcp-server-remote-python
4. Agent calls: functions_template_get(language: "python", template: "mcp-server-remote-python")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Code Indicators Verified
- HTTP trigger endpoint for JSON-RPC 2.0 protocol
- `tools/list` returns tool definitions with schemas
- `tools/call` executes tools and returns results
- Ready for AI agent integration (Copilot, Claude, etc.)
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete remote MCP server with JSON-RPC endpoint and IaC.
summary.md 1.5 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 3 (Bicep) | ā
| ā
Verified |
| TypeScript | 2 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 2 (Bicep) | ā | š AZD template exists |
| Java | 2 (Bicep) | ā | š AZD template exists |
| JavaScript | ā | ā | ā ļø No AZD template |
| PowerShell | ā | ā | ā ļø No AZD template |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). JavaScript and PowerShell have no MCP AZD template. Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Templates found via resource filter |
| IaC Included | ā
PASS | Storage queue config in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls, template `mcp-server-remote-python` retrieved and applied |
## Results
| Test | Python |
|------|--------|
| Health | ā
|
| tools/list | ā
|
| tools/call | ā
|
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- Requires `enableQueue: true` for MCP state management
- Uses JSON-RPC 2.0 protocol over HTTP
## Test Date
2026-04-22
README.md 1.3 KB
# Service Bus Recipe
Service Bus queue/topic trigger with managed identity authentication.
## Template Selection
Resource filter: `servicebus`
Discover templates via MCP tool or CDN manifest where `resource == "servicebus"` and `language` matches user request.
## Troubleshooting
### 500 Error on First Request
**Cause:** RBAC role assignment hasn't propagated to Service Bus data plane.
**Solution:** Wait 30-60 seconds after provisioning, or restart the function app.
### "Unauthorized" or "Forbidden" Errors
**Cause:** Missing UAMI credential settings.
**Solution:** Ensure all three settings are present in app configuration:
- `ServiceBusConnection__fullyQualifiedNamespace`
- `ServiceBusConnection__credential` (value: `managedidentity`)
- `ServiceBusConnection__clientId`
See [Service Bus trigger connections](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger#connections) for identity-based config ā refer to the **"Connections"** section on that page for managed identity app settings.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
| [eval/typescript.md](eval/typescript.md) | TypeScript evaluation results |
python.md 1.3 KB
# Service Bus Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "servicebus"` finds matches | ā
PASS |
| Template scaffolded | `servicebus-trigger-python-azd` | ā
PASS |
| Has trigger code | `@app.service_bus_queue_trigger` decorator in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Service Bus Data Receiver/Sender role | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "servicebus" ā servicebus-trigger-python-azd
4. Agent calls: functions_template_get(language: "python", template: "servicebus-trigger-python-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Code Indicators Verified
- `@app.service_bus_queue_trigger` with queue_name
- `connection="ServiceBusConnection"` (UAMI pattern)
- `ServiceBusConnection__fullyQualifiedNamespace` binding
- Extension bundle v4
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete Service Bus trigger with IaC, RBAC, and UAMI binding.
summary.md 1.7 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 1 (Bicep) | [ā
](python.md) | ā
Verified |
| TypeScript | 1 (Bicep) | [ā
](typescript.md) | ā
Verified |
| JavaScript | 1 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 1 (Bicep) | ā | š AZD template exists |
| Java | 1 (Bicep) | ā | š AZD template exists |
| PowerShell | 1 (Bicep) | ā | š AZD template exists |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Templates found via resource filter |
| IaC Included | ā
PASS | Service Bus Bicep + RBAC in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls per language, templates retrieved and applied |
## Results
| Test | Python | TypeScript |
|------|--------|------------|
| Health | ā
| ā
|
| Queue message | ā
| ā
|
| Output binding | ā
| ā
|
| Code Indicator | ā
`service_bus_queue_trigger` | ā
`app.serviceBusQueue` |
| Extra Indicator (IaC) | ā
`Microsoft.ServiceBus` | ā
`Microsoft.ServiceBus` |
| Template Scaffolded | `servicebus-trigger-python-azd` | `servicebus-trigger-typescript-azd` |
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- See README for UAMI troubleshooting (500 error, Unauthorized)
## Test Date
2026-04-22
typescript.md 1.4 KB
# Service Bus Recipe - TypeScript Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "typescript")` returns list | ā
PASS |
| Filter by resource | `resource == "servicebus"` finds matches | ā
PASS |
| Template scaffolded | `servicebus-trigger-typescript-azd` | ā
PASS |
| Has trigger code | `app.serviceBusQueue` trigger binding in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Service Bus Data Receiver/Sender role | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "typescript")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "servicebus" ā servicebus-trigger-typescript-azd
4. Agent calls: functions_template_get(language: "typescript", template: "servicebus-trigger-typescript-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Code Indicators Verified
- `app.serviceBusQueue` trigger binding (V4 model)
- TypeScript compilation successful (`npm install` + `tsc`)
- Service Bus namespace with managed identity RBAC
- VNet integration and Flex Consumption plan
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete TypeScript Service Bus trigger with IaC, RBAC, and UAMI binding.
README.md 1.8 KB
# Azure SQL Recipe
SQL change tracking trigger with Entra ID managed identity authentication.
## Template Selection
Resource filter: `sql`
Discover templates via MCP or CDN manifest where `resource == "sql"` and `language` matches user request.
## Troubleshooting
### SQL Trigger Not Firing
**Cause:** Change tracking not enabled on the target table.
**Solution:** Run these T-SQL commands on your database:
```sql
ALTER DATABASE [YourDatabase] SET CHANGE_TRACKING = ON;
ALTER TABLE [dbo].[ToDo] ENABLE CHANGE_TRACKING;
```
### "Login failed" or "Unauthorized" Errors
**Cause:** Missing managed identity authentication or SQL access not granted.
**Solution:** Set the SQL connection string with managed identity authentication:
```
Server=tcp:<server>.database.windows.net,1433;Database=<database>;Authentication=Active Directory Default;Encrypt=True;TrustServerCertificate=False;
```
For user-assigned managed identity, add `User Id=<ClientId>`:
```
Server=tcp:<server>.database.windows.net,1433;Database=<database>;Authentication=Active Directory Default;User Id=<ClientId>;Encrypt=True;TrustServerCertificate=False;
```
Also run post-deploy T-SQL to grant the function app data access:
```sql
CREATE USER [<function-app-name>] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [<function-app-name>];
ALTER ROLE db_datawriter ADD MEMBER [<function-app-name>];
```
See [SQL managed identity](https://learn.microsoft.com/en-us/azure/azure-functions/functions-identity-access-azure-sql-with-managed-identity) for identity-based config ā refer to the **"Configure the function app"** section for connection string and identity settings.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
python.md 1.2 KB
# sql Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "sql"` finds matches | ā
PASS |
| Template scaffolded | `sql-trigger-python-azd` | ā
PASS |
| Has trigger code | `@app.sql_trigger` decorator in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
| Has RBAC | Appropriate role assignment | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "sql" ā sql-trigger-python-azd
4. Agent calls: functions_template_get(language: "python", template: "sql-trigger-python-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Notes
- Template names may vary - use `resource` field or `description` to match
- Never hardcode template names - always discover via list call first
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete sql trigger with IaC, RBAC, and UAMI binding.
summary.md 2.7 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 1 (Bicep) | ā
| ā
Verified |
| TypeScript | 1 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 1 (Bicep) | ā | š AZD template exists |
| Java | ā | ā | ā ļø No AZD template |
| JavaScript | ā | ā | ā ļø No AZD template |
| PowerShell | ā | ā | ā ļø No AZD template |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). Java, JavaScript, and PowerShell have no SQL AZD template. Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Templates found via resource filter |
| IaC Included | ā
PASS | SQL Server Bicep + RBAC in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls, template `sql-trigger-python-azd` retrieved and applied |
## IaC Validation
| IaC Type | File | Syntax | Policy Compliant | Status |
|----------|------|--------|------------------|--------|
| Bicep | sql.bicep | ā
| ā
| PASS |
| Terraform | sql.tf | ā
| ā
| PASS |
## Deployment Validation
| Test | Status | Details |
|------|--------|---------|
| AZD Template Init | ā
PASS | `functions-quickstart-python-azd-sql` |
| AZD Provision | ā
PASS | Resources created in `rg-sql-eval` |
| AZD Deploy | ā
PASS | Function deployed to `func-api-arkwcvhvbkqwc` |
| HTTP Response | ā
PASS | HTTP 200 from function endpoint |
| SQL Server | ā
PASS | `sql-arkwcvhvbkqwc` with Entra-only auth |
| SQL Database | ā
PASS | `ToDo` database created |
## Results
| Test | Python |
|------|--------|
| Health | ā
|
| SQL trigger | ā
|
| SQL output | ā
|
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- Dedicated AZD templates available for Python, TypeScript, .NET
- Requires T-SQL post-deploy for managed identity access
## IaC Features
| Feature | Bicep | Terraform |
|---------|-------|-----------|
| SQL Server (Entra-only) | ā
| ā
|
| SQL Database | ā
| ā
|
| Firewall Rules | ā
| ā
|
| Private Endpoint (VNet) | ā
| ā
|
| Azure Policy Compliance | ā
| ā
|
## Post-Deploy Note
SQL managed identity access requires T-SQL after deployment:
```sql
CREATE USER [<function-app-name>] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [<function-app-name>];
ALTER ROLE db_datawriter ADD MEMBER [<function-app-name>];
```
## Test Date
2026-04-22
README.md 1.5 KB
# Timer Recipe
Scheduled/cron trigger for periodic task execution.
## Template Selection
Resource filter: `timer`
Discover templates via MCP or CDN manifest where `resource == "timer"` and `language` matches user request.
## Cron Expressions
Some templates define the schedule via an app setting reference `%TIMER_SCHEDULE%` so the cron expression is configurable without code changes. Set the `TIMER_SCHEDULE` app setting to the desired expression.
| Schedule | Expression |
|----------|------------|
| Every 5 minutes | `0 */5 * * * *` |
| Every hour | `0 0 * * * *` |
| Every day at midnight | `0 0 0 * * *` |
| Every Monday at 9am | `0 0 9 * * 1` |
| Every 30 seconds | `*/30 * * * * *` |
> Azure uses 6-part cron expressions (with seconds). See [Azure Functions timer trigger](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer#ncrontab-expressions) for full syntax reference.
## Troubleshooting
### Timer Not Firing
**Cause:** Invalid cron expression or function app not running.
**Solution:** Verify cron syntax; check function app is started and healthy.
### Duplicate Executions
**Cause:** Multiple instances running the same timer.
**Solution:** Timer triggers use Storage lease to ensure single execution. Verify `AzureWebJobsStorage` is configured.
## Eval
| Path | Description |
|------|-------------|
| [eval/summary.md](eval/summary.md) | Evaluation summary |
| [eval/python.md](eval/python.md) | Python evaluation results |
python.md 1.2 KB
# Timer Recipe - Python Eval
## MCP Template Validation
| Criteria | Expected | Status |
|----------|----------|--------|
| Template discovery | `functions_template_get(language: "python")` returns list | ā
PASS |
| Filter by resource | `resource == "timer"` finds matches | ā
PASS |
| Template scaffolded | `timer-trigger-python-azd` | ā
PASS |
| Has trigger code | `@app.timer_trigger` decorator in output | ā
PASS |
| Has IaC | `projectFiles[]` includes Bicep | ā
PASS |
## Agent Behavior Validation
```text
1. Agent calls: functions_template_get(language: "python")
2. Agent scans templateList.triggers[] descriptions and resource field
3. Agent selects: template where resource == "timer" ā timer-trigger-python-azd
4. Agent calls: functions_template_get(language: "python", template: "timer-trigger-python-azd")
5. Agent writes: functionFiles[] + projectFiles[]
```
## Code Indicators Verified
- `@app.timer_trigger` with schedule parameter
- `TIMER_SCHEDULE` app setting reference (`%TIMER_SCHEDULE%`)
- 6-part cron expression (with seconds)
## Test Date
2026-04-22
## Verdict
**PASS** - MCP template provides complete timer trigger with configurable schedule and IaC.
summary.md 1.4 KB
# Eval Summary
## Coverage Status
| Language | Manifest Templates | Eval | Status |
|----------|-------------------|------|--------|
| Python | 1 (Bicep) | ā
| ā
Verified |
| TypeScript | 1 (Bicep) | ā | š AZD template exists |
| JavaScript | 1 (Bicep) | ā | š AZD template exists |
| C# (.NET) | 1 (Bicep) | ā | š AZD template exists |
| Java | 1 (Bicep) | ā | š AZD template exists |
| PowerShell | 1 (Bicep) | ā | š AZD template exists |
> ā ļø **Eval cost note:** Each language eval requires ~5 min of agent runtime. Python is verified end-to-end; other languages confirmed in [manifest](https://cdn.functions.azure.com/public/templates-manifest/manifest.json). Multi-language eval expansion tracked as follow-up.
## MCP Tool Validation
| Test | Status | Details |
|------|--------|---------|
| `functions_template_get` | ā
PASS | 2 calls via `azure-functions` MCP tool |
| Template Discovery | ā
PASS | Timer templates found via resource filter |
| IaC Included | ā
PASS | Bicep infra/ included in projectFiles |
| E2E Agent Test | ā
PASS | 2 `azure-functions` calls, template `timer-trigger-python-azd` retrieved and applied |
## Results
| Test | Python |
|------|--------|
| Health | ā
|
| Timer fires | ā
|
| Schedule correct | ā
|
## Notes
- Templates retrieved via `functions_template_get(language: "<language>", template: "<template-name>")` MCP tool
- Timer templates include cron schedule in function code
## Test Date
2026-04-22
README.md 1.4 KB
# Azure Key Vault
Centralized secrets, keys, and certificate management.
## When to Use
- Storing application secrets
- Managing certificates
- Storing encryption keys
- Centralizing secret management
- Enabling secret rotation
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| None required | Key Vault is self-contained |
| Private Endpoint | Secure access (optional) |
## SKU Selection
| SKU | Features |
|-----|----------|
| Standard | Software-protected keys |
| Premium | HSM-protected keys |
## RBAC Roles
| Role | Permissions |
|------|-------------|
| Key Vault Administrator | Full access |
| Key Vault Secrets Officer | Manage secrets |
| Key Vault Secrets User | Read secrets |
| Key Vault Certificates Officer | Manage certificates |
| Key Vault Crypto Officer | Manage keys |
## Environment Variables
| Variable | Value |
|----------|-------|
| `KEY_VAULT_URL` | `https://{vault-name}.vault.azure.net/` |
| `KEY_VAULT_NAME` | Vault name |
## Best Practices
1. **Always use RBAC** over access policies
2. **Enable soft delete and purge protection** for production
3. **Use managed identities** instead of storing keys in apps
4. **Set expiration dates** on secrets
5. **Use separate vaults** for different environments
## References
- [Bicep Patterns](bicep.md)
- [SDK Access](sdk.md)
bicep.md 1.8 KB
# Key Vault - Bicep Patterns
## Basic Vault
```bicep
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: '${resourcePrefix}-kv-${uniqueHash}'
location: location
properties: {
tenantId: subscription().tenantId
sku: {
family: 'A'
name: 'standard'
}
enableRbacAuthorization: true
enableSoftDelete: true
softDeleteRetentionInDays: 90
enablePurgeProtection: true
}
}
```
## Storing Secrets
```bicep
resource secret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'database-connection-string'
properties: {
value: databaseConnectionString
}
}
```
## Role Assignment (Managed Identity)
```bicep
resource keyVaultRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, principalId, 'Key Vault Secrets User')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6')
principalId: principalId
principalType: 'ServicePrincipal'
}
}
```
## Referencing in App Service / Functions
```bicep
appSettings: [
{
name: 'DATABASE_URL'
value: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=database-connection-string)'
}
]
```
## Referencing in Container Apps
```bicep
secrets: [
{
name: 'db-connection'
keyVaultUrl: '${keyVault.properties.vaultUri}secrets/database-connection-string'
identity: containerApp.identity.principalId
}
]
```
## Secret with Expiration
```bicep
resource secret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
parent: keyVault
name: 'api-key'
properties: {
value: apiKey
attributes: {
exp: dateTimeToEpoch(dateTimeAdd(utcNow(), 'P90D'))
}
}
}
```
sdk.md 2.0 KB
# Key Vault - SDK Patterns
## Node.js
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../../auth-best-practices.md) for production patterns.
```javascript
const { SecretClient } = require("@azure/keyvault-secrets");
const { DefaultAzureCredential } = require("@azure/identity");
const client = new SecretClient(
process.env.KEY_VAULT_URL,
new DefaultAzureCredential()
);
const secret = await client.getSecret("database-connection-string");
console.log(secret.value);
```
## Python
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../../auth-best-practices.md) for production patterns.
```python
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
client = SecretClient(
vault_url=os.environ["KEY_VAULT_URL"],
credential=DefaultAzureCredential()
)
secret = client.get_secret("database-connection-string")
print(secret.value)
```
## .NET
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../../auth-best-practices.md) for production patterns.
```csharp
var client = new SecretClient(
new Uri(Environment.GetEnvironmentVariable("KEY_VAULT_URL")),
new DefaultAzureCredential()
);
KeyVaultSecret secret = await client.GetSecretAsync("database-connection-string");
Console.WriteLine(secret.Value);
```
## Event Grid Integration (Expiry Notifications)
```bicep
resource kvEventSubscription 'Microsoft.EventGrid/eventSubscriptions@2023-12-15-preview' = {
name: 'secret-expiry-notification'
scope: keyVault
properties: {
destination: {
endpointType: 'WebHook'
properties: {
endpointUrl: 'https://my-api.example.com/secret-rotation'
}
}
filter: {
includedEventTypes: [
'Microsoft.KeyVault.SecretNearExpiry'
'Microsoft.KeyVault.SecretExpired'
]
}
}
}
```
README.md 1.1 KB
# Azure Logic Apps
Low-code workflow automation and integration platform.
## When to Use
- Integration-heavy workloads
- Business process automation
- Connecting multiple SaaS services
- Approval and human workflow processes
- Low-code/visual workflow design
- Event-driven orchestration
## Deployment Note
Logic Apps are typically deployed as infrastructure, not application services:
```yaml
# Logic Apps are defined in Bicep infrastructure
```
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| Storage Account | Workflow state (Standard only) |
| Log Analytics | Monitoring |
| API Connections | External service connections |
## Consumption vs Standard
| Feature | Consumption | Standard |
|---------|-------------|----------|
| Pricing | Per execution | App Service Plan |
| VNET | Limited | Full support |
| State | Azure-managed | Custom storage |
| Deployment | ARM/Bicep | VS Code deployment |
| Multi-workflow | One per resource | Multiple per app |
## References
- [Bicep Patterns](bicep.md)
- [Triggers](triggers.md)
bicep.md 1.9 KB
# Logic Apps - Bicep Patterns
## Consumption (Multi-tenant)
```bicep
resource logicApp 'Microsoft.Logic/workflows@2019-05-01' = {
name: '${resourcePrefix}-logic-${uniqueHash}'
location: location
properties: {
state: 'Enabled'
definition: {
'$schema': 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#'
contentVersion: '1.0.0.0'
triggers: {
manual: {
type: 'Request'
kind: 'Http'
inputs: {
schema: {}
}
}
}
actions: {}
}
parameters: {}
}
}
```
## Standard (Single-tenant)
```bicep
resource logicAppPlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${resourcePrefix}-logicplan-${uniqueHash}'
location: location
sku: {
name: 'WS1'
tier: 'WorkflowStandard'
}
properties: {
reserved: true
}
}
resource logicAppStandard 'Microsoft.Web/sites@2022-09-01' = {
name: '${resourcePrefix}-logic-${uniqueHash}'
location: location
kind: 'functionapp,workflowapp'
properties: {
serverFarmId: logicAppPlan.id
siteConfig: {
appSettings: [
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'node'
}
{
name: 'AzureWebJobsStorage'
value: storageConnectionString
}
]
}
}
}
```
## API Connection
```bicep
resource serviceBusConnection 'Microsoft.Web/connections@2016-06-01' = {
name: 'servicebus-connection'
location: location
properties: {
displayName: 'Service Bus Connection'
api: {
id: subscriptionResourceId('Microsoft.Web/locations/managedApis', location, 'servicebus')
}
parameterValues: {
connectionString: serviceBus.listKeys().primaryConnectionString
}
}
}
```
triggers.md 1.8 KB
# Logic Apps - Triggers
## HTTP Request
```json
{
"triggers": {
"manual": {
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"type": "object",
"properties": {
"orderId": { "type": "string" }
}
}
}
}
}
}
```
## Recurrence (Schedule)
```json
{
"triggers": {
"Recurrence": {
"type": "Recurrence",
"recurrence": {
"frequency": "Hour",
"interval": 1
}
}
}
}
```
## Service Bus Queue
```json
{
"triggers": {
"When_a_message_is_received": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['servicebus']['connectionId']"
}
},
"method": "get",
"path": "/@{encodeURIComponent('orders')}/messages/head"
}
}
}
}
```
## Common Actions
### HTTP Action
```json
{
"HTTP": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://api.example.com/orders",
"headers": {
"Content-Type": "application/json"
},
"body": "@triggerBody()"
}
}
}
```
### Approval Email
```json
{
"Send_approval_email": {
"type": "ApiConnectionWebhook",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['office365']['connectionId']"
}
},
"body": {
"NotificationUrl": "@{listCallbackUrl()}",
"Message": {
"To": "approver@example.com",
"Subject": "Approval Required",
"Options": "Approve, Reject"
}
},
"path": "/approvalmail/$subscriptions"
}
}
}
```
README.md 1.5 KB
# Azure Service Bus
Enterprise messaging with queues and pub/sub topics.
## When to Use
- Reliable message delivery
- Pub/sub messaging patterns
- Message ordering requirements
- Dead-letter handling
- Transaction support
- Enterprise integration
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| None required | Service Bus is self-contained |
| Key Vault | Store connection strings (legacy) |
## SKU Selection
| SKU | Features | Use Case |
|-----|----------|----------|
| Basic | Queues only, 256KB messages | Simple messaging |
| Standard | Topics, 256KB messages | Pub/sub patterns |
| Premium | 100MB messages, VNET, zones | Enterprise, high throughput |
## Environment Variables
### Managed Identity (Recommended)
| Variable | Value |
|----------|-------|
| `SERVICEBUS__fullyQualifiedNamespace` | `<namespace>.servicebus.windows.net` |
| `SERVICEBUS_NAMESPACE` | Namespace name (for SDK) |
| `SERVICEBUS_QUEUE` | Queue name |
**Required RBAC roles:**
- `Azure Service Bus Data Sender` (69a216fc-b8fb-44d8-bc22-1f3c2cd27a39) - for sending
- `Azure Service Bus Data Receiver` (4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0) - for receiving
### Connection String (Legacy)
| Variable | Value |
|----------|-------|
| `SERVICEBUS_CONNECTION_STRING` | Connection string (Key Vault) |
| `SERVICEBUS_NAMESPACE` | Namespace name |
| `SERVICEBUS_QUEUE` | Queue name |
## References
- [Bicep Patterns](bicep.md)
- [Messaging Patterns](patterns.md)
bicep.md 3.8 KB
# Service Bus - Bicep Patterns
## Namespace
```bicep
resource serviceBus 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' = {
name: '${resourcePrefix}-sb-${uniqueHash}'
location: location
sku: {
name: 'Standard'
tier: 'Standard'
}
}
```
## Queue
```bicep
resource queue 'Microsoft.ServiceBus/namespaces/queues@2022-10-01-preview' = {
parent: serviceBus
name: 'orders'
properties: {
maxDeliveryCount: 10
deadLetteringOnMessageExpiration: true
defaultMessageTimeToLive: 'P14D'
lockDuration: 'PT5M'
}
}
```
## Topic and Subscription
```bicep
resource topic 'Microsoft.ServiceBus/namespaces/topics@2022-10-01-preview' = {
parent: serviceBus
name: 'events'
properties: {
defaultMessageTimeToLive: 'P14D'
}
}
resource subscription 'Microsoft.ServiceBus/namespaces/topics/subscriptions@2022-10-01-preview' = {
parent: topic
name: 'order-processor'
properties: {
maxDeliveryCount: 10
deadLetteringOnMessageExpiration: true
lockDuration: 'PT5M'
}
}
```
## Subscription Filters
### SQL Filter
```bicep
resource filterRule 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2022-10-01-preview' = {
parent: subscription
name: 'high-priority'
properties: {
filterType: 'SqlFilter'
sqlFilter: {
sqlExpression: 'priority = \'high\''
}
}
}
```
### Correlation Filter
```bicep
resource correlationRule 'Microsoft.ServiceBus/namespaces/topics/subscriptions/rules@2022-10-01-preview' = {
parent: subscription
name: 'orders-only'
properties: {
filterType: 'CorrelationFilter'
correlationFilter: {
label: 'order'
}
}
}
```
## Managed Identity Access
### Service Bus Data Receiver (for triggers/consumers)
```bicep
resource serviceBusReceiverRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(serviceBus.id, principalId, 'Azure Service Bus Data Receiver')
scope: serviceBus
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0')
principalId: principalId
principalType: 'ServicePrincipal'
}
}
```
### Service Bus Data Sender (for producers)
```bicep
resource serviceBusSenderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(serviceBus.id, principalId, 'Azure Service Bus Data Sender')
scope: serviceBus
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39')
principalId: principalId
principalType: 'ServicePrincipal'
}
}
```
### Both Sender and Receiver
```bicep
// Grant both sender and receiver roles for bidirectional messaging
resource serviceBusReceiverRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(serviceBus.id, principalId, 'receiver')
scope: serviceBus
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0')
principalId: principalId
principalType: 'ServicePrincipal'
}
}
resource serviceBusSenderRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(serviceBus.id, principalId, 'sender')
scope: serviceBus
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '69a216fc-b8fb-44d8-bc22-1f3c2cd27a39')
principalId: principalId
principalType: 'ServicePrincipal'
}
}
```
> š” **Role Selection:**
> - Use **Data Receiver** for Function triggers or message consumers
> - Use **Data Sender** for applications that send messages
> - Use **both roles** for bidirectional communication
> - Roles can be scoped to namespace (all queues/topics) or specific queue/topic
patterns.md 4.4 KB
# Service Bus - Messaging Patterns
## Point-to-Point (Queue)
```
Producer ā Queue ā Consumer
```
Use for: Work distribution, command processing
## Pub/Sub (Topic + Subscriptions)
```
Publisher ā Topic ā Subscription A ā Consumer A
ā Subscription B ā Consumer B
```
Use for: Event broadcasting, multiple consumers
## SDK Patterns
### Managed Identity (Recommended)
#### Node.js
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../../auth-best-practices.md) for production patterns.
```javascript
const { ServiceBusClient } = require("@azure/service-bus");
const { DefaultAzureCredential } = require("@azure/identity");
const credential = new DefaultAzureCredential();
const fullyQualifiedNamespace = process.env.SERVICEBUS_NAMESPACE + ".servicebus.windows.net";
const client = new ServiceBusClient(fullyQualifiedNamespace, credential);
// Send
const sender = client.createSender("orders");
await sender.sendMessages({ body: { orderId: "123" } });
// Receive
const receiver = client.createReceiver("orders");
const messages = await receiver.receiveMessages(10);
for (const message of messages) {
await receiver.completeMessage(message);
}
```
#### Python
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../../auth-best-practices.md) for production patterns.
```python
from azure.servicebus import ServiceBusClient, ServiceBusMessage
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
fully_qualified_namespace = f"{os.environ['SERVICEBUS_NAMESPACE']}.servicebus.windows.net"
client = ServiceBusClient(fully_qualified_namespace, credential)
# Send
sender = client.get_queue_sender("orders")
with sender:
sender.send_messages(ServiceBusMessage('{"orderId": "123"}'))
# Receive
receiver = client.get_queue_receiver("orders")
with receiver:
messages = receiver.receive_messages(max_message_count=10, max_wait_time=5)
for message in messages:
print(message)
receiver.complete_message(message)
```
#### .NET
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../../auth-best-practices.md) for production patterns.
```csharp
using Azure.Identity;
using Azure.Messaging.ServiceBus;
var credential = new DefaultAzureCredential();
var fullyQualifiedNamespace = $"{Environment.GetEnvironmentVariable("SERVICEBUS_NAMESPACE")}.servicebus.windows.net";
var client = new ServiceBusClient(fullyQualifiedNamespace, credential);
// Send
var sender = client.CreateSender("orders");
await sender.SendMessageAsync(new ServiceBusMessage("{\"orderId\": \"123\"}"));
// Receive
var receiver = client.CreateReceiver("orders");
var messages = await receiver.ReceiveMessagesAsync(maxMessages: 10);
foreach (var message in messages)
{
await receiver.CompleteMessageAsync(message);
}
```
> š” **Required Permissions:**
> - `Azure Service Bus Data Sender` (69a216fc-b8fb-44d8-bc22-1f3c2cd27a39) - for sending
> - `Azure Service Bus Data Receiver` (4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0) - for receiving
### Connection String (Legacy)
#### Node.js
```javascript
const { ServiceBusClient } = require("@azure/service-bus");
const client = new ServiceBusClient(process.env.SERVICEBUS_CONNECTION_STRING);
// Send
const sender = client.createSender("orders");
await sender.sendMessages({ body: { orderId: "123" } });
// Receive
const receiver = client.createReceiver("orders");
const messages = await receiver.receiveMessages(10);
for (const message of messages) {
await receiver.completeMessage(message);
}
```
#### Python
```python
from azure.servicebus import ServiceBusClient, ServiceBusMessage
client = ServiceBusClient.from_connection_string(
os.environ["SERVICEBUS_CONNECTION_STRING"]
)
sender = client.get_queue_sender("orders")
with sender:
sender.send_messages(ServiceBusMessage('{"orderId": "123"}'))
```
#### .NET
```csharp
var client = new ServiceBusClient(
Environment.GetEnvironmentVariable("SERVICEBUS_CONNECTION_STRING")
);
var sender = client.CreateSender("orders");
await sender.SendMessageAsync(new ServiceBusMessage("{\"orderId\": \"123\"}"));
```
## Dead Letter Handling
```javascript
const dlqReceiver = client.createReceiver("orders", {
subQueueType: "deadLetter"
});
```
README.md 1.9 KB
# Azure SQL Database
Managed relational database with ACID compliance and full SQL Server compatibility.
## When to Use
- Relational data with ACID requirements
- Complex queries and joins
- Existing SQL Server workloads
- Reporting and analytics
- Strong schema enforcement
## Authentication
**Default:** Entra-only authentication (recommended)
- Required for subscriptions with Entra-only policies
- More secure than SQL authentication
- Eliminates password management
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| Key Vault | Store connection strings |
| Private Endpoint | Secure access (optional) |
## SKU Selection
| Tier | Use Case | Features |
|------|----------|----------|
| **Basic** | Dev/test, light workloads | 5 DTUs, 2GB |
| **Standard** | Production workloads | 10-3000 DTUs |
| **Premium** | High-performance | In-memory OLTP |
| **Serverless** | Variable workloads | Auto-pause, auto-scale |
| **Hyperscale** | Large databases | 100TB+, instant backup |
## Environment Variables
| Variable | Value | When to Set |
|----------|-------|-------------|
| `AZURE_PRINCIPAL_ID` | Current user's object ID | After `azd init`, before `azd provision` |
| `AZURE_PRINCIPAL_NAME` | Current user's display name | After `azd init`, before `azd provision` |
| `SQL_SERVER` | `{server}.database.windows.net` | Runtime (from Bicep outputs) |
| `SQL_DATABASE` | Database name | Runtime (from Bicep outputs) |
| `SQL_CONNECTION_STRING` | Full connection string (Key Vault) | Runtime (from Bicep outputs) |
**Set principal variables:**
```bash
PRINCIPAL_INFO=$(az ad signed-in-user show --query "{id:id, name:displayName}" -o json)
azd env set AZURE_PRINCIPAL_ID $(echo $PRINCIPAL_INFO | jq -r '.id')
azd env set AZURE_PRINCIPAL_NAME $(echo $PRINCIPAL_INFO | jq -r '.name')
```
## References
- [Bicep Patterns](bicep.md)
- [Entra ID Auth](auth.md)
- [SDK Patterns](sdk.md)
auth.md 4.1 KB
# SQL Database - Entra ID Authentication
## Entra ID Admin Configuration (User)
**Recommended for development** ā Uses signed-in user as admin.
```bicep
param principalId string
param principalName string
@allowed(['User', 'Group', 'Application'])
param principalType string = 'User'
resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
name: '${resourcePrefix}-sql-${uniqueHash}'
location: location
properties: {
administrators: {
administratorType: 'ActiveDirectory'
principalType: principalType
login: principalName
sid: principalId
tenantId: subscription().tenantId
azureADOnlyAuthentication: true
}
minimalTlsVersion: '1.2'
}
}
```
> ā ļø **Warning:** If deploying from CI/CD with a service principal, set `principalType` to `'Application'`. The default `'User'` only works for interactive (human) deployments.
**Get signed-in user info:**
```bash
az ad signed-in-user show --query "{id:id, name:displayName}" -o json
```
**Set as azd environment variables:**
```bash
PRINCIPAL_INFO=$(az ad signed-in-user show --query "{id:id, name:displayName}" -o json)
azd env set AZURE_PRINCIPAL_ID $(echo $PRINCIPAL_INFO | jq -r '.id')
azd env set AZURE_PRINCIPAL_NAME $(echo $PRINCIPAL_INFO | jq -r '.name')
```
> š” **Tip:** Set these immediately after `azd init` to avoid deployment failures.
## Entra ID Admin Configuration (Group)
**Recommended for production** ā Uses Entra group for admin access.
```bicep
resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
name: '${resourcePrefix}-sql-${uniqueHash}'
location: location
properties: {
administrators: {
administratorType: 'ActiveDirectory'
principalType: 'Group'
login: 'SQL Admins'
sid: entraGroupObjectId
tenantId: subscription().tenantId
azureADOnlyAuthentication: true
}
minimalTlsVersion: '1.2'
}
}
```
## Managed Identity Access
Grant app managed identity access via SQL:
```sql
CREATE USER [my-container-app] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [my-container-app];
ALTER ROLE db_datawriter ADD MEMBER [my-container-app];
```
## Common Database Roles
| Role | Permissions |
|------|-------------|
| `db_datareader` | Read all tables |
| `db_datawriter` | Insert, update, delete |
| `db_ddladmin` | Create/modify schema |
| `db_owner` | Full access |
## Connection Strings
### Entra ID Authentication (Recommended)
> ā ļø **MANDATORY**: When generating SQL connection strings for apps with Entra-only auth, ALWAYS include an `Authentication` parameter. Use `Authentication=Active Directory Default` for general scenarios (App Service, local dev) or `Authentication=Active Directory Managed Identity` when a user-assigned managed identity with a specific `User Id` is required (e.g., Azure Functions SQL bindings). Do NOT use manual `AccessToken` assignment, `DefaultAzureCredential().GetToken()` patterns, or bare connection strings without an authentication parameter. The `Authentication` parameter is required in both the Bicep infrastructure output (e.g., App Service `connectionStrings` property) and in any application configuration files (e.g., `appsettings.json`).
```
Server=tcp:{server}.database.windows.net,1433;Database={database};Authentication=Active Directory Default;Encrypt=True;TrustServerCertificate=False;
```
**Required for .NET applications:**
- `Microsoft.Data.SqlClient` (v5.1.0+)
- `Azure.Identity` (for local development)
### Legacy SQL Authentication (ā DO NOT USE - EVER)
> ā **PROHIBITED ā NEVER generate connection strings with `User ID` and `Password` parameters, and NEVER generate `administratorLogin` or `administratorLoginPassword` Bicep properties.**
> This applies unconditionally ā do not include them in any conditional, ternary, or optional branch.
> SQL admin authentication is disabled in all subscriptions with Entra-only policies and violates Azure security best practices.
> Always use an Entra ID `Authentication=Active Directory Default` or `Authentication=Active Directory Managed Identity` connection string above.
bicep.md 8.0 KB
# SQL Database - Bicep Patterns
> ā **CRITICAL ā SQL Server Bicep MUST use Entra-only authentication. NEVER include `administratorLogin` or `administratorLoginPassword` anywhere in a Bicep file ā not even inside a conditional (`condition ? { ... } : { administratorLoginPassword: ... }`) branch. If either property name appears anywhere in the file, the deployment will be rejected. Always use the pattern below.**
## Basic Setup (Entra-Only Authentication)
**Required approach** ā Uses Microsoft Entra ID authentication only. Required for subscriptions with Entra-only policies; SQL admin authentication is disabled by policy in those environments.
```bicep
param principalId string
param principalName string
@allowed(['User', 'Group', 'Application'])
param principalType string = 'User'
resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
name: '${resourcePrefix}-sql-${uniqueHash}'
location: location
properties: {
administrators: {
administratorType: 'ActiveDirectory'
principalType: principalType
login: principalName
sid: principalId
tenantId: subscription().tenantId
azureADOnlyAuthentication: true
}
minimalTlsVersion: '1.2'
}
}
resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-05-01-preview' = {
parent: sqlServer
name: 'appdb'
location: location
sku: {
name: 'Basic'
tier: 'Basic'
}
properties: {
collation: 'SQL_Latin1_General_CP1_CI_AS'
maxSizeBytes: 2147483648 // 2 GB
}
}
resource sqlFirewallAzure 'Microsoft.Sql/servers/firewallRules@2022-05-01-preview' = {
parent: sqlServer
name: 'AllowAzureServices'
properties: {
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
}
}
```
**Set Entra admin parameters:**
1. Get current user info:
```bash
az ad signed-in-user show --query "{id:id, name:displayName}" -o json
```
> ā ļø **Warning:** If deploying from CI/CD with a service principal, set `principalType` to `'Application'`. The default `'User'` only works for interactive (human) deployments. Mismatched `principalType` causes `UnmatchedPrincipalType` errors during provisioning.
2. Set as azd environment variables:
```bash
PRINCIPAL_INFO=$(az ad signed-in-user show --query "{id:id, name:displayName}" -o json)
azd env set AZURE_PRINCIPAL_ID $(echo $PRINCIPAL_INFO | jq -r '.id')
azd env set AZURE_PRINCIPAL_NAME $(echo $PRINCIPAL_INFO | jq -r '.name')
```
> š” **Tip:** Set these variables immediately after `azd init` to avoid deployment failures. The Bicep `principalId` and `principalName` parameters will automatically use these environment variables.
## ā ļø MANDATORY: Connection String with Entra Auth Parameter
> **CRITICAL:** When outputting SQL connection strings in Bicep (e.g., in App Service `connectionStrings` or as outputs), ALWAYS include an `Authentication` parameter. Use `Authentication=Active Directory Default` for general scenarios or `Authentication=Active Directory Managed Identity` when a user-assigned managed identity with a specific `User Id` is required. A bare `Server=tcp:...;Database=...;` connection string without this parameter will not authenticate via Entra ID and the app will fail to connect.
**Correct pattern in Bicep (Active Directory Default):**
```bicep
connectionStrings: [
{
name: 'DefaultConnection'
connectionString: 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${sqlDatabase.name};Authentication=Active Directory Default;Encrypt=True;TrustServerCertificate=False;'
type: 'SQLAzure'
}
]
```
**With user-assigned managed identity:**
```bicep
connectionStrings: [
{
name: 'DefaultConnection'
connectionString: 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=${sqlDatabase.name};Authentication=Active Directory Managed Identity;User Id=${uamiClientId};Encrypt=True;TrustServerCertificate=False;'
type: 'SQLAzure'
}
]
```
> ā **DO NOT** generate connection strings without an `Authentication=Active Directory Default` or `Authentication=Active Directory Managed Identity` parameter.
> ā **DO NOT** use manual `AccessToken` assignment or `DefaultAzureCredential().GetToken()` in application code as an alternative to the connection string parameter.
## Serverless Configuration
```bicep
resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-05-01-preview' = {
parent: sqlServer
name: 'appdb'
location: location
sku: {
name: 'GP_S_Gen5'
tier: 'GeneralPurpose'
family: 'Gen5'
capacity: 2
}
properties: {
autoPauseDelay: 60 // minutes
minCapacity: json('0.5')
}
}
```
## Private Endpoint
```bicep
resource sqlPrivateEndpoint 'Microsoft.Network/privateEndpoints@2023-05-01' = {
name: '${sqlServer.name}-pe'
location: location
properties: {
subnet: {
id: subnet.id
}
privateLinkServiceConnections: [
{
name: '${sqlServer.name}-connection'
properties: {
privateLinkServiceId: sqlServer.id
groupIds: ['sqlServer']
}
}
]
}
}
```
## ā MANDATORY: SQL Data-Plane Access via postprovision Hook
> **CRITICAL:** ARM/Bicep role assignments (`SQL DB Contributor`) only grant **control-plane** access. They do **not** grant the app **data-plane** access to the database. Without the T-SQL grant below, apps using `Authentication=Active Directory Default` will crash on startup with a login failure.
>
> **When you generate SQL + Managed Identity infrastructure you MUST also:**
> 1. Add a `postprovision` hook to `azure.yaml` that runs the SQL grant script
> 2. Copy the pre-built scripts from [scripts/grant-sql-access.sh](scripts/grant-sql-access.sh) and [scripts/grant-sql-access.ps1](scripts/grant-sql-access.ps1) to `scripts/` in the user's project root
**azure.yaml hooks section (add or merge):**
```yaml
hooks:
postprovision:
posix:
shell: sh
run: ./scripts/grant-sql-access.sh
windows:
shell: pwsh
run: ./scripts/grant-sql-access.ps1
```
**Copy the pre-built scripts** ā Read [scripts/grant-sql-access.sh](scripts/grant-sql-access.sh) and [scripts/grant-sql-access.ps1](scripts/grant-sql-access.ps1) and write them verbatim to the project's `scripts/` folder. Do not regenerate them from scratch.
Key behaviours of the scripts:
- Installs the `rdbms-connect` Azure CLI extension if not already present (required for `az sql db query`)
- Loads `azd env get-values` safely (no `eval`)
- Grants `db_datareader` + `db_datawriter` by default (idempotent)
- Set `SQL_GRANT_DDLADMIN=true` in the azd env to also grant `db_ddladmin` (needed for EF Core migrations)
- `SERVICE_WEB_NAME` takes priority over `SERVICE_API_NAME` when resolving the app identity
> š” Make executable: `chmod +x scripts/*.sh`
> š” If the app also uses EF Core migrations, combine this with the migration hook ā see the `azure-deploy` skill's `references/recipes/azd/sql-managed-identity.md`.
## Legacy SQL Authentication (ā DO NOT USE - EVER)
> ā **PROHIBITED ā NEVER generate `administratorLogin` or `administratorLoginPassword` properties.**
> This applies unconditionally: do NOT include them in direct assignments, conditional expressions, ternary branches, parameter defaults, or any other construct. The **text** of these property names must not appear anywhere in a generated `.bicep` file.
> SQL admin authentication is disabled in all subscriptions with Entra-only policies and violates Azure security best practices.
> Always use the Entra-only authentication pattern at the top of this file.
>
> ā **DO NOT generate conditional patterns like:**
> ```bicep
> properties: useEntraAuth ? {
> administrators: {
> azureADOnlyAuthentication: true
> // ...
> }
> } : {
> administratorLogin: sqlAdminLogin // ā PROHIBITED
> administratorLoginPassword: sqlAdminPassword // ā PROHIBITED
> }
> ```
> Use only the unconditional Entra-only pattern. No fallback. No toggle. No conditional.
sdk.md 1.9 KB
# SQL Database - SDK Patterns
## Node.js (mssql)
```javascript
const sql = require('mssql');
const config = {
server: process.env.SQL_SERVER,
database: process.env.SQL_DATABASE,
authentication: {
type: 'azure-active-directory-default'
},
options: {
encrypt: true
}
};
const pool = await sql.connect(config);
```
## Python (pyodbc)
> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](../../auth-best-practices.md) for production patterns.
```python
import pyodbc
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
token = credential.get_token("https://database.windows.net/.default")
conn = pyodbc.connect(
f"Driver={{ODBC Driver 18 for SQL Server}};"
f"Server={os.environ['SQL_SERVER']};"
f"Database={os.environ['SQL_DATABASE']};"
f"Authentication=ActiveDirectoryMsi"
)
```
## .NET (Entity Framework Core)
**Required NuGet Packages:**
```bash
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.Data.SqlClient --version 5.1.0
dotnet add package Azure.Identity
```
**Connection string (Entra ID):**
```
Server=tcp:{server}.database.windows.net,1433;Database={database};Authentication=Active Directory Default;Encrypt=True;
```
**Configuration:**
```csharp
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
sqlOptions => sqlOptions.EnableRetryOnFailure()
));
```
**appsettings.json:**
```json
{
"ConnectionStrings": {
"DefaultConnection": "Server=tcp:myserver.database.windows.net,1433;Database=mydb;Authentication=Active Directory Default;Encrypt=True;"
}
}
```
## Connection String Format
```
Server=tcp:{server}.database.windows.net,1433;Database={database};Authentication=Active Directory Default;Encrypt=True;
```
grant-sql-access.ps1 3.5 KB
# Grant Azure SQL data-plane access to the App Service / Container App managed identity.
#
# USAGE: Copy this file to scripts/grant-sql-access.ps1 in your project root and add
# a postprovision hook in azure.yaml:
#
# hooks:
# postprovision:
# posix:
# shell: sh
# run: ./scripts/grant-sql-access.sh
# windows:
# shell: pwsh
# run: ./scripts/grant-sql-access.ps1
#
# ENVIRONMENT VARIABLES (sourced from azd env):
# SQL_SERVER - SQL server name (without .database.windows.net)
# SQL_DATABASE - Database name
# AZURE_RESOURCE_GROUP - Resource group name
# SERVICE_WEB_NAME - App Service name (used when set, takes priority)
# SERVICE_API_NAME - API service name (fallback when SERVICE_WEB_NAME is not set)
# SQL_GRANT_DDLADMIN - Set to "true" to also grant db_ddladmin (needed for EF migrations)
$ErrorActionPreference = 'Stop'
# Load azd environment variables
azd env get-values | ForEach-Object {
$name, $value = $_.Split('=', 2)
Set-Item "env:$name" $value.Trim('"')
}
# Determine app identity name (App Service uses SERVICE_WEB_NAME, APIs use SERVICE_API_NAME)
$AppName = if ($env:SERVICE_WEB_NAME) { $env:SERVICE_WEB_NAME } else { $env:SERVICE_API_NAME }
if (-not $AppName) {
throw "ERROR: Neither SERVICE_WEB_NAME nor SERVICE_API_NAME is set in azd environment."
}
Write-Host "Granting SQL data-plane access to managed identity: $AppName"
# Build idempotent SQL grant queries (reader + writer, required for all apps)
$SqlQuery = @"
IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = '$AppName')
CREATE USER [$AppName] FROM EXTERNAL PROVIDER;
IF NOT EXISTS (
SELECT 1 FROM sys.database_role_members drm
JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id
JOIN sys.database_principals m ON drm.member_principal_id = m.principal_id
WHERE r.name = 'db_datareader' AND m.name = '$AppName'
)
ALTER ROLE db_datareader ADD MEMBER [$AppName];
IF NOT EXISTS (
SELECT 1 FROM sys.database_role_members drm
JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id
JOIN sys.database_principals m ON drm.member_principal_id = m.principal_id
WHERE r.name = 'db_datawriter' AND m.name = '$AppName'
)
ALTER ROLE db_datawriter ADD MEMBER [$AppName];
"@
# Optionally grant db_ddladmin (needed when EF Core migrations run at startup or via hook)
$GrantDdlAdmin = $env:SQL_GRANT_DDLADMIN -eq 'true'
if ($GrantDdlAdmin) {
$SqlQuery += @"
IF NOT EXISTS (
SELECT 1 FROM sys.database_role_members drm
JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id
JOIN sys.database_principals m ON drm.member_principal_id = m.principal_id
WHERE r.name = 'db_ddladmin' AND m.name = '$AppName'
)
ALTER ROLE db_ddladmin ADD MEMBER [$AppName];
"@
}
# Ensure the rdbms-connect extension is installed (provides 'az sql db query')
az extension show --name rdbms-connect *> $null
if ($LASTEXITCODE -ne 0) {
Write-Host "Azure CLI extension 'rdbms-connect' is not installed. Installing..."
az extension add --name rdbms-connect --yes
if ($LASTEXITCODE -ne 0) {
throw "ERROR: Failed to install Azure CLI extension 'rdbms-connect'. Cannot continue because 'az sql db query' requires it."
}
}
az sql db query `
--server $env:SQL_SERVER `
--database $env:SQL_DATABASE `
--resource-group $env:AZURE_RESOURCE_GROUP `
--auth-mode ActiveDirectoryDefault `
--queries $SqlQuery
Write-Host "SQL access granted successfully."
grant-sql-access.sh 3.6 KB
#!/bin/bash
# Grant Azure SQL data-plane access to the App Service / Container App managed identity.
#
# USAGE: Copy this file to scripts/grant-sql-access.sh in your project root and add
# a postprovision hook in azure.yaml:
#
# hooks:
# postprovision:
# posix:
# shell: sh
# run: ./scripts/grant-sql-access.sh
# windows:
# shell: pwsh
# run: ./scripts/grant-sql-access.ps1
#
# ENVIRONMENT VARIABLES (sourced from azd env):
# SQL_SERVER - SQL server name (without .database.windows.net)
# SQL_DATABASE - Database name
# AZURE_RESOURCE_GROUP - Resource group name
# SERVICE_WEB_NAME - App Service name (used when set, takes priority)
# SERVICE_API_NAME - API service name (fallback when SERVICE_WEB_NAME is not set)
# SQL_GRANT_DDLADMIN - Set to "true" to also grant db_ddladmin (needed for EF migrations)
set -e
# Safely load azd environment variables without eval
while IFS= read -r line; do
[ -n "$line" ] || continue
key=${line%%=*}
value=${line#*=}
case "$value" in
\"*\") value=${value#\"}; value=${value%\"} ;;
\'*\') value=${value#\'}; value=${value%\'} ;;
esac
export "$key=$value"
done < <(azd env get-values)
# Determine app identity name (App Service uses SERVICE_WEB_NAME, APIs use SERVICE_API_NAME)
APP_NAME=${SERVICE_WEB_NAME:-$SERVICE_API_NAME}
if [ -z "$APP_NAME" ]; then
echo "ERROR: Neither SERVICE_WEB_NAME nor SERVICE_API_NAME is set in azd environment." >&2
exit 1
fi
echo "Granting SQL data-plane access to managed identity: $APP_NAME"
# Build idempotent SQL grant queries (reader + writer, required for all apps)
SQL_QUERIES="
IF NOT EXISTS (SELECT * FROM sys.database_principals WHERE name = '$APP_NAME')
CREATE USER [$APP_NAME] FROM EXTERNAL PROVIDER;
IF NOT EXISTS (
SELECT 1 FROM sys.database_role_members drm
JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id
JOIN sys.database_principals m ON drm.member_principal_id = m.principal_id
WHERE r.name = 'db_datareader' AND m.name = '$APP_NAME'
)
ALTER ROLE db_datareader ADD MEMBER [$APP_NAME];
IF NOT EXISTS (
SELECT 1 FROM sys.database_role_members drm
JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id
JOIN sys.database_principals m ON drm.member_principal_id = m.principal_id
WHERE r.name = 'db_datawriter' AND m.name = '$APP_NAME'
)
ALTER ROLE db_datawriter ADD MEMBER [$APP_NAME];
"
# Optionally grant db_ddladmin (needed when EF Core migrations run at startup or via hook)
SQL_GRANT_DDLADMIN="${SQL_GRANT_DDLADMIN:-false}"
if [ "$SQL_GRANT_DDLADMIN" = "true" ]; then
SQL_QUERIES="$SQL_QUERIES
IF NOT EXISTS (
SELECT 1 FROM sys.database_role_members drm
JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id
JOIN sys.database_principals m ON drm.member_principal_id = m.principal_id
WHERE r.name = 'db_ddladmin' AND m.name = '$APP_NAME'
)
ALTER ROLE db_ddladmin ADD MEMBER [$APP_NAME];
"
fi
# Ensure the rdbms-connect extension is installed (provides 'az sql db query')
if ! az extension show --name rdbms-connect >/dev/null 2>&1; then
echo "Azure CLI extension 'rdbms-connect' is not installed. Installing..."
if ! az extension add --name rdbms-connect --yes; then
echo "ERROR: Failed to install Azure CLI extension 'rdbms-connect'. Ensure Azure CLI has network access and retry." >&2
exit 1
fi
fi
az sql db query \
--server "$SQL_SERVER" \
--database "$SQL_DATABASE" \
--resource-group "$AZURE_RESOURCE_GROUP" \
--auth-mode ActiveDirectoryDefault \
--queries "$SQL_QUERIES"
echo "SQL access granted successfully."
README.md 1.4 KB
# Azure Static Web Apps
Serverless hosting for static sites and SPAs with integrated APIs.
## When to Use
- Single Page Applications (React, Vue, Angular)
- Static sites (HTML/CSS/JS)
- JAMstack applications
- Sites with serverless API backends
- Documentation sites
## Service Type in azure.yaml
```yaml
services:
my-web:
host: staticwebapp
project: ./src/web
```
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| None required | Static Web Apps is fully managed |
| Application Insights | Monitoring (optional) |
## SKU Selection
| SKU | Features |
|-----|----------|
| Free | 2 custom domains, 0.5GB storage, shared bandwidth |
| Standard | 5 custom domains, 2GB storage, SLA, auth customization |
## Build Configuration
| Framework | outputLocation |
|-----------|----------------|
| React | `build` |
| Vue | `dist` |
| Angular | `dist/my-app` |
| Next.js (Static) | `out` |
## API Integration
Integrated Functions API structure:
```
project/
āāā src/ # Frontend
āāā api/ # Azure Functions API
āāā hello/
ā āāā index.js
āāā host.json
```
## References
- [Region Availability](region-availability.md)
- [Bicep Patterns](bicep.md)
- [Terraform Patterns](terraform.md)
- [Routing and Auth](routing.md)
- [Deployment](deployment.md)
bicep.md 1.1 KB
# Static Web Apps - Bicep Patterns
## Basic Resource
```bicep
resource staticWebApp 'Microsoft.Web/staticSites@2022-09-01' = {
name: '${resourcePrefix}-${serviceName}-${uniqueHash}'
location: location
sku: {
name: 'Standard'
tier: 'Standard'
}
properties: {
buildProperties: {
appLocation: '/'
apiLocation: 'api'
outputLocation: 'dist'
}
}
}
```
## Custom Domain
```bicep
resource customDomain 'Microsoft.Web/staticSites/customDomains@2022-09-01' = {
parent: staticWebApp
name: 'www.example.com'
properties: {}
}
```
## Application Settings
For the integrated API:
```bicep
resource staticWebAppSettings 'Microsoft.Web/staticSites/config@2022-09-01' = {
parent: staticWebApp
name: 'appsettings'
properties: {
DATABASE_URL: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=db-url)'
}
}
```
## Deployment Token
> ā ļø **Security Warning:** Do NOT expose deployment tokens in Bicep outputs.
See [deployment.md](deployment.md) for secure token handling.
deployment.md 1.9 KB
# Static Web Apps - Deployment
## azd Deploy (Default)
Standard deployment via Azure Developer CLI:
```bash
azd deploy
```
## GitHub-Linked Deployments
For CI/CD builds on Azure (instead of azd deploy):
```bicep
properties: {
repositoryUrl: 'https://github.com/owner/repo'
branch: 'main'
buildProperties: {
appLocation: 'src'
apiLocation: 'api'
outputLocation: 'dist'
}
}
```
## Deployment Token
> ā ļø **Security Warning:** Do NOT expose deployment tokens in ARM/Bicep outputs. Deployment outputs are visible in Azure portal deployment history and logs.
**Recommended approach** - retrieve token via Azure CLI and store directly in secret store:
```bash
# Capture token to variable (never echo or log)
DEPLOYMENT_TOKEN=$(az staticwebapp secrets list --name <app-name> --query "properties.apiKey" -o tsv)
# Store directly in Key Vault
az keyvault secret set --vault-name <vault-name> --name swa-deployment-token --value "$DEPLOYMENT_TOKEN" --output none
```
**Do NOT do this** (exposes token in deployment history):
```bicep
// ā INSECURE - token visible in deployment history
// output deploymentToken string = staticWebApp.listSecrets().properties.apiKey
```
## Terraform Deployment
> ā ļø **Use `azurerm_static_web_app`** ā NOT Storage Account `static_website`.
> Storage Account static websites require anonymous blob access, which is blocked
> by enterprise Azure Policies. See [terraform.md](terraform.md) for correct patterns.
Terraform-based deployments use `azd deploy` the same way as Bicep:
```bash
azd deploy
```
The `azd-service-name` tag on the `azurerm_static_web_app` resource tells azd where to deploy.
**Do NOT do this** (exposes token in Terraform state):
```hcl
# ā INSECURE - token stored in Terraform state
# output "deployment_token" {
# value = azurerm_static_web_app.web.api_key
# sensitive = true
# }
```
region-availability.md 0.7 KB
# SWA Region Availability
ā ļø **NOT available in many common regions** ā Check before deployment.
| ā
Available | ā NOT Available (will FAIL) |
|-------------|------------------------------|
| `westus2` | `eastus` |
| `centralus` | `northeurope` |
| `eastus2` | `southeastasia` |
| `westeurope` | `uksouth` |
| `eastasia` | `canadacentral` |
| | `australiaeast` |
| | `westus3` |
## Recommended Regions
| Pattern | Use |
|---------|-----|
| SWA only | `westus2`, `centralus`, `eastus2`, `westeurope`, `eastasia` |
| SWA + backend | `westus2`, `centralus`, `eastus2`, `westeurope`, `eastasia` |
| SWA + Azure OpenAI | `eastus2` (only region with full overlap) |
routing.md 1.3 KB
# Static Web Apps - Routing & Authentication
## Route Configuration
Create `staticwebapp.config.json` in the app root:
```json
{
"routes": [
{
"route": "/api/*",
"allowedRoles": ["authenticated"]
}
],
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/api/*", "/*.{png,jpg,gif}"]
},
"responseOverrides": {
"404": {
"rewrite": "/404.html"
}
}
}
```
## Authentication
### Built-in Providers
```json
{
"routes": [
{
"route": "/admin/*",
"allowedRoles": ["admin"]
}
],
"auth": {
"identityProviders": {
"azureActiveDirectory": {
"registration": {
"openIdIssuer": "https://login.microsoftonline.com/{tenant-id}",
"clientIdSettingName": "AAD_CLIENT_ID",
"clientSecretSettingName": "AAD_CLIENT_SECRET"
}
}
}
}
}
```
### Supported Providers
- Azure Active Directory / Entra ID
- GitHub
- Twitter
- Custom OpenID Connect
## Role-Based Access
```json
{
"routes": [
{ "route": "/admin/*", "allowedRoles": ["admin"] },
{ "route": "/account/*", "allowedRoles": ["authenticated"] },
{ "route": "/*", "allowedRoles": ["anonymous"] }
]
}
```
terraform.md 3.4 KB
# Static Web Apps ā Terraform Patterns ā REFERENCE ONLY
> ā **DO NOT COPY THIS CODE DIRECTLY**
>
> This file contains **reference patterns** for understanding Azure Static Web Apps Terraform structure.
> Infrastructure for Static Web Apps must be composed using the Azure Prepare skill workflow, not copied directly from this reference.
>
> When composing infrastructure:
> - Start from an approved base template for Static Web Apps defined by your platform or template team.
> - Use `azurerm_static_web_app` ā **NEVER** use Storage Account `static_website` for static web app hosting.
>
> Hand-writing Terraform from these patterns will result in missing tags, broken azd deploy, and policy violations.
> ā ļø **WARNING: Do NOT use Storage Account static website hosting.**
> Storage Account `static_website` requires anonymous blob access, which violates
> enterprise Azure Policies (`RequestDisallowedByPolicy: "Anonymous blob access is not allowed"`).
> Always use `azurerm_static_web_app` instead ā it is fully managed and policy-compliant.
## Basic Resource
```hcl
resource "azurerm_static_web_app" "web" {
name = "swa-${var.environment_name}-${var.service_name}-${var.unique_hash}"
resource_group_name = azurerm_resource_group.main.name
location = var.location
# sku_tier defaults to "Free"; set to "Standard" for production features
sku_tier = "Standard"
# Required for azd deploy to find this resource
tags = merge(var.tags, {
"azd-service-name" = var.service_name
})
}
```
> ā ļø **Region availability is limited.** Check [region-availability.md](region-availability.md) before selecting a region.
> š” **Key Points:**
> - No Storage Account, Service Plan, or other supporting resources required
> - Static Web Apps is a fully managed service ā storage is built-in
> - The `azd-service-name` tag is **required** for `azd deploy` to locate the resource
## Custom Domain
```hcl
resource "azurerm_static_web_app_custom_domain" "example" {
static_web_app_id = azurerm_static_web_app.web.id
domain_name = "www.example.com"
validation_type = "cname-delegation"
}
```
## Application Settings
For the integrated API backend:
```hcl
resource "azurerm_static_web_app_function_app_registration" "api" {
static_web_app_id = azurerm_static_web_app.web.id
function_app_id = azurerm_linux_function_app.api.id
}
```
Environment variables for the SWA (available to both frontend and managed Functions API):
```hcl
resource "azurerm_static_web_app" "web" {
# ... (base configuration from above)
app_settings = {
"DATABASE_URL" = "@Microsoft.KeyVault(VaultName=${azurerm_key_vault.kv.name};SecretName=db-url)"
}
}
```
## Outputs
```hcl
output "WEB_URL" {
value = azurerm_static_web_app.web.default_host_name
}
output "STATIC_WEB_APP_NAME" {
value = azurerm_static_web_app.web.name
}
```
> š” **Tip:** Output names in UPPERCASE are automatically set as azd environment variables.
## Deployment Token
> ā ļø **Security Warning:** Do NOT expose deployment tokens in Terraform outputs.
See [deployment.md](deployment.md) for secure token handling.
## azure.yaml Integration
```yaml
services:
web:
project: ./src/web
language: js
host: staticwebapp
dist: dist
```
## References
- [AZD + Terraform](../../recipes/azd/terraform.md) ā Full azd+Terraform project setup
- [Region Availability](region-availability.md) ā SWA is NOT available in all regions
- [Routing and Auth](routing.md)
- [Deployment](deployment.md)
README.md 1.5 KB
# Azure Storage
Scalable cloud storage for blobs, files, queues, and tables.
## When to Use
- Blob storage (files, images, videos)
- File shares (SMB/NFS)
- Queue storage (simple messaging)
- Table storage (NoSQL key-value)
- Static website hosting
## Required Supporting Resources
| Resource | Purpose |
|----------|---------|
| None required | Storage is self-contained |
| Key Vault | Store connection strings |
| Private Endpoint | Secure access (optional) |
## SKU Selection
| SKU | Replication | Use Case |
|-----|-------------|----------|
| Standard_LRS | Local (3 copies) | Dev/test, non-critical |
| Standard_ZRS | Zone-redundant | Production, regional HA |
| Standard_GRS | Geo-redundant | DR requirements |
| Premium_LRS | Premium SSD | High performance |
## Storage Types
| Type | Best For |
|------|----------|
| Blob | Files, images, videos, backups, logs |
| Queue | Simple message queuing, decoupling |
| Table | NoSQL key-value data |
| File Share | Lift-and-shift, SMB/NFS access |
## Access Tiers
| Tier | Use Case |
|------|----------|
| Hot | Frequent access |
| Cool | Infrequent access (30+ days) |
| Archive | Rare access (180+ days) |
## Environment Variables
| Variable | Value |
|----------|-------|
| `AZURE_STORAGE_CONNECTION_STRING` | Connection string (Key Vault) |
| `AZURE_STORAGE_ACCOUNT` | Account name |
| `AZURE_STORAGE_CONTAINER` | Container name |
## References
- [Bicep Patterns](bicep.md)
- [Access Patterns](access.md)
access.md 3.7 KB
# Storage - Access Patterns
## Prerequisites for Granting Storage Access
> ā ļø **Important**: To assign storage roles to managed identities, you need:
> - **User Access Administrator** or **Owner** role on the Storage Account (or parent resource group/subscription)
> - The role must include the `Microsoft.Authorization/roleAssignments/write` permission
**Common scenarios**:
- Granting Storage Blob Data Owner to a Web App or Function App's managed identity (System Assigned or User Assigned)
- Adding read/write access to blobs, queues, and tables for application workloads
- Allowing user identities (developers, data admins) access in dev/test environments
- Allowing applications to access storage using managed identity instead of connection strings
**Scope best practices**:
- Grant roles at the **smallest scope possible** (e.g., specific storage account, not resource group or subscription)
- Avoid broad scopes (Resource Group, Subscription, Tenant) unless absolutely necessary
- Prefer resource-level assignments for production workloads
**Managed identity types**:
- **System Assigned**: Automatically created with the resource (Web App, Function). Default when using `DefaultAzureCredential`.
- **User Assigned**: Standalone identity that can be shared across resources. Requires additional configuration:
- Set `AZURE_CLIENT_ID` app setting to the User Assigned Managed Identity's client ID
- Configure identity in Bicep with both `type: 'SystemAssigned, UserAssigned'` and `userAssignedIdentities`
If you encounter `AuthorizationFailed` errors when assigning roles, ensure you have User Access Administrator or Owner permissions at the target scope.
## Managed Identity Role Assignment
```bicep
resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(storageAccount.id, principalId, 'Storage Blob Data Contributor')
scope: storageAccount
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')
principalId: principalId
principalType: 'ServicePrincipal'
}
}
```
## Storage Roles
| Role | Permissions |
|------|-------------|
| Storage Blob Data Reader | Read blobs |
| Storage Blob Data Contributor | Read/write blobs |
| Storage Queue Data Contributor | Read/write queues |
| Storage Table Data Contributor | Read/write tables |
## SDK Connection Patterns
### Node.js
```javascript
const { BlobServiceClient } = require("@azure/storage-blob");
const blobServiceClient = BlobServiceClient.fromConnectionString(
process.env.AZURE_STORAGE_CONNECTION_STRING
);
const containerClient = blobServiceClient.getContainerClient("uploads");
```
### Python
```python
from azure.storage.blob import BlobServiceClient
blob_service_client = BlobServiceClient.from_connection_string(
os.environ["AZURE_STORAGE_CONNECTION_STRING"]
)
container_client = blob_service_client.get_container_client("uploads")
```
### .NET
```csharp
var blobServiceClient = new BlobServiceClient(
Environment.GetEnvironmentVariable("AZURE_STORAGE_CONNECTION_STRING")
);
var containerClient = blobServiceClient.GetBlobContainerClient("uploads");
```
## Managed Identity Access
Use `DefaultAzureCredential` for local development (in production, use `ManagedIdentityCredential` ā see [auth-best-practices.md](../../auth-best-practices.md)):
```javascript
const { DefaultAzureCredential } = require("@azure/identity");
const { BlobServiceClient } = require("@azure/storage-blob");
const client = new BlobServiceClient(
`https://${accountName}.blob.core.windows.net`,
new DefaultAzureCredential()
);
```
bicep.md 1.8 KB
# Storage - Bicep Patterns
## Storage Account
```bicep
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: '${resourcePrefix}stor${uniqueHash}'
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
allowBlobPublicAccess: false
minimumTlsVersion: 'TLS1_2'
supportsHttpsTrafficOnly: true
}
}
```
## Blob Container
```bicep
resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
parent: storageAccount
name: 'default'
properties: {
deleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
parent: blobService
name: 'uploads'
properties: {
publicAccess: 'None'
}
}
```
## Queue
```bicep
resource queueService 'Microsoft.Storage/storageAccounts/queueServices@2023-01-01' = {
parent: storageAccount
name: 'default'
}
resource queue 'Microsoft.Storage/storageAccounts/queueServices/queues@2023-01-01' = {
parent: queueService
name: 'orders'
}
```
## Table
```bicep
resource tableService 'Microsoft.Storage/storageAccounts/tableServices@2023-01-01' = {
parent: storageAccount
name: 'default'
}
resource table 'Microsoft.Storage/storageAccounts/tableServices/tables@2023-01-01' = {
parent: tableService
name: 'logs'
}
```
## File Share
```bicep
resource fileService 'Microsoft.Storage/storageAccounts/fileServices@2023-01-01' = {
parent: storageAccount
name: 'default'
}
resource fileShare 'Microsoft.Storage/storageAccounts/fileServices/shares@2023-01-01' = {
parent: fileService
name: 'shared'
properties: {
shareQuota: 100 // GB
}
}
```
License (MIT)
View full license text
MIT License Copyright 2025 (c) Microsoft Corporation. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.