02A - First Deploy (Private Egress)¶
Deploy a Java Function App to an Elastic Premium plan (EP1) with VNet integration and private endpoint support, then publish code and verify the app is live.
Prerequisites¶
- You completed 01 - Run Locally.
- You are signed in to Azure CLI and have Contributor access.
- You already exported:
$RG,$APP_NAME,$PLAN_NAME,$STORAGE_NAME,$LOCATION(usekoreacentralfor this guide).
| Tool | Minimum version | Purpose |
|---|---|---|
| JDK | 17+ | Compile and run Java functions locally |
| Maven | 3.6+ | Build and package Java artifacts |
| Azure Functions Core Tools | 4.x | Publish function code |
| Azure CLI | 2.60+ | Provision resources |
What You'll Build¶
- A Linux Java Function App on Elastic Premium (
EP1) with runtime settings. - VNet integration and a site private endpoint for private inbound access.
- A first deployment pipeline (
func azure functionapp publish) and endpoint verification.
Network Scenario Choices
This tutorial deploys with VNet + Private Endpoints. For other network configurations:
| Scenario | Description | Guide |
|---|---|---|
| Public Only | No VNet, simplest setup | Public Only |
| Private Egress | VNet + Storage PE | Private Egress |
| Private Ingress | + Site Private Endpoint (this tutorial) | Current page |
| Fixed Outbound IP | + NAT Gateway | Fixed Outbound |
Infrastructure Context
Plan: Premium (EP1) | Network: VNet + Private Endpoints | Always warm: ✅
Premium deploys with VNet integration (delegated subnet), a private endpoint for inbound access, private DNS zone, and pre-warmed instances. Storage uses connection string or identity-based authentication.
flowchart TD
INET[Internet] -->|HTTPS| FA[Function App\nPremium EP1\nLinux Java 17]
subgraph VNET["VNet 10.20.0.0/16"]
subgraph INT_SUB["Integration Subnet 10.20.1.0/24\nDelegation: Microsoft.Web/serverFarms"]
FA
end
subgraph PE_SUB["Private Endpoint Subnet 10.20.2.0/24"]
PE_BLOB[PE: blob]
PE_QUEUE[PE: queue]
PE_TABLE[PE: table]
PE_FILE[PE: file]
end
end
PE_BLOB --> ST["Storage Account\nallowPublicAccess: false\nallowSharedKeyAccess: true"]
PE_QUEUE --> ST
PE_TABLE --> ST
PE_FILE --> ST
subgraph DNS[Private DNS Zones]
DNS_BLOB[privatelink.blob.core.windows.net]
DNS_QUEUE[privatelink.queue.core.windows.net]
DNS_TABLE[privatelink.table.core.windows.net]
DNS_FILE[privatelink.file.core.windows.net]
end
PE_BLOB -.-> DNS_BLOB
PE_QUEUE -.-> DNS_QUEUE
PE_TABLE -.-> DNS_TABLE
PE_FILE -.-> DNS_FILE
FA -.->|System-Assigned MI| ENTRA[Microsoft Entra ID]
FA --> AI[Application Insights]
subgraph STORAGE[Content Backend]
SHARE[Azure Files\ncontent share]
end
ST --- SHARE
WARM["🔥 Pre-warmed instances\nMin: 1, Max: 20-100"] -.- FA
style FA fill:#ff8c00,color:#fff
style VNET fill:#E8F5E9,stroke:#4CAF50
style ST fill:#FFF3E0
style DNS fill:#E3F2FD
style WARM fill:#FFF3E0,stroke:#FF9800 Steps¶
-
Authenticate and set subscription context.
Command/Parameter Purpose az loginAuthenticates your Azure CLI session. az account set --subscription "<subscription-id>"Sets the active subscription for the current session. --subscription "<subscription-id>"Specifies the target subscription ID. Expected output (abridged):
-
Create resource group and storage account.
export RG="rg-func-java-prem-demo" export STORAGE_NAME="stjprem$(openssl rand -hex 4)" export APP_NAME="func-jprem-$(openssl rand -hex 4)" export PLAN_NAME="plan-jprem-$(openssl rand -hex 4)" export LOCATION="koreacentral" az group create \ --name "$RG" \ --location "$LOCATION" az storage account create \ --name "$STORAGE_NAME" \ --resource-group "$RG" \ --location "$LOCATION" \ --sku "Standard_LRS" \ --kind "StorageV2" \ --allow-blob-public-access falseCommand/Parameter Purpose az group createProvisions a new resource group. --name "$RG"Sets the resource group name. --location "$LOCATION"Places the resource group in the target region. az storage account createCreates a storage account for function app use. --sku "Standard_LRS"Uses Standard Locally Redundant Storage. --kind "StorageV2"Selects General Purpose v2 storage account type. --allow-blob-public-access falseDisables public anonymous access to blobs. Expected output (abridged):
-
Create the Premium plan and Function App (Linux example).
az functionapp plan create \ --name "$PLAN_NAME" \ --resource-group "$RG" \ --location "$LOCATION" \ --sku "EP1" \ --is-linux az functionapp create \ --name "$APP_NAME" \ --resource-group "$RG" \ --plan "$PLAN_NAME" \ --storage-account "$STORAGE_NAME" \ --runtime "java" \ --runtime-version "17" \ --functions-version "4" \ --os-type "Linux"Command/Parameter Purpose az functionapp plan createProvisions an Elastic Premium hosting plan. --sku "EP1"Selects the initial Premium tier size. --is-linuxConfigures the plan for Linux hosting. az functionapp createProvisions the function app within the Premium plan. --plan "$PLAN_NAME"Links the app to the newly created plan. --runtime "java"Sets the language runtime to Java. --runtime-version "17"Specifies the Java version. --functions-version "4"Selects version 4.x of the runtime. --os-type "Linux"Deploys the app on a Linux host.
Globally unique names required
Both $APP_NAME and $STORAGE_NAME must be globally unique across all Azure subscriptions. If you get a naming conflict, append a random suffix (e.g., func-jprem-04091234).
Expected output (abridged):
Enterprise policy: Shared key access
Some enterprise subscriptions enforce Azure Policy that sets allowSharedKeyAccess: false on all storage accounts. Premium (EP1) requires WEBSITE_CONTENTAZUREFILECONNECTIONSTRING with a connection string that uses shared key access to create the content file share during provisioning. If your subscription has this policy, the Function App creation will fail with a 403 error. Solutions:
- Request a policy exemption from your Azure administrator
- Use Flex Consumption (FC1) which supports identity-based blob storage without shared keys
- Use Dedicated (B1) which uses
WEBSITE_RUN_FROM_PACKAGEwithout a content file share
-
Configure app settings using classic
siteConfig.appSettingsmodel values.az functionapp config appsettings set \ --name "$APP_NAME" \ --resource-group "$RG" \ --settings \ "FUNCTIONS_WORKER_RUNTIME=java" \ "AzureWebJobsStorage__accountName=$STORAGE_NAME" \ "AzureWebJobsStorage__credential=managedidentity"Command/Parameter Purpose az functionapp config appsettings setConfigures environment variables for the function app. "FUNCTIONS_WORKER_RUNTIME=java"Sets the runtime worker to Java. "AzureWebJobsStorage__accountName=$STORAGE_NAME"Sets the storage account name for host storage. "AzureWebJobsStorage__credential=managedidentity"Configures the host to use managed identity for storage access. Expected output (abridged):
[ { "name": "FUNCTIONS_WORKER_RUNTIME", "value": "java" }, { "name": "AzureWebJobsStorage__accountName", "value": "<storage-account-name>" }, { "name": "AzureWebJobsStorage__credential", "value": "managedidentity" } ]For Premium, both host-storage models are valid: - Connection string:
AzureWebJobsStorage=<connection-string>- Identity-based:AzureWebJobsStorage__accountName=<storage-account-name>plusAzureWebJobsStorage__credential=managedidentity -
Enable a system-assigned managed identity for the Function App.
az functionapp identity assign \ --name "$APP_NAME" \ --resource-group "$RG" export MI_PRINCIPAL_ID=$(az functionapp identity show \ --name "$APP_NAME" \ --resource-group "$RG" \ --query "principalId" \ --output tsv)Command/Parameter Purpose az functionapp identity assignEnables system-assigned managed identity for the app. az functionapp identity showRetrieves properties of the app's identity. --query "principalId"Extracts the principal ID for RBAC role assignments. --output tsvFormats output for variable capture. Expected output (abridged):
-
Assign storage RBAC roles to the managed identity.
export STORAGE_ID=$(az storage account show \ --name "$STORAGE_NAME" \ --resource-group "$RG" \ --query "id" \ --output tsv) az role assignment create \ --assignee "$MI_PRINCIPAL_ID" \ --role "Storage Blob Data Owner" \ --scope "$STORAGE_ID" az role assignment create \ --assignee "$MI_PRINCIPAL_ID" \ --role "Storage Account Contributor" \ --scope "$STORAGE_ID" az role assignment create \ --assignee "$MI_PRINCIPAL_ID" \ --role "Storage Queue Data Contributor" \ --scope "$STORAGE_ID" az role assignment create \ --assignee "$MI_PRINCIPAL_ID" \ --role "Storage File Data Privileged Contributor" \ --scope "$STORAGE_ID"Command/Parameter Purpose az storage account showRetrieves the storage account resource ID. az role assignment createGrants a specific RBAC role to the managed identity. --assignee "$MI_PRINCIPAL_ID"Targets the function app's system-assigned identity. --role "Storage Blob Data Owner"Grants full blob access for host data. --role "Storage Account Contributor"Allows platform-level management of the storage account. --role "Storage Queue Data Contributor"Grants access to storage queues for runtime operations. --role "Storage File Data Privileged Contributor"Grants access to file shares for Premium content storage. Expected output (abridged):
/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>{ "principalId": "<object-id>", "roleDefinitionName": "Storage Blob Data Owner", "scope": "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>" }{ "principalId": "<object-id>", "roleDefinitionName": "Storage Account Contributor", "scope": "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>" }{ "principalId": "<object-id>", "roleDefinitionName": "Storage Queue Data Contributor", "scope": "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>" }{ "principalId": "<object-id>", "roleDefinitionName": "Storage File Data Privileged Contributor", "scope": "/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name>" }Why these four roles are required on Premium
Premium Function Apps need host storage and Azure Files content share access during provisioning and runtime:
Storage Blob Data Ownerfor host blobs, leases, and trigger state.Storage Account Contributorfor storage account-level management operations used by the platform.Storage Queue Data Contributorfor queue-backed host coordination and trigger operations.Storage File Data Privileged Contributorfor the Azure Files content share (WEBSITE_CONTENTAZUREFILECONNECTIONSTRING) used by Premium.
-
Create a VNet with separate subnets for integration and private endpoints.
az network vnet create \ --name "vnet-premium-demo" \ --resource-group "$RG" \ --location "$LOCATION" \ --address-prefixes "10.20.0.0/16" \ --subnet-name "snet-integration" \ --subnet-prefixes "10.20.1.0/24" az network vnet subnet create \ --name "snet-private-endpoints" \ --resource-group "$RG" \ --vnet-name "vnet-premium-demo" \ --address-prefixes "10.20.2.0/24" az network vnet subnet update \ --name "snet-integration" \ --resource-group "$RG" \ --vnet-name "vnet-premium-demo" \ --delegations "Microsoft.Web/serverFarms" az functionapp vnet-integration add \ --name "$APP_NAME" \ --resource-group "$RG" \ --vnet "vnet-premium-demo" \ --subnet "snet-integration" for SVC in blob queue table file; do az network private-endpoint create \ --name "pe-st-$SVC" \ --resource-group "$RG" \ --location "$LOCATION" \ --vnet-name "vnet-premium-demo" \ --subnet "snet-private-endpoints" \ --private-connection-resource-id "$STORAGE_ID" \ --group-ids "$SVC" \ --connection-name "conn-st-$SVC" doneCommand/Parameter Purpose az network vnet createProvisions a new Virtual Network. --address-prefixes "10.20.0.0/16"Defines the CIDR range for the VNet. az network vnet subnet createAdds a subnet for private endpoints. az network vnet subnet updateModifies the integration subnet. --delegations "Microsoft.Web/serverFarms"Delegating the subnet to Premium hosting plans. az functionapp vnet-integration addConfigures outbound traffic to flow through the VNet. az network private-endpoint createProvisions private endpoints for storage services. --group-ids "$SVC"Specifies the storage sub-resource (blob, queue, etc). Expected output (abridged):
-
Create a private endpoint for inbound private access.
APP_ID=$(az functionapp show \ --name "$APP_NAME" \ --resource-group "$RG" \ --query "id" \ --output tsv) az network private-endpoint create \ --name "pe-$APP_NAME" \ --resource-group "$RG" \ --location "$LOCATION" \ --vnet-name "vnet-premium-demo" \ --subnet "snet-private-endpoints" \ --private-connection-resource-id "$APP_ID" \ --group-ids "sites" \ --connection-name "conn-$APP_NAME"Command/Parameter Purpose az functionapp showRetrieves the function app resource ID. az network private-endpoint createProvisions an inbound private endpoint for the function app. --group-ids "sites"Targets the primary web app (site) resource. --connection-name "conn-$APP_NAME"Sets the private link connection name. Expected output (abridged):
/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Web/sites/<function-app-name>{ "name": "pe-<function-app-name>", "provisioningState": "Succeeded", "privateLinkServiceConnections": [ { "groupIds": [ "sites" ] } ] }Private endpoint name resolution requires private DNS configuration. At minimum, create and link
privatelink.azurewebsites.netto the VNet, then attach the zone to the private endpoint:az network private-dns zone create \ --resource-group "$RG" \ --name "privatelink.azurewebsites.net" az network private-dns link vnet create \ --resource-group "$RG" \ --zone-name "privatelink.azurewebsites.net" \ --name "link-vnet-premium-demo" \ --virtual-network "vnet-premium-demo" \ --registration-enabled false az network private-endpoint dns-zone-group create \ --resource-group "$RG" \ --endpoint-name "pe-$APP_NAME" \ --name "web-dns-zone-group" \ --private-dns-zone "privatelink.azurewebsites.net" \ --zone-name "web-config" for SVC in blob queue table file; do az network private-dns zone create \ --resource-group "$RG" \ --name "privatelink.$SVC.core.windows.net" az network private-dns link vnet create \ --resource-group "$RG" \ --zone-name "privatelink.$SVC.core.windows.net" \ --name "link-$SVC" \ --virtual-network "vnet-premium-demo" \ --registration-enabled false az network private-endpoint dns-zone-group create \ --resource-group "$RG" \ --endpoint-name "pe-st-$SVC" \ --name "$SVC-dns-zone-group" \ --private-dns-zone "privatelink.$SVC.core.windows.net" \ --zone-name "$SVC" doneCommand/Parameter Purpose az network private-dns zone createProvisions private DNS zones for name resolution. az network private-dns link vnet createLinks the DNS zone to the VNet for resolution. az network private-endpoint dns-zone-group createConnects the private endpoint to its DNS zone for automatic IP registration. Expected output (abridged):
{ "name": "privatelink.blob.core.windows.net", "provisioningState": "Succeeded" } { "name": "privatelink.queue.core.windows.net", "provisioningState": "Succeeded" } { "name": "privatelink.table.core.windows.net", "provisioningState": "Succeeded" } { "name": "privatelink.file.core.windows.net", "provisioningState": "Succeeded" } -
Build and publish function code (Premium supports file share-based deployment and SCM/Kudu).
cd apps/java mvn clean package cd target/azure-functions/azure-functions-java-guide func azure functionapp publish "$APP_NAME"Command/Parameter Purpose cd apps/javaNavigates to the Java project directory. mvn clean packageCompiles the Java source and packages it into the Maven staging directory. cd target/azure-functions/azure-functions-java-guideNavigates to the Maven staging directory containing deployment artifacts. func azure functionapp publish "$APP_NAME"Deploys the local project to the Azure Function App. Expected output (abridged):
-
Verify app status and endpoint.
az functionapp show \ --name "$APP_NAME" \ --resource-group "$RG" \ --output table curl --request GET "https://$APP_NAME.azurewebsites.net/api/health"Command/Parameter Purpose az functionapp showDisplays function app status. --output tableFormats output as a table. curl --request GETTests the public HTTP endpoint. Expected output (abridged):
Auto-created Application Insights
az functionapp create automatically creates an Application Insights resource and links it via APPINSIGHTS_INSTRUMENTATIONKEY and APPLICATIONINSIGHTS_CONNECTION_STRING. Tutorial 04 creates a second, explicit Application Insights resource. You may see duplicate resources — this is expected.
Verification¶
Expected output when policy allows shared key access¶
{
"id": "/subscriptions/<subscription-id>/resourceGroups/rg-func-java-prem-demo/providers/Microsoft.Web/sites/<app-name>",
"location": "koreacentral",
"name": "<app-name>",
"state": "Running",
"defaultHostName": "<app-name>.azurewebsites.net"
}
[INFO] BUILD SUCCESS
...
Getting site publishing info...
Creating archive for current directory...
Uploading 326.47 KB [########################################]
Upload completed successfully.
Deployment completed successfully.
Syncing triggers...
Functions in <app-name>:
health - [httpTrigger]
info - [httpTrigger]
Deployment Verification Results¶
Blocked by enterprise policy
In our Korea Central deployment, EP1 Premium was blocked during provisioning by the same allowSharedKeyAccess: false policy as Consumption. Premium plans also require WEBSITE_CONTENTAZUREFILECONNECTIONSTRING with shared key access for the content file share.
Observed error:
ERROR: Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'.
Please check if the storage account is accessible.
Workarounds:
- Request a policy exemption from your Azure administrator
- Use Flex Consumption (FC1) which supports identity-based blob storage
- Use Dedicated (B1) which uses
WEBSITE_RUN_FROM_PACKAGEwithout a content file share
flowchart LR
A[Internet] --> B[Private Endpoint]
B --> C[Function App Premium EP1]
C --> D[VNet Integration Subnet]
D --> E[Private Backend]
C --> F[Pre-warmed Instances] Next Steps¶
Next: 03 - Configuration
See Also¶
- Tutorial Overview & Plan Chooser
- Java Language Guide
- Platform: Hosting Plans
- Operations: Deployment
- Recipes Index