Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# API Template — REFERENCE ONLY

REST and gRPC API services on Azure Container Apps with ingress configuration.

## When to Use

- REST API with OpenAPI/Swagger
- gRPC service
- API gateway backend
- Backend-for-frontend (BFF) pattern

## Project Structure

```
project-root/
├── azure.yaml
├── Dockerfile
├── src/
│ └── (API code)
└── infra/
├── main.bicep
└── app/
└── api.bicep
```

## azure.yaml

```yaml
name: my-api
metadata:
template: container-apps-api
services:
api:
host: containerapp
project: .
language: <js|ts|python|csharp|java|go>
```

## Bicep — API Container App

```bicep
param name string
param location string = resourceGroup().location
param tags object = {}
param envId string
param containerRegistryName string
param imageName string
param userAssignedIdentityId string
param isGrpc bool = false

resource api 'Microsoft.App/containerApps@2024-03-01' = {
name: name
location: location
tags: union(tags, { 'azd-service-name': 'api' })
identity: {
type: 'UserAssigned'
userAssignedIdentities: { '${userAssignedIdentityId}': {} }
}
properties: {
managedEnvironmentId: envId
configuration: {
ingress: {
external: true
targetPort: 8080
transport: isGrpc ? 'http2' : 'auto'
corsPolicy: {
allowedOrigins: ['*']
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
allowedHeaders: ['*']
}
Comment on lines +66 to +70
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaulting CORS to allowedOrigins: ['*'] is an insecure baseline for an API template and is easy to copy into production unintentionally. Consider omitting corsPolicy from the base template (and documenting it as an opt-in), or parameterizing allowed origins with a safe default (e.g., empty list / explicit origins only).

Copilot uses AI. Check for mistakes.
}
registries: [
{
server: '${containerRegistryName}.azurecr.io'
identity: userAssignedIdentityId
}
]
}
template: {
containers: [
{
name: 'api'
image: imageName
resources: { cpu: json('0.5'), memory: '1Gi' }
env: [
{ name: 'PORT', value: '8080' }
]
}
]
scale: {
minReplicas: 1
maxReplicas: 20
rules: [
{
name: 'http-scale'
http: { metadata: { concurrentRequests: '50' } }
}
]
}
}
}
}

output fqdn string = api.properties.configuration.ingress.fqdn
output name string = api.name
```

## REST vs gRPC

| Setting | REST | gRPC |
|---------|------|------|
| `transport` | `auto` | `http2` |
| `targetPort` | 8080 | 8080 |
| Ingress | External or internal | External or internal |

> ⚠️ **gRPC requires `transport: 'http2'`** — without it, gRPC calls fail.

## Internal API (No External Ingress)

For backend APIs only consumed by other Container Apps:

```bicep
ingress: {
external: false // internal only
targetPort: 8080
transport: 'auto'
}
```

Internal APIs are reachable at `https://<app-name>.internal.<env-domain>`.

## API with Authentication

Use Easy Auth or integrate with Microsoft Entra ID:

```bicep
configuration: {
ingress: {
external: true
targetPort: 8080
}
}
```

> 💡 **Tip:** For API authentication, consider using the built-in authentication
> feature of Container Apps (Easy Auth) or validating JWT tokens in your application code.

## CORS Configuration

Restrict `allowedOrigins` for production:

```bicep
corsPolicy: {
allowedOrigins: ['https://myapp.example.com']
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
allowedHeaders: ['Authorization', 'Content-Type']
maxAge: 3600
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Functions on Container Apps — REFERENCE ONLY

Azure Functions hosted on Container Apps for event-driven triggers and bindings
with Container Apps scaling and networking.

## When to Use

- Event-driven processing requiring Functions triggers/bindings
- Need KEDA-based scaling with Functions programming model
- Want Container Apps networking (VNet, private endpoints) with Functions
- Migrating from Functions Consumption/Premium to Container Apps

## Why Functions on Container Apps?

| Feature | Functions (Flex) | Functions on ACA |
|---------|-----------------|------------------|
| Programming model | Functions v4 | Functions v4 |
| Triggers/bindings | ✅ Full support | ✅ Full support |
| Scaling | Flex Consumption | KEDA (Container Apps) |
| Networking | VNet integration | Container Apps VNet |
| Container support | Managed | Full Dockerfile control |
| Dapr integration | ❌ | ✅ |
| Side-cars | ❌ | ✅ |

## Project Structure

```
project-root/
├── azure.yaml
├── Dockerfile
├── host.json
├── src/
│ └── (Functions code)
└── infra/
├── main.bicep
└── app/
└── functions-app.bicep
```

## Dockerfile

```dockerfile
# Example: Node.js Functions on ACA
FROM mcr.microsoft.com/azure-functions/node:4-node20

ENV AzureWebJobsScriptRoot=/home/site/wwwroot
COPY . /home/site/wwwroot
RUN cd /home/site/wwwroot && npm install --production
```

## azure.yaml

```yaml
name: my-functions-aca
services:
api:
host: containerapp
project: .
language: js
```

## Bicep — Functions on Container Apps

```bicep
param name string
param location string = resourceGroup().location
param tags object = {}
param envId string
param containerRegistryName string
param imageName string
param userAssignedIdentityId string
param storageAccountName string

resource funcApp 'Microsoft.App/containerApps@2024-03-01' = {
name: name
location: location
tags: union(tags, { 'azd-service-name': 'api' })
identity: {
type: 'UserAssigned'
userAssignedIdentities: { '${userAssignedIdentityId}': {} }
}
properties: {
managedEnvironmentId: envId
configuration: {
ingress: {
external: true
targetPort: 80
transport: 'auto'
}
registries: [
{
server: '${containerRegistryName}.azurecr.io'
identity: userAssignedIdentityId
}
]
}
template: {
containers: [
{
name: 'functions'
image: imageName
resources: { cpu: json('0.5'), memory: '1Gi' }
env: [
{
name: 'AzureWebJobsStorage__accountName'
value: storageAccountName
}
{
name: 'AzureWebJobsStorage__credential'
value: 'managedidentity'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
]
}
]
scale: {
minReplicas: 0
maxReplicas: 30
}
}
}
}
```

## Supported Triggers

All Functions triggers work on Container Apps:

| Trigger | KEDA Scaler | Notes |
|---------|-------------|-------|
| HTTP | `http` | Built-in HTTP scaling |
| Timer | `cron` | Cron-based scheduling |
| Service Bus | `azure-servicebus` | Queue/topic scaling |
| Event Hubs | `azure-eventhub` | Partition-based scaling |
| Cosmos DB | `azure-cosmosdb` | Change feed scaling |
| Blob Storage | `azure-blob` | Blob count scaling |
| Storage Queue | `azure-queue` | Queue length scaling |

## KEDA Scale Rules for Triggers

```bicep
scale: {
minReplicas: 0
maxReplicas: 30
rules: [
{
name: 'servicebus-scale'
custom: {
type: 'azure-servicebus'
metadata: {
queueName: 'myqueue'
namespace: 'my-sb-namespace'
messageCount: '5'
}
}
}
]
}
```

## Key Differences from Standard Functions

1. **You manage the Dockerfile** — base image must be `mcr.microsoft.com/azure-functions/<runtime>`
2. **Scaling is KEDA-based** — configure scale rules explicitly
3. **Storage is still required** — Functions runtime needs `AzureWebJobsStorage`
4. **No Flex Consumption billing** — billed as Container Apps

> ⚠️ **Always use the official Functions base images** from MCR.
> Custom base images may break the Functions runtime.
Loading
Loading