Skip to content
Merged
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
21 changes: 20 additions & 1 deletion docs/handbook/10-deploy-azure-free.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,28 @@ az webapp config appsettings set --name $APP --resource-group $RG --settings \
Jwt__Issuer="https://$APP.azurewebsites.net" \
Jwt__Audience="widgetworks-spa" \
Payments__Provider="Mock" \
Email__Provider="Dev"
Email__Provider="Dev" \
RateLimiting__TrustForwardedFor="true"
```

`RateLimiting__TrustForwardedFor` is not optional here. App Service is a reverse proxy, so every
request reaches the app carrying the *proxy's* address. Left `false`, every caller in the world
collapses into a single throttling partition and the per-caller limits become a global cap that the
first busy minute trips for everybody. The app logs a warning once when the setting and the traffic
disagree — if you see it, this is the line you missed. See
[throttling](04-configuration-and-2fa.md#throttling-and-the-one-setting-that-can-cause-an-outage).

**Health probe.** On Basic or higher, point the platform at readiness rather than liveness:

```bash
az webapp config set --name $APP --resource-group $RG --health-check-path /health/ready
```

Health check is unavailable on **F1/Free** — the setting simply will not apply. There, use an
external monitor (or a scheduled workflow) against `/health/ready` every 30–60 minutes. Not more
often: readiness queries the database, and waking it on the keep-warm cadence is exactly the cost
`/health` exists to avoid.

Verify every reference resolved — a broken one shows an error instead of `Resolved`:

```bash
Expand Down
35 changes: 32 additions & 3 deletions infra/Provision.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,42 @@ Write-Ok "SPA deployed to $SwaUrl"
# ---------------------------------------------------------- 10. CORS loop ----
# Named origin, never a wildcard - the app sends credentials. App__BaseUrl is what password-reset
# emails build their links from; left at localhost it sends customers to their own machine.
Write-Step 'Wiring CORS and email links'
#
# RateLimiting__TrustForwardedFor rides along here, in the block that runs on every provision,
# rather than in section 6 which -SkipInfra skips. It is a correctness setting, not an
# infrastructure one: App Service is a reverse proxy, so every request reaches the app carrying
# the proxy's address. Left false, all callers collapse into one throttling partition and the
# per-caller limits become a global cap that the first busy minute trips for everybody. The app
# logs a warning when the setting and the traffic disagree, but a provision run should not
# produce an environment that needs the warning.
Write-Step 'Wiring CORS, email links, and proxy-aware throttling'
Set-AppSettings -App $AppName -Group $ResourceGroup -Settings @{
'Cors__AllowedOrigins' = $SwaUrl
'App__BaseUrl' = $SwaUrl
'Cors__AllowedOrigins' = $SwaUrl
'App__BaseUrl' = $SwaUrl
'RateLimiting__TrustForwardedFor' = 'true'
}
Write-Ok 'Set'

# ------------------------------------------------------- 11. health probe ----
# /health is liveness and deliberately does not touch the database - the keep-warm schedule pings
# it every few minutes, and waking a serverless database on that cadence would cost far more than
# the free allowance. /health/ready is the one that answers "can this instance actually serve",
# so that is what the platform should watch.
#
# Health check needs Basic or higher; on the F1 free tier the setting does not apply. Rather than
# fail a free-tier provision, say so and point at the substitute.
Write-Step 'Health check path'
if ($Sku -eq 'F1' -or $Sku -eq 'D1') {
Write-Warn "Health check is not available on the $Sku tier - skipping."
Write-Host ' Substitute: point an external monitor (or a scheduled workflow) at' -ForegroundColor DarkGray
Write-Host " $ApiUrl/health/ready on a 30-60 minute interval. Not more often:" -ForegroundColor DarkGray
Write-Host ' a readiness check wakes the database, which is what /health avoids.' -ForegroundColor DarkGray
} else {
Invoke-Az webapp config set --name $AppName --resource-group $ResourceGroup `
--health-check-path '/health/ready' --output none | Out-Null
Write-Ok 'Probing /health/ready (readiness, queries the database)'
}

Write-Host @"

Done
Expand Down