App Service is the workhorse PaaS for web applications on Azure, old enough to be unfashionable and good enough to keep winning anyway. Most teams use a tenth of it. The three features that separate a production deployment from a portal experiment: slots, the networking pair, and the hardening baseline.
Slots: Deployment Safety for Free
A staging slot is a second instance of your app on the same plan with its own hostname and configuration. Deploy there, warm it, smoke test it, then swap: the platform exchanges the slots’ content behind the frontends after warming the incoming instance, so production never serves a cold start, and rollback is swapping back, seconds not redeployments. The detail everyone misses is slot settings: configuration marked sticky stays with the slot (connection to the staging database stays on staging), everything else travels with the swap. Audit that split before your first production swap, because a production app swapping in staging’s connection string is a classic incident. Auto swap exists for teams that want deploy to staging to flow to production automatically after warmup; I prefer the explicit swap in the pipeline with a gate, consistent with the environments discipline from the DevOps post.
resource "azurerm_linux_web_app" "this" {
name = "app-orders-prod"
location = "westeurope"
resource_group_name = azurerm_resource_group.web.name
service_plan_id = azurerm_service_plan.this.id
https_only = true
public_network_access_enabled = false
virtual_network_subnet_id = azurerm_subnet.appsvc_integration.id
identity {
type = "SystemAssigned"
}
site_config {
minimum_tls_version = "1.2"
ftps_state = "Disabled"
http2_enabled = true
always_on = true
health_check_path = "/healthz"
vnet_route_all_enabled = true
}
app_settings = {
"WEBSITE_RUN_FROM_PACKAGE" = "1"
"KeyVaultUri" = azurerm_key_vault.app.vault_uri
}
}
resource "azurerm_linux_web_app_slot" "staging" {
name = "staging"
app_service_id = azurerm_linux_web_app.this.id
site_config {
always_on = true
health_check_path = "/healthz"
}
}
The Networking Pair
App Service networking confuses people because inbound and outbound are separate features. Private endpoints handle inbound: the app gets a private IP, public access goes off, and traffic arrives via Front Door with Private Link (the pattern from that post) or from inside the network. Regional VNet integration handles outbound: the app’s calls to your databases, Key Vault, and internal APIs egress through a delegated subnet, subject to your NSGs, routes, and firewall, with vnet_route_all making sure all traffic (not just RFC1918) takes the integrated path. Production apps generally want both, and the two smoke tests that verify them: resolve and reach the app only privately, and confirm the app’s outbound calls hit private endpoints (check the resolved IPs in logs), not public ones.
Scale and the Hardening Floor
Autoscale on the plan, not the app: scale out on requests or CPU with the asymmetric thresholds argued in the VMSS post, minimum two instances zone balanced for production (zone redundancy requires Premium v3 and at least two instances), and the health check path configured so the platform recycles broken instances instead of routing to them. The hardening floor, most of it visible in the Terraform above: HTTPS only with TLS 1.2 minimum, FTP disabled, run from package so the filesystem is immutable and deployments atomic, managed identity for every downstream call with Key Vault references for settings, and Easy Auth in front of internal apps, platform level Entra authentication that returns 401s before a single line of your code runs, which for admin tools and internal dashboards is an entire class of vulnerabilities deleted by a checkbox. Diagnostics to Log Analytics, and the app is boring, which is the compliment infrastructure earns.
Cheers
Osama
Leave a comment