Azure Container Apps sits in a useful middle ground: you get container-level control without managing Kubernetes directly, plus built-in autoscaling (including scale-to-zero), traffic splitting, and Dapr integration if you want it. Here's how we containerize and ship a .NET 10 API to it.
- Use a multi-stage Dockerfile so the final image only contains the published output, not the SDK.
- Scale rules are KEDA-based — you can scale on HTTP concurrency, queue depth, or CPU, including down to zero replicas.
- Pull secrets from Key Vault via a managed identity rather than baking them into environment variables or the image.
- Use revisions and traffic splitting for blue-green or canary rollouts instead of replacing a single running revision.
A multi-stage Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["OrderApi/OrderApi.csproj", "OrderApi/"]
RUN dotnet restore "OrderApi/OrderApi.csproj"
COPY . .
WORKDIR /src/OrderApi
RUN dotnet publish -c Release -o /app/publish --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "OrderApi.dll"]
Provisioning the environment
A Container Apps environment groups your apps into a shared network boundary with a Log Analytics workspace. Create it once per environment (dev/staging/prod), then deploy apps into it.
az containerapp env create \
--name order-api-env \
--resource-group order-api-rg \
--location eastus
az containerapp create \
--name order-api \
--resource-group order-api-rg \
--environment order-api-env \
--image myregistry.azurecr.io/order-api:latest \
--target-port 8080 \
--ingress external \
--min-replicas 0 \
--max-replicas 10
Scaling rules
Container Apps uses KEDA under the hood, so you can scale on HTTP concurrency, a queue's message count, or CPU/memory — including down to zero replicas when there's no traffic, which matters for internal or low-traffic APIs.
{
"scale": {
"minReplicas": 0,
"maxReplicas": 10,
"rules": [
{
"name": "http-concurrency",
"http": { "metadata": { "concurrentRequests": "50" } }
}
]
}
}
minReplicas to at least 1 and let scale-to-zero apply only to genuinely bursty internal workloads.Secrets via managed identity, not environment variables
Reference Key Vault secrets directly rather than copying secret values into Container Apps' own secret store or plain environment variables. Grant the app's managed identity get access on the vault, then reference the secret by URI.
az containerapp identity assign --name order-api --resource-group order-api-rg --system-assigned
az keyvault set-policy \
--name order-api-kv \
--object-id <managed-identity-principal-id> \
--secret-permissions get
az containerapp secret set \
--name order-api \
--resource-group order-api-rg \
--secrets "db-connection=keyvaultref:https://order-api-kv.vault.azure.net/secrets/db-connection,identityref:system"
Revisions and traffic splitting
Every deployment creates a new revision. Instead of cutting traffic over instantly, split it between the old and new revision and shift gradually while you watch error rates and latency.
az containerapp ingress traffic set \
--name order-api \
--resource-group order-api-rg \
--revision-weight order-api--v2=20 order-api--v1=80
Once the new revision has proven itself at partial traffic, shift the remaining weight over and deactivate the old revision. If something looks wrong, shifting weight back is a single command, not a redeploy.