02 - First Deploy (Dedicated)¶
In this tutorial you deploy the Function App to a Dedicated App Service Plan using Basic B1. Dedicated plans are always running (no scale-to-zero), support Linux and Windows, and use fixed monthly pricing regardless of executions.
Prerequisites¶
- Completed 01 - Run Locally
- Variables exported in your shell:
export RG="rg-func-dedicated-dev"
export APP_NAME="func-dedi-<unique-suffix>"
export PLAN_NAME="asp-dedi-b1-dev"
export STORAGE_NAME="stdedidev<unique>"
export LOCATION="koreacentral"
| Command/Parameter | Purpose |
|---|---|
export RG="rg-func-dedicated-dev" | Defines the resource group name for the deployment. |
export APP_NAME="func-dedi-<unique-suffix>" | Sets the unique name for the Azure Function App. |
export PLAN_NAME="asp-dedi-b1-dev" | Defines the App Service Plan name. |
export STORAGE_NAME="stdedidev<unique>" | Sets the storage account name. |
export LOCATION="koreacentral" | Specifies the Azure region for deployment. |
What You'll Build¶
You will provision a Basic (B1) Linux App Service Plan, create a Python Function App on that plan, deploy from apps/python, and validate live endpoints.
Network Scenario Choices
This tutorial deploys with public networking on B1. Basic supports App Service VNet integration and private endpoints, but this guide uses Standard (S1+) for private networking walkthroughs to match the production-oriented validation path.
| Scenario | Description | Guide |
|---|---|---|
| Public Only | No VNet (this tutorial, B1) | Current page |
| Private Egress | VNet + Storage PE (guide validates S1+) | Private Egress |
| Private Ingress | + Site Private Endpoint (guide validates S1+) | Private Ingress |
| Fixed Outbound IP | + NAT Gateway (guide validates S1+) | Fixed Outbound |
Infrastructure Context
Plan: Dedicated (B1) | Network: Public internet in this tutorial | VNet: Supported by platform, not configured here
The app runs on a fixed App Service Plan (always on, no scale-to-zero). Basic B1 supports App Service VNet integration and private endpoints, but this guide uses Standard (S1+) for private networking scenarios to provide scale headroom, deployment slots, and a production-oriented validation path.
flowchart TD
INET[Internet] -->|HTTPS| FA[Function App\nDedicated B1-P3v3\nLinux Python 3.11]
subgraph VNET["VNet 10.0.0.0/16"]
subgraph INT_SUB["Integration Subnet 10.0.1.0/24\nDelegation: Microsoft.Web/serverFarms"]
FA
end
subgraph PE_SUB["Private Endpoint Subnet 10.0.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"]
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]
RFP["📦 WEBSITE_RUN_FROM_PACKAGE=1\nNo content share required"] -.- FA
ALWAYS_ON["⚙️ Always On: true\nFixed capacity"] -.- FA
style FA fill:#5c2d91,color:#fff
style VNET fill:#E8F5E9,stroke:#4CAF50
style ST fill:#FFF3E0
style DNS fill:#E3F2FD Steps¶
Step 1 - Create resource group and storage account¶
az group create \
--name $RG \
--location $LOCATION
az storage account create \
--name $STORAGE_NAME \
--resource-group $RG \
--location $LOCATION \
--sku Standard_LRS \
--kind StorageV2
| Command/Parameter | Purpose |
|---|---|
az group create | Provisions a new resource group. |
--name $RG | Sets the resource group name. |
--location $LOCATION | Places the resource group in the selected region. |
az storage account create | Creates a new storage account for the function app. |
--sku Standard_LRS | Selects Standard Locally Redundant Storage. |
--kind StorageV2 | Specifies the storage account type as General Purpose v2. |
Step 2 - Create a Dedicated App Service Plan (B1)¶
az appservice plan create \
--name $PLAN_NAME \
--resource-group $RG \
--location $LOCATION \
--sku B1 \
--is-linux
| Command/Parameter | Purpose |
|---|---|
az appservice plan create | Provisions a Dedicated App Service plan. |
--sku B1 | Selects the Basic B1 pricing tier. |
--is-linux | Configures the plan for Linux hosting. |
For production workloads, use S1 or P1v2 when you need higher scale limits, autoscale rules, deployment slots, or the same private networking path validated by this guide.
Step 3 - Create the Function App on the plan¶
az functionapp create \
--name $APP_NAME \
--resource-group $RG \
--plan $PLAN_NAME \
--storage-account $STORAGE_NAME \
--runtime python \
--runtime-version 3.11 \
--functions-version 4 \
--os-type Linux
| Command/Parameter | Purpose |
|---|---|
az functionapp create | Provisions the function app within the Dedicated plan. |
--plan $PLAN_NAME | Links the app to the specific App Service plan. |
--runtime python | Sets the language runtime to Python. |
--runtime-version 3.11 | Specifies the Python version. |
--functions-version 4 | Selects version 4.x of the runtime. |
--os-type Linux | Deploys the app on a Linux host. |
Step 4 - Enable Always On (recommended)¶
| Command/Parameter | Purpose |
|---|---|
az functionapp config set | Modifies configuration settings for the function app. |
--always-on true | Keeps the function host loaded to prevent cold start delays. |
Step 5 - Deploy code¶
| Command/Parameter | Purpose |
|---|---|
cd apps/python | Navigates to the project directory. |
func azure functionapp publish $APP_NAME | Deploys the local project to Azure. |
--python | Sets the application language. |
Dedicated deployment here uses remote Oryx build via func azure functionapp publish --python with WEBSITE_RUN_FROM_PACKAGE=1. Unlike Consumption and Premium content share scenarios, B1 does not require WEBSITE_CONTENTAZUREFILECONNECTIONSTRING.
Set placeholder trigger settings before first request
The reference app includes EventHub, Queue, and Timer triggers that need connection settings. If these are missing, the function host may report errors. Set placeholder values immediately after first deploy:
az functionapp config appsettings set \
--name $APP_NAME \
--resource-group $RG \
--settings \
EventHubConnection="Endpoint=sb://placeholder.servicebus.windows.net/;SharedAccessKeyName=placeholder;SharedAccessKey=placeholder=;EntityPath=placeholder" \
QueueStorage="UseDevelopmentStorage=true" \
TIMER_LAB_SCHEDULE="0 0 0 1 1 *"
az functionapp restart --name $APP_NAME --resource-group $RG
| Command/Parameter | Purpose |
|---|---|
az functionapp config appsettings set | Configures key-value pairs in the environment. |
--settings "..." | Sets trigger-specific placeholder settings. |
az functionapp restart | Restarts the function app to apply configuration changes. |
After restart, wait 60–90 seconds for the host to become ready on B1 tier.
Step 6 - Verify deployment¶
az functionapp show \
--name $APP_NAME \
--resource-group $RG \
--query "{state:state,defaultHostName:defaultHostName,kind:kind}" \
--output json
curl --request GET "https://$APP_NAME.azurewebsites.net/api/health"
| Command/Parameter | Purpose |
|---|---|
az functionapp show | Retrieves current status of the app. |
--query "{...}" | Selects relevant status fields. |
curl --request GET | Tests the public HTTP health endpoint. |
flowchart TD
A[Internet] --> B[Function App\nLinux, Python 3.11]
B --> C[App Service Plan B1\nAlways Running]
B --> D[Azure Services\nStorage, Monitor, App Insights] B1 network support and guide scope
Basic (B1) supports App Service VNet integration and private endpoints, but this tutorial intentionally remains public-only. Use the optional section when you want to test private networking; use Standard (S1+) if you want to match the guide-tested production path.
Optional: VNet and Private Endpoints¶
Optional: VNet and Private Endpoints
Basic (B1) can use these platform networking features. This walkthrough upgrades to S1 to align with the guide-tested private networking path and provide more production headroom.
Step A: Upgrade Plan (recommended guide path)¶
| Command/Parameter | Purpose |
|---|---|
az appservice plan update | Modifies plan properties. |
--sku S1 | Upgrades the tier to Standard S1 for the guide-tested private networking path. |
Step B: Create VNet and Subnets¶
export VNET_NAME="vnet-dedicated-demo"
az network vnet create \
--name "$VNET_NAME" \
--resource-group "$RG" \
--location "$LOCATION" \
--address-prefixes "10.0.0.0/16" \
--subnet-name "snet-integration" \
--subnet-prefixes "10.0.1.0/24"
az network vnet subnet create \
--name "snet-private-endpoints" \
--resource-group "$RG" \
--vnet-name "$VNET_NAME" \
--address-prefixes "10.0.2.0/24"
az network vnet subnet update \
--name "snet-integration" \
--resource-group "$RG" \
--vnet-name "$VNET_NAME" \
--delegations "Microsoft.Web/serverFarms"
| Command/Parameter | Purpose |
|---|---|
az network vnet create | Provisions a new virtual network. |
az network vnet subnet create | Adds a subnet for private endpoints. |
az network vnet subnet update | Configures the integration subnet delegation. |
Step C: Enable VNet Integration¶
az functionapp vnet-integration add \
--name "$APP_NAME" \
--resource-group "$RG" \
--vnet "$VNET_NAME" \
--subnet "snet-integration"
| Command/Parameter | Purpose |
|---|---|
az functionapp vnet-integration add | Connects the app to the virtual network. |
Step D: Enable System-Assigned Managed Identity¶
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 assign | Enables managed identity for the app. |
az functionapp identity show | Retrieves the app's identity properties. |
Step E: Assign RBAC Roles¶
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"
| Command/Parameter | Purpose |
|---|---|
az role assignment create | Grants specified permissions to the managed identity. |
--role "..." | Selects a specific storage data or contributor role. |
RBAC roles explained
- Storage Blob Data Owner: Read/write blob data (used by the Functions runtime for triggers, bindings, and internal state)
- Storage Account Contributor: Manage storage account properties
- Storage Queue Data Contributor: Read/write queue messages (used by durable functions, queue triggers)
Step F: Lock Down Storage¶
az storage account update \
--name "$STORAGE_NAME" \
--resource-group "$RG" \
--allow-blob-public-access false
| Command/Parameter | Purpose |
|---|---|
az storage account update | Modifies storage properties. |
--allow-blob-public-access false | Disables public anonymous access. |
Step G: Create Storage Private Endpoints (×4)¶
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_NAME" \
--subnet "snet-private-endpoints" \
--private-connection-resource-id "$STORAGE_ID" \
--group-ids "$SVC" \
--connection-name "conn-st-$SVC"
done
| Command/Parameter | Purpose |
|---|---|
az network private-endpoint create | Provisions private endpoints for each storage sub-resource. |
Step H: Create Private DNS Zones and Link to VNet (×4)¶
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_NAME" \
--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"
done
| Command/Parameter | Purpose |
|---|---|
az network private-dns zone create | Provisions private DNS zones for storage services. |
az network private-dns link vnet create | Links DNS zones to the virtual network. |
az network private-endpoint dns-zone-group create | Configures automatic IP registration in DNS. |
Step I: Configure Identity-Based Storage¶
az functionapp config appsettings set \
--name "$APP_NAME" \
--resource-group "$RG" \
--settings \
"AzureWebJobsStorage__accountName=$STORAGE_NAME" \
"AzureWebJobsStorage__credential=managedidentity"
| Command/Parameter | Purpose |
|---|---|
az functionapp config appsettings set | Configures identity-based storage access. |
Step J: Verify VNet Integration¶
az functionapp show \
--name "$APP_NAME" \
--resource-group "$RG" \
--query "virtualNetworkSubnetId" \
--output tsv
| Command/Parameter | Purpose |
|---|---|
az functionapp show | Retrieves current VNet integration details. |
Verification¶
az appservice plan create ... --sku B1:
{
"id": "/subscriptions/<subscription-id>/resourceGroups/rg-func-dedicated-dev/providers/Microsoft.Web/serverfarms/asp-dedi-b1-dev",
"kind": "linux",
"location": "koreacentral",
"name": "asp-dedi-b1-dev",
"resourceGroup": "rg-func-dedicated-dev",
"sku": {
"name": "B1",
"tier": "Basic"
},
"status": "Ready"
}
az functionapp config set --always-on true:
curl --request GET "https://$APP_NAME.azurewebsites.net/api/health":
Deployment Verification Results¶
Endpoint test results from the Korea Central deployment (all returned HTTP 200):
GET /api/health→{"status": "healthy", "timestamp": "2026-04-04T05:38:46Z", "version": "1.0.0"}GET /api/info→{"name": "azure-functions-field-guide", "version": "1.0.0", "python": "3.11.13", "environment": "development", "telemetryMode": "basic"}GET /api/requests/log-levels→{"message": "Logged at all levels", "levels": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]}GET /api/dependencies/external→{"status": "success", "statusCode": 200, "responseTime": "1143ms", "url": "https://httpbin.org/get"}GET /api/exceptions/test-error→{"error": "Handled exception", "type": "ValueError", "message": "Simulated error for testing"}
Next Steps¶
Your first Dedicated deployment is live. Next you will configure app settings, storage options, and runtime behavior with siteConfig.appSettings conventions.
Next: 03 - Configuration
See Also¶
- Tutorial Overview & Plan Chooser
- Python Language Guide
- Platform: Hosting Plans
- Operations: Deployment
- Recipes Index