Skip to main content

Azure App Service

The simplest path to a production FastAPI app on Azure — git push or ZIP deploy, TLS included, Managed Identity with one toggle.

When to choose App Service​

  • Steady, predictable traffic (not spiky)
  • You want PaaS simplicity without managing Kubernetes
  • Zero-downtime deployments with deployment slots (blue/green)
  • Built-in TLS, custom domains, and autoscaling without extra config
  • Team prefers az webapp deploy over YAML manifests

Create the App Service​

# Create a resource group and App Service plan (Linux, B2 = 2 vCPU / 3.5 GB)
az group create --name my-rg --location eastus

az appservice plan create \
--name my-plan \
--resource-group my-rg \
--is-linux \
--sku B2

# Create the web app (Python 3.14)
az webapp create \
--name my-fastapi-app \
--resource-group my-rg \
--plan my-plan \
--runtime "PYTHON:3.14"

Enable Managed Identity​

az webapp identity assign \
--name my-fastapi-app \
--resource-group my-rg

# Capture the principal ID for RBAC assignments
PRINCIPAL_ID=$(az webapp identity show \
--name my-fastapi-app \
--resource-group my-rg \
--query principalId -o tsv)

Grant Key Vault access​

az role assignment create \
--assignee-object-id "$PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>

Configure app settings​

# No secrets here — only non-sensitive config
az webapp config appsettings set \
--name my-fastapi-app \
--resource-group my-rg \
--settings \
AZURE_KEYVAULT_URL="https://myvault.vault.azure.net/" \
APPLICATIONINSIGHTS_CONNECTION_STRING="<your-connection-string>" \
WEBSITES_PORT=8000

Observability with Azure Monitor​

Install Azure Monitor OpenTelemetry — covers distributed tracing, structured logging, and metrics in one package:

uv add azure-monitor-opentelemetry

Call configure_azure_monitor once at startup, before the FastAPI app is instantiated:

import os
from azure.monitor.opentelemetry import configure_azure_monitor

if conn_str := os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING"):
configure_azure_monitor(connection_string=conn_str)

After this, every HTTP request is traced automatically and all logging output is forwarded to Application Insights Logs.

Secrets from Key Vault​

import os
from contextlib import asynccontextmanager
from typing import AsyncIterator
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
client = SecretClient(
vault_url=os.environ["AZURE_KEYVAULT_URL"],
credential=DefaultAzureCredential(), # resolves Managed Identity automatically
)
app.state.jwt_secret = client.get_secret("jwt-signing-key").value
app.state.db_password = client.get_secret("db-password").value
yield


app = FastAPI(lifespan=lifespan)

Startup command​

az webapp config set \
--name my-fastapi-app \
--resource-group my-rg \
--startup-file "uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4"

Deploy​

Option A: ZIP deploy (simplest)​

# Package the app (excluding dev files)
zip -r app.zip . \
--exclude ".git/*" \
--exclude ".venv/*" \
--exclude "website/*" \
--exclude "*.pyc" \
--exclude "__pycache__/*"

az webapp deploy \
--name my-fastapi-app \
--resource-group my-rg \
--src-path app.zip \
--type zip
# Push to ACR
az acr build --registry myacr --image fastapi-books:latest .

# Configure the web app to pull from ACR with MI
az webapp config container set \
--name my-fastapi-app \
--resource-group my-rg \
--container-image-name myacr.azurecr.io/fastapi-books:latest \
--container-registry-url https://myacr.azurecr.io

# Grant AcrPull so App Service can pull images without credentials
az role assignment create \
--assignee-object-id "$PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role AcrPull \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ContainerRegistry/registries/myacr

Zero-downtime deploys with slots​

# Create a staging slot
az webapp deployment slot create \
--name my-fastapi-app \
--resource-group my-rg \
--slot staging

# Deploy to staging first
az webapp deploy \
--name my-fastapi-app \
--resource-group my-rg \
--slot staging \
--src-path app.zip \
--type zip

# Swap staging → production (zero downtime)
az webapp deployment slot swap \
--name my-fastapi-app \
--resource-group my-rg \
--slot staging \
--target-slot production

Autoscaling​

az monitor autoscale create \
--resource-group my-rg \
--resource my-plan \
--resource-type Microsoft.Web/serverfarms \
--name autoscale-policy \
--min-count 2 \
--max-count 10 \
--count 2

az monitor autoscale rule create \
--resource-group my-rg \
--autoscale-name autoscale-policy \
--condition "CpuPercentage > 70 avg 5m" \
--scale out 2

Health check​

App Service can use your /api/ps endpoint to take unhealthy instances out of rotation automatically:

az webapp config set \
--name my-fastapi-app \
--resource-group my-rg \
--generic-configurations '{"healthCheckPath": "/api/ps"}'