Stage 4 — Network Isolation¶
Trigger: "Compliance requires private data access."
Stage 3 made the app safe to expose and ready to grow. Stage 4 makes the data tier private: the Azure SQL logical server stops accepting public connections, a Private Endpoint gives it an address inside a virtual network, a private DNS zone makes the SQL FQDN resolve to that private address, and the web app reaches it over regional VNet integration.
Before you start¶
Read these foundations first — Stage 4 applies the decisions they describe:
- Network topology basics — how VNets, subnets, and private endpoints fit together.
- Private connectivity patterns — when a private endpoint is the right tool and how DNS makes it work.
- Hub-spoke vs Virtual WAN — where a single-VNet stage sits on the topology spectrum.
What you'll build¶
flowchart TD
User[Public user] -->|HTTPS 443| AFD[Azure Front Door<br/>Standard, global edge]
AFD --> WAF[WAF policy<br/>Prevention, DRS 2.1]
WAF -->|Route /*, probe /healthz| WebApp[App Service Web App<br/>Linux S1, DOTNETCORE 8.0]
WebApp -->|VNet integration<br/>WEBSITE_VNET_ROUTE_ALL| IntSubnet[Integration subnet<br/>delegated Microsoft.Web/serverFarms]
IntSubnet --> PE[SQL Private Endpoint<br/>private-endpoint subnet]
DNS[Private DNS zone<br/>privatelink.database.windows.net] -.->|Resolves FQDN to 10.x| WebApp
PE -->|Private link| SQL[(Azure SQL Database<br/>publicNetworkAccess Disabled)]
WebApp -->|Managed identity| KV[Key Vault<br/>SqlConnectionString]
WebApp -->|Telemetry| AI[Application Insights]
AI --> LA[Log Analytics workspace]
subgraph VNet[Virtual network 10.10.0.0/16]
IntSubnet
PE
end
subgraph RG[Single resource group]
WebApp
KV
SQL
AI
LA
VNet
DNS
end | Resource | SKU / Tier | Role |
|---|---|---|
| Virtual network | 10.10.0.0/16, two subnets | Private network fabric for the stage |
| Integration subnet | 10.10.1.0/24, delegated | Hosts the App Service regional VNet integration |
| Private endpoint subnet | 10.10.2.0/24 | Hosts the SQL Private Endpoint |
| Private DNS zone | privatelink.database.windows.net | Resolves the SQL FQDN to the private IP |
| SQL Private Endpoint | sqlServer group | Private network path into the database |
| Azure SQL Database | Basic (2 GB), public access Disabled | Catalog and order data, private-only |
| App Service plan + Web App | Linux S1, DOTNETCORE\|8.0 | Runs the Storefront app, egress via VNet |
| Front Door + WAF | Standard, Prevention | Global edge entry point (unchanged from Stage 3) |
| Key Vault | Standard, RBAC | Custody of the SQL connection string |
| Application Insights + Log Analytics | Workspace-based | Telemetry and 30-day retention |
| Autoscale + Action Group + alerts | CPU, Http5xx, response time | Scaling and alerting (unchanged from Stage 3) |
Cost: ~$0.24–$0.36/hour. Time: 35–55 minutes.
Prerequisites¶
- Azure CLI logged in (
az login) with rights to create resource groups, virtual networks, private endpoints, and role assignments. - A strong SQL administrator password exported as
SQL_ADMIN_PASSWORD(never commit it). - An Entra principal for the SQL admin, exported as
SQL_ENTRA_ADMIN_LOGINandSQL_ENTRA_ADMIN_OBJECT_ID. - An operations notification email exported as
ALERT_EMAIL_ADDRESS.
Deploy¶
The generic driver scripts under scripts/practical/ wrap the Bicep deployment:
export SQL_ADMIN_PASSWORD='<choose-a-strong-password>'
export SQL_ENTRA_ADMIN_LOGIN='<entra-user-or-group-display-name>'
export SQL_ENTRA_ADMIN_OBJECT_ID='<entra-object-id>'
export ALERT_EMAIL_ADDRESS='<ops-notification-email>'
scripts/practical/deploy-stage.sh stage-04
To deploy the Bicep directly instead:
az group create --resource-group rg-practical-storefront-stage04 --location koreacentral
az deployment group create \
--resource-group rg-practical-storefront-stage04 \
--template-file infra/bicep/stages/stage-04-network-isolation/main.bicep \
--parameters infra/bicep/stages/stage-04-network-isolation/main.bicepparam \
--parameters sqlAdministratorLoginPassword="$SQL_ADMIN_PASSWORD"
| Command | Purpose |
|---|---|
az group create --resource-group rg-practical-storefront-stage04 --location koreacentral | Creates the resource group that holds every Stage 4 resource. |
--resource-group rg-practical-storefront-stage04 | Names the resource group to create. |
--location koreacentral | Sets the Azure region for the resource group. |
az deployment group create | Deploys the Bicep template into the resource group. |
--resource-group rg-practical-storefront-stage04 | Targets the resource group that receives the deployment. |
--template-file infra/bicep/stages/stage-04-network-isolation/main.bicep | Points to the Bicep template to deploy. |
--parameters infra/bicep/stages/stage-04-network-isolation/main.bicepparam | Supplies deployment parameters from the .bicepparam file. |
--parameters sqlAdministratorLoginPassword="$SQL_ADMIN_PASSWORD" | Overrides the SQL administrator password inline from the exported variable. |
Verify¶
This runs three smoke tests:
- HTTP smoke —
GET /on the origin returns200,GET /healthzreturns{"status":"Healthy"},GET /ops/inforeturns JSON with aversionfield. - Front Door smoke — confirms the endpoint is enabled, a WAF policy is associated, the origin group probes
/healthz, and the autoscale maximum is 2. A non-200edge response is reported as a warning because Front Door endpoints propagate globally over several minutes. - Private connectivity smoke — confirms SQL
publicNetworkAccessisDisabled, the private endpoint connection isApproved, and theprivatelink.database.windows.netDNS zone exists and is linked to the VNet.
The public-endpoint SQL smoke test from earlier stages is intentionally dropped here, because with public access disabled the deploy host can no longer reach the database over the internet — that is the point of this stage.
Spot-check individual controls:
az sql server show --name <sqlServer> --resource-group rg-practical-storefront-stage04 --query publicNetworkAccess
az network private-endpoint show --name <sqlPrivateEndpoint> --resource-group rg-practical-storefront-stage04 --query "privateLinkServiceConnections[0].privateLinkServiceConnectionState.status"
az network private-dns zone show --name privatelink.database.windows.net --resource-group rg-practical-storefront-stage04 --query name
| Command | Purpose |
|---|---|
az sql server show --name <sqlServer> --resource-group rg-practical-storefront-stage04 --query publicNetworkAccess | Returns whether the SQL server's public network access is disabled. |
az network private-endpoint show --name <sqlPrivateEndpoint> --resource-group rg-practical-storefront-stage04 --query "privateLinkServiceConnections[0].privateLinkServiceConnectionState.status" | Returns the approval status of the SQL private endpoint connection. |
az network private-dns zone show --name privatelink.database.windows.net --resource-group rg-practical-storefront-stage04 --query name | Confirms the private DNS zone for SQL exists and returns its name. |
To confirm the app actually resolves SQL privately, open the App Service SSH/Kudu console and run nslookup <sqlServer>.database.windows.net; it should resolve to a 10.x.x.x address in the private-endpoint subnet.
See labs/trunk/stage-04-network-isolation/ for the full checklist, sample requests, and expected results.
Best practices embedded in this stage¶
- Separate public ingress from private data — user traffic still enters through Front Door on the public edge, but the database has no public face. Reachability is a deliberate choice made per tier, not a default.
- Private DNS is architecture, not an afterthought — the
privatelink.database.windows.netzone and its VNet link are provisioned in the same template. Without the zone link, the app would resolve the SQL FQDN to its public IP and the private endpoint would go unused. - Lock down the data tier first —
publicNetworkAccessisDisabledand the public firewall rules are removed, so the Private Endpoint is the only path into the database.
The web app origin is still reached by Front Door over its public hostname at this stage. Fronting the origin privately and locking it down to this Front Door instance (via the
X-Azure-FDIDheader and theAzureFrontDoor.Backendservice tag) happen in a later stage. Stage 4 isolates the data tier, not the compute ingress.
Clean up¶
This deletes the resource group and everything in it.
Go deeper¶
- Private internal app — network and access
- Private internal app — baseline architecture
- Design Lab 02 — private internal app
- Network topology cheatsheet
See Also¶
- Stage 3 — Scale / Edge
- Network topology basics
- Private connectivity patterns
- Hub-spoke vs Virtual WAN