Skip to content

Private Network Deployment

Use this recipe when the simple public az webapp up flow is no longer enough and your Flask app needs outbound private connectivity plus identity-based access to Azure services.

flowchart TD
    INET[Internet] --> WA[Web App\nApp Service Linux\nPython 3.11]

    subgraph VNET[Virtual Network]
        INT[Integration Subnet]
        PE[Private Endpoint Subnet]
    end

    WA --> INT
    PE --> ST[Storage Account]
    WA -.-> MI[System-Assigned Managed Identity]
    MI -.-> ENTRA[Microsoft Entra ID]

    style WA fill:#0078d4,color:#fff

Prerequisites

  • Completed 01 - Local Run
  • Existing Azure subscription and Azure CLI authentication
  • App Service app already created or ready to create in a supported tier for VNet integration
  • Permissions to create VNets, subnets, private endpoints, storage accounts, and managed identity assignments

Main Content

Step 1: Prepare deployment variables

RG="rg-flask-tutorial"
LOCATION="koreacentral"
APP_NAME="app-flask-tutorial-abc123"
VNET_NAME="vnet-flask-tutorial"
INTEGRATION_SUBNET_NAME="snet-appsvc-integration"
PE_SUBNET_NAME="snet-private-endpoints"
STORAGE_NAME="stflasktutorialabc123"
Command/Parameter Purpose
RG="rg-flask-tutorial" Defines the resource group that holds the App Service and networking resources.
LOCATION="koreacentral" Sets the Azure region for the VNet and storage resources.
APP_NAME="app-flask-tutorial-abc123" Identifies the target App Service app.
VNET_NAME="vnet-flask-tutorial" Names the virtual network used for private connectivity.
INTEGRATION_SUBNET_NAME="snet-appsvc-integration" Names the subnet delegated to App Service VNet integration.
PE_SUBNET_NAME="snet-private-endpoints" Names the subnet reserved for private endpoints.
STORAGE_NAME="stflasktutorialabc123" Sets a globally unique storage account name for the example backend.

Step 2: Create the VNet and delegated integration subnet

az network vnet create --resource-group $RG --name $VNET_NAME --location $LOCATION --address-prefixes 10.0.0.0/16
az network vnet subnet create --resource-group $RG --vnet-name $VNET_NAME --name $INTEGRATION_SUBNET_NAME --address-prefixes 10.0.1.0/24 --delegations Microsoft.Web/serverFarms
Command/Parameter Purpose
az network vnet create Creates the virtual network that will host the deployment subnets.
--resource-group $RG Places the VNet in the selected resource group.
--name $VNET_NAME Sets the VNet name.
--location $LOCATION Creates the VNet in the selected Azure region.
--address-prefixes 10.0.0.0/16 Defines the overall CIDR range for the VNet.
az network vnet subnet create Creates a subnet inside the VNet.
--vnet-name $VNET_NAME Targets the subnet creation to the named VNet.
--name $INTEGRATION_SUBNET_NAME Names the delegated integration subnet.
--address-prefixes 10.0.1.0/24 Defines the CIDR range for the integration subnet.
--delegations Microsoft.Web/serverFarms Delegates the subnet to App Service so VNet integration can use it.

Step 3: Create the private endpoint subnet

az network vnet subnet create --resource-group $RG --vnet-name $VNET_NAME --name $PE_SUBNET_NAME --address-prefixes 10.0.2.0/24 --disable-private-endpoint-network-policies true
Command/Parameter Purpose
az network vnet subnet create Creates the subnet that will host private endpoints.
--resource-group $RG Uses the same resource group as the VNet.
--vnet-name $VNET_NAME Creates the subnet inside the named VNet.
--name $PE_SUBNET_NAME Names the private endpoint subnet.
--address-prefixes 10.0.2.0/24 Defines the CIDR range for the private endpoint subnet.
--disable-private-endpoint-network-policies true Disables subnet policies that would block private endpoint NICs.

Step 4: Integrate the web app with the VNet

az webapp vnet-integration add --resource-group $RG --name $APP_NAME --vnet $VNET_NAME --subnet $INTEGRATION_SUBNET_NAME
Command/Parameter Purpose
az webapp vnet-integration add Connects the web app to a delegated subnet for outbound private access.
--resource-group $RG Selects the resource group containing the app.
--name $APP_NAME Selects the target App Service app.
--vnet $VNET_NAME Chooses the virtual network used for integration.
--subnet $INTEGRATION_SUBNET_NAME Chooses the delegated integration subnet.

Step 5: Assign managed identity to the web app

az webapp identity assign --resource-group $RG --name $APP_NAME
Command/Parameter Purpose
az webapp identity assign Enables a system-assigned managed identity on the web app.
--resource-group $RG Selects the resource group containing the app.
--name $APP_NAME Targets the specific App Service instance.

Step 6: Create a private endpoint for Storage

az storage account create --resource-group $RG --name $STORAGE_NAME --location $LOCATION --sku Standard_LRS --kind StorageV2
STORAGE_ID="$(az storage account show --resource-group $RG --name $STORAGE_NAME --query id --output tsv)"
az network private-endpoint create --resource-group $RG --name pe-storage-blob --vnet-name $VNET_NAME --subnet $PE_SUBNET_NAME --private-connection-resource-id $STORAGE_ID --group-id blob --connection-name pe-storage-blob-connection
Command/Parameter Purpose
az storage account create Creates the storage account used in the private endpoint example.
--resource-group $RG Places the storage account in the selected resource group.
--name $STORAGE_NAME Sets the storage account name.
--location $LOCATION Creates the storage account in the selected region.
--sku Standard_LRS Uses standard locally redundant storage.
--kind StorageV2 Creates a general-purpose v2 storage account.
STORAGE_ID="$(...)" Stores the storage account resource ID in a shell variable.
az storage account show Retrieves the existing storage account metadata.
--query id Returns only the id field from the command output.
--output tsv Formats the ID as plain text for shell assignment.
az network private-endpoint create Creates a private endpoint for the storage account.
--name pe-storage-blob Names the private endpoint resource.
--vnet-name $VNET_NAME Places the private endpoint in the selected VNet.
--subnet $PE_SUBNET_NAME Uses the subnet reserved for private endpoints.
--private-connection-resource-id $STORAGE_ID Points the private endpoint at the storage account resource.
--group-id blob Connects the endpoint to the Blob service subresource.
--connection-name pe-storage-blob-connection Names the private link connection object.

A private endpoint only helps if the app's DNS resolves the standard storage FQDN to the private IP. Create the privatelink.blob.core.windows.net zone, link it to the VNet, and wire the private endpoint to it with a DNS zone group.

az network private-dns zone create --resource-group $RG --name "privatelink.blob.core.windows.net"
az network private-dns link vnet create --resource-group $RG --zone-name "privatelink.blob.core.windows.net" --name link-blob --virtual-network $VNET_NAME --registration-enabled false
az network private-endpoint dns-zone-group create --resource-group $RG --endpoint-name pe-storage-blob --name pe-storage-blob-zonegroup --private-dns-zone "privatelink.blob.core.windows.net" --zone-name blob
Command/Parameter Purpose
az network private-dns zone create Creates the private DNS zone for the Blob service.
--name "privatelink.blob.core.windows.net" Uses the required privatelink zone name for Blob storage.
az network private-dns link vnet create Links the zone to the VNet so the app resolves private records.
--registration-enabled false The zone is for resolution only, not auto-registration.
az network private-endpoint dns-zone-group create Binds the private endpoint's A record into the zone automatically.
--zone-name blob Matches the Blob subresource group used on the private endpoint.

Always resolve the standard FQDN

Point your app settings and code at https://$STORAGE_NAME.blob.core.windows.net. The Private DNS zone resolves that standard name to the private endpoint IP. Never hardcode the privatelink. hostname.

Step 8: Restrict the storage account's public network access

With the private path in place, close the public endpoint so the account is reachable only through the private endpoint.

az storage account update --resource-group $RG --name $STORAGE_NAME --public-network-access Disabled --default-action Deny
Command/Parameter Purpose
az storage account update Updates the storage account network configuration.
--public-network-access Disabled Turns off the public endpoint entirely; only private endpoints can reach the account.
--default-action Deny Denies any traffic not matched by an explicit network rule.

Public access vs. authorization are separate

Disabling public access controls the network path only. The app's managed identity still needs a data-plane role such as Storage Blob Data Contributor to read or write data. See App Service to Azure Storage connectivity.

Verification

az webapp vnet-integration list --resource-group $RG --name $APP_NAME --output table
az webapp identity show --resource-group $RG --name $APP_NAME --output json
az network private-endpoint list --resource-group $RG --output table
Command/Parameter Purpose
az webapp vnet-integration list Lists the current VNet integration attached to the web app.
--resource-group $RG Selects the app resource group.
--name $APP_NAME Targets the web app being validated.
--output table Formats the VNet integration results for quick inspection.
az webapp identity show Displays the managed identity configuration for the app.
--output json Returns the identity details as JSON.
az network private-endpoint list Lists private endpoints in the resource group.

Troubleshooting

  • If VNet integration fails, confirm the integration subnet is delegated to Microsoft.Web/serverFarms.
  • If private endpoint creation fails, confirm the private endpoint subnet has network policies disabled.
  • If managed identity authentication fails, confirm the identity is enabled and RBAC or access policies are configured on the target service.

Run It in the Portal

Portal view: Access Restrictions blade (before-state inbound access configuration)

Access Restrictions blade with public network access on its default enabled behavior, unmatched rule action set to Allow, and only the default Allow all rule

Purpose: Show the inbound access state before any access restriction is added.

Look for: Public network access shows Enabled (using default behavior), Unmatched rule action is Allow, and the only rule is Allow all with priority 2147483647.

Expected result: With no custom rules, the displayed default-endpoint policy admits any source until you add rules or disable public network access. This is a configuration reading, not a measured reachability test.

The Access Restrictions blade is the Portal surface that displays the access configuration of this app while you work through the recipe's VNet integration, managed identity, and storage private endpoint steps. In the visible default state, Public network access is enabled with default behavior and the rules table contains only Allow all, so this screenshot works as a before-state reminder that those outbound/private-connectivity steps do not by themselves change the displayed inbound configuration. The blade shows configuration, not whether any client can reach the app: a private endpoint, DNS, routing and the applicable main-site or SCM policy (depending on the target and ingress path) also decide that, and the effective-policy reading is in Private Endpoint and Access Restriction Decisions. Use it only as a before-state configuration reference, not as evidence that the recipe itself has already added access-restriction rules.

See Also

Sources