%%{init: {"flowchart": {"htmlLabels": false, "padding": 12, "nodeSpacing": 30, "rankSpacing": 38}, "themeVariables": {"fontSize": "13px"}}}%%
flowchart TD
A["Setup local development environment"] --> B["Create 1 HTTP triggered Function"]
B --> C["Create Function App in Azure"]
C --> D["Deploy Function App to Azure"]
D --> E["Run Function from local machine"]
Minimalistic Azure Function App creation
I recently passed the Azure Fabric Data Engineer certification to get my hands dirty with top-shelf products from a major cloud provider. Fabric is a powerful, feature-rich platform, but I feel it carries too steep a cost overhead for small and medium-sized projects. I took the opportunity to explore a less costly alternative for data engineering tasks: serverless computing — specifically Function-as-a-Service (FaaS).
There are countless articles about Azure Functions and an extensive official Microsoft documentation. To avoid documentation overload, I relied only on Microsoft’s documentation and ad-hoc free AI search from Perplexity. The goal is not to reinvent the wheel but to shrink the learning curve and show how to navigate the issues that arise during setup — there are a few. This post walks through a minimal local-to-cloud Function App creation and deployment workflow to base additional features on. It should be used as a starting point for more realistic and complex use cases.
Reference documentation
There’s plenty of official documentation to work from as a starting point on Microsoft’s Azure Functions documentation. More specifically, I heavily rely on the Quickstart: Create a function in Azure from the command line.
Minimalistic workflow to run a Function on Azure
Setup local development environment
Install Azure Functions Core Tools
Follow the instructions Install the Azure Functions Core Tools.
Verify the installation:
func --version4.8.0
Install Azurite storage emulator
A Function App needs a default system storage to work. Running Azurite locally allows simulating a storage account for the Function App. Detailed Azurite installation instructions can be found in the Azure Blob storage documentation.
Installation steps for Windows:
- Install Node.js from nodejs.org
- Install Azurite with npm
npm install -g azuriteVerify the installation:
azurite --version3.35.0
Create 1 HTTP triggered function
A new Function App from a template
Using Azure Functions Core Tools CLI, create a new Function App from a template in a directory of your choice.
mkdir http_az_function_app
cd http_az_function_app
func init demo-function --worker-runtime python --model V2The new Python programming model is generally available. Learn more at https://aka.ms/pythonprogrammingmodel Writing requirements.txt Writing function_app.py Writing .gitignore Writing host.json Writing local.settings.json Writing C:\...\http_az_function_app\demo-function\.vscode\extensions.json
The project directory is now structured as follows:
tree . /fC:\...\HTTP_AZ_FUNCTION_APP
└───demo-function
│ .gitignore
│ function_app.py
│ host.json
│ local.settings.json
│ requirements.txt
│
└───.vscode
extensions.json
Create a Python virtual environment
To test the Function locally in a reproducible environment, create a Python virtual environment inside the Azure Function App project. With pyenv, the latest listed stable Python version is 3.14.3. I used 3.13.12 (the latest v3.13 release) because it is the latest Python version supported by Azure Functions at the time of writing.
For a robust Python environment setup, I use this PowerShell script.
Working directory: demo-function
C:\...\tools\create-python-venv.ps1:: [Info] :: Mirror: https://www.python.org/ftp/python Requirement already satisfied: pip in c:\...\http_az_function_app\demo-function\.venv\lib\site-packages (25.3) Collecting pip ... Successfully installed packaging-26.2 pip-26.1.1 setuptools-82.0.1 wheel-0.47.0 Collecting azure-functions (from -r requirements.txt (line 5)) ... Installing collected packages: markupsafe, werkzeug, azure-functions Successfully installed azure-functions-2.1.0 markupsafe-3.0.3 werkzeug-3.1.8 success creating .venv
Test the Function App
Testing the Function after activating the Python virtual environment.
Working directory: demo-function
Virtual environment: .venv (activated)
Start a local Azurite storage emulator:
azurite --silent --location azurite --debug azurite/debug.logAzurite Blob service is starting at http://127.0.0.1:10000 Azurite Blob service is successfully listening at http://127.0.0.1:10000 Azurite Queue service is starting at http://127.0.0.1:10001 Azurite Queue service is successfully listening at http://127.0.0.1:10001 Azurite Table service is starting at http://127.0.0.1:10002 Azurite Table service is successfully listening at http://127.0.0.1:10002
Start the Function App with
func start:func startAzure Functions Core Tools Core Tools Version: 4.8.0+ec58eb7110992ea02aa19e9c060e45ac8882cc02 (64-bit) Function Runtime Version: 4.1046.100.25610 [2026-05-25T08:47:53.299Z] No job functions found. Try making your job classes and methods public. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.). For detailed output, run func with --verbose flag. [2026-05-25T08:47:58.439Z] Host lock lease acquired by instance ID '00000000000000000000000030C52BF0'.
The Function App starts successfully. No functions are registered yet, so no job functions found is expected.
A new Function from a template
Let’s create a dummy HTTP triggered Function that takes an input name and returns a success message with it or a failure message otherwise.
Working directory: demo-function
Virtual environment: .venv (activated)
func new --template "Http Trigger" --name helloName --authlevel "function" --language pythonThe function "helloName" was created successfully from the "Http Trigger" template.
A Function is added to the function_app.py file as follows:
...
@app.route(route="helloName", auth_level=func.AuthLevel.FUNCTION)
def helloName(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
name = req.params.get('name')
if not name:
try:
req_body = req.get_json()
except ValueError:
pass
else:
name = req_body.get('name')
if name:
return func.HttpResponse(f"Hello, {name}. This HTTP triggered function executed successfully.")
else:
return func.HttpResponse(
"This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.",
status_code=200
)Test the Function
As in the previous local test:
Working directory: demo-function
Virtual environment: .venv (activated)
- Start Azurite with
azurite --silent --location azurite --debug azurite/debug.log - Start the Function App. The newly added Function is now listed:
func start...
[2026-05-26T01:49:55.832Z] Worker process started and initialized.
Functions:
helloName: http://localhost:7071/api/helloName
...
- Call the HTTP triggered Function (in PowerShell, use
Invoke-RestMethodinstead of curl):
Invoke-RestMethod -Uri "http://localhost:7071/api/helloName" -Method Post -ContentType "application/json" -Body '{"name":"Johny Doe"}'Hello, Johny Doe. This HTTP triggered function executed successfully.
The Function works as expected locally. Let’s now create a Function App in Azure to deploy it.
Create Function App in Azure
Install Azure CLI
To interact with your Azure account from the command line, install Azure CLI from Azure CLI.
Running on PowerShell 7.6.1:
az --versionazure-cli 2.86.0 ...
Create required Azure components
This documentation provides a list of required Azure resources to create before deploying a Function App.
Use/Create a Subscription on the Azure portal manually — named
subscription_demoshereLog in to the Azure account and select the subscription with Azure CLI:
az loginSelect the account you want to log in with. For more information on login with Azure CLI, see https://go.microsoft.com/fwlink/?linkid=2271136 Retrieving tenants and subscriptions for the selection... [Tenant and subscription selection] No Subscription name Subscription ID Tenant ----- ------------------- ------------------------------------ ----------------- [1] * xxxxxxxxxxxx_xxxxx xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Default Directory [2] subscription_demos xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Default Directory The default is marked with an *; the default tenant is 'Default Directory' and subscription is 'xxxxxxxxxxxx_xxxxx' (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). Select a subscription and tenant (Type a number or Enter for no changes): 2 Tenant: Default Directory Subscription: subscription_demos (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
Create a Resource Group (logical container for related resources) — named
func-app-demos-rghere- Select the best available region for the Function App (Flex Consumption) with the
az functionappcommand at functions-flex-supported-regions-cli —southeastasiain my case - Run the resource group creation command:
az group create --name "func-app-demos-rg" --location "southeastasia"{ "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/func-app-demos-rg", "location": "southeastasia", "managedBy": null, "name": "func-app-demos-rg", "properties": { "provisioningState": "Succeeded" }, "tags": null, "type": "Microsoft.Resources/resourceGroups" }- Select the best available region for the Function App (Flex Consumption) with the
Create a Storage Account (used by the Functions host to maintain state and other information about your functions) — named
httpazfuncappsahere.Register the Subscription to use Microsoft.Storage
az provider register --namespace Microsoft.StorageCreate the storage account
az storage account create --name "httpazfuncappsa" --location "southeastasia" --resource-group "func-app-demos-rg" --sku "Standard_LRS" --allow-blob-public-access false --allow-shared-key-access false{ "accessTier": "Hot", "accountMigrationInProgress": null, "allowBlobPublicAccess": false, "allowCrossTenantReplication": false, "allowSharedKeyAccess": false, ... "type": "Microsoft.Storage/storageAccounts", "zones": null }Create a user-assigned managed identity (used by the Functions host to connect to the default storage account) — named
httpazfuncappsa-userhere.The following commands create a “Storage Blob Data Owner” role assignment on the storage account (full access to Azure Storage blob containers and data) that the Function App will use to access storage.
$output = az identity create --name "httpazfuncappsa-user" --resource-group "func-app-demos-rg" --location "southeastasia" --query "{userId:id, principalId:principalId, clientId:clientId}" -o json $userId = ($output | ConvertFrom-Json).userId $principalId = ($output | ConvertFrom-Json).principalId $clientId = ($output | ConvertFrom-Json).clientId $storageId = az storage account show --resource-group "func-app-demos-rg" --name httpazfuncappsa --query 'id' -o tsv az role assignment create --assignee-object-id $principalId --assignee-principal-type ServicePrincipal --role "Storage Blob Data Owner" --scope $storageIdWARNING: Resource provider 'Microsoft.ManagedIdentity' used by this operation is not registered. We are registering for you. WARNING: Registration succeeded. { "condition": null, ... "scope": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/func-app-demos-rg/providers/Microsoft.Storage/storageAccounts/httpazfuncappsa", "type": "Microsoft.Authorization/roleAssignments", "updatedBy": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "updatedOn": "2026-05-26T07:39:31.845940+00:00" }Create a Function App (provides the environment for executing the Function code) — named
httpazfuncapphere.Register the Subscription to use Microsoft.Web
az provider register --namespace Microsoft.WebCreate the Function App
az functionapp create --resource-group "func-app-demos-rg" --name httpazfuncapp --flexconsumption-location "southeastasia" --runtime python --runtime-version "3.13" --storage-account httpazfuncappsa --deployment-storage-auth-type UserAssignedIdentity --deployment-storage-auth-value "httpazfuncappsa-user" --https-only true --instance-memory 512 --tags Environment=DemoCreating deployment storage account container 'app-package-httpazfuncapp-5265228' ... Resource provider 'Microsoft.OperationalInsights' used by this operation is not registered. We are registering for you. Registration succeeded. Resource provider 'microsoft.insights' used by this operation is not registered. We are registering for you. Registration succeeded. Application Insights "httpazfuncapp" was created for this Function App. You can visit https://portal.azure.com/#resource/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/func-app-demos-rg/providers/microsoft.insights/components/httpazfuncapp/overview to view your Application Insights component { "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/func-app-demos-rg/providers/Microsoft.Web/sites/httpazfuncapp", ... "resourceGroup": "func-app-demos-rg", "type": "Microsoft.Web/sites" }Notes on Application Insights:
- Running
az functionapp createautomatically creates an associated Azure Application Insights instance in the same resource group. “The instance incurs no costs until you activate it.” (documentation). You cannot disable its usage by removing theAPPLICATIONINSIGHTS_CONNECTION_STRINGfrom the Function App Environment Variables. If you want to reactivate it later, you can retrieve the key from the Application Insights instance in Azure. For more information on Application Insights integration with Azure Function App, see Enable Application Insights integration. A better alternative is to use the user-assigned managed identity already created in step 6 for the Function App to access the Application Insights instance (Documentation):
clientId=$(az identity show --name httpazfuncappsa-user --resource-group func-app-demos-rg --query 'clientId' -o tsv) az functionapp config appsettings set --name httpazfuncapp --resource-group "func-app-demos-rg" APPLICATIONINSIGHTS_AUTHENTICATION_STRING="ClientId=$clientId;Authorization=AAD"- The additional parameters
--https-only true --instance-memory 512 --tags Environment=Demowere added on top of the defaults in the documentation for improved security, minimized resource usage, and better tracking of the Function App in Azure.
- Running
Upgrade the Function App default storage account to user-assigned managed identity. Replace the default
AzureWebJobsStorageconnection string setting with several settings prefixed withAzureWebJobsStorage__.Create 3 new environment variables for the Function App:
$clientId = az identity show --name httpazfuncappsa-user --resource-group func-app-demos-rg --query 'clientId' -o tsv az functionapp config appsettings set --name httpazfuncapp --resource-group "func-app-demos-rg" --settings AzureWebJobsStorage__accountName=httpazfuncappsa AzureWebJobsStorage__credential=managedidentity AzureWebJobsStorage__clientId=$clientId[ { "name": "AzureWebJobsStorage", "slotSetting": false, "value": null }, { "name": "AzureWebJobsStorage__accountName", ... }, { "name": "AzureWebJobsStorage__credential", ... }, { "name": "AzureWebJobsStorage__clientId", "slotSetting": false, "value": null } ]Delete the old environment variable:
az functionapp config appsettings delete --name httpazfuncapp --resource-group "func-app-demos-rg" --setting-names AzureWebJobsStorage[ { "name": "AzureWebJobsStorage__accountName", "slotSetting": false, "value": null }, { "name": "AzureWebJobsStorage__credential", ... }, { "name": "AzureWebJobsStorage__clientId", "slotSetting": false, "value": null } ]
Create a .funcignore file
To prevent unnecessary files from being pushed to the Azure Function App, create a .funcignore file with the following contents:
__pycache__/
.venv/
.vscode/
azurite/
.git/
.gitignore
.python-version
local.settings.json
Deploy Function App to Azure
Deploy the local project to the Azure Function App using func azure functionapp publish <APP_NAME>.
Working directory: demo-function
Virtual environment: .venv (activated)
Publish Function App to Azure
func azure functionapp publish httpazfuncappGetting site publishing info...
[2026-05-26T10:52:42.148Z] Starting the function app deployment...
[2026-05-26T10:52:42.156Z] Creating archive for current directory...
Performing remote build for functions project.
Uploading 1,01 KB [###############################################################################]
Deployment in progress, please wait...
Starting deployment pipeline.
[Kudu-SourcePackageUriDownloadStep] Skipping download. Zip package is present at /tmp/zipdeploy/7984310a-1ec7-445a-85ab-47dcdde7bcd8.zip
[Kudu-ValidationStep] starting.
...
[Kudu-CleanUpStep] completed.
Finished deployment pipeline.
[Kudu-SyncTriggerStep] completed.
Checking the app health...Host status endpoint: https://httpazfuncapp.azurewebsites.net/admin/host/status
. done
Host status: {"id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","state":"Running","version":"4.1048.200.26180","versionDetails":"4.1048.200+dfc109ad0152e6166b1a6d7a95f64a7459da96b3","platformVersion":"","instanceId":"0--xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","computerName":"","processUptime":402011,"functionAppContentEditingState":"NotAllowed","extensionBundle":{"id":"Microsoft.Azure.Functions.ExtensionBundle","version":"4.35.0"}}
[2026-05-26T10:55:08.765Z] The deployment was successful!
Functions in httpazfuncapp:
helloName - [httpTrigger]
Invoke url: https://httpazfuncapp.azurewebsites.net/api/helloname
Check successfull Function App deployment
# Get functions from the Function App
$functions = az functionapp function list `
--resource-group "func-app-demos-rg" `
--name httpazfuncapp | ConvertFrom-Json
# Create summary table with correct property paths
$functions | Select-Object `
@{Name="Name";Expression={$_.config.name}},
@{Name="Type";Expression={$_.config.bindings | Where-Object {$_.type -eq "httpTrigger"} | Select-Object -ExpandProperty type}},
@{Name="Route";Expression={$_.config.bindings | Where-Object {$_.type -eq "httpTrigger"} | Select-Object -ExpandProperty route}},
@{Name="IsDisabled";Expression={$_.isDisabled}},
@{Name="InvokeUrlTemplate";Expression={$_.invokeUrlTemplate}} |
Format-Table -AutoSizeName Type Route IsDisabled InvokeUrlTemplate ---- ---- ----- ---------- ----------------- helloName httpTrigger helloName False https://httpazfuncapp.azurewebsites.net/api/helloname
Run Function from local machine
The documentation suggests testing the Function with the Function key. Note that using a hardcoded key in code is not best practice. A better alternative is to authenticate through az login without using a key.
Getting the Function key
az login
func azure functionapp list-functions httpazfuncapp --show-keysFunctions in httpazfuncapp:
helloName - [httpTrigger]
Invoke url: https://httpazfuncapp.azurewebsites.net/api/helloname?code=xxxxx
Run the Function with a key
Call the Function from the local machine using the Invoke URL:
Invoke-RestMethod -Uri "https://httpazfuncapp.azurewebsites.net/api/helloname?code=xxxx" -Method Post -ContentType "application/json" -Body '{"name":"Johny Doe"}'Hello, Johny Doe. This HTTP triggered function executed successfully.
Note that the Function call will fail if there is no connection to Azure:
az logout
Invoke-RestMethod -Uri "https://httpazfuncapp.azurewebsites.net/api/helloname?code=xxxx" -Method Post -ContentType "application/json" -Body '{"name":"Johny Doe"}'Invoke-RestMethod: Response status code does not indicate success: 401 (Unauthorized).
Conclusion
Getting a Python HTTP Function from my laptop to Azure required more moving parts than initially expected. Two configuration layers stack on top of each other:
- Local tooling: Azure CLI, Azure Functions Core Tools, Azurite, and a Python virtual environment
- Azure containers and resources: resource group, storage account, user-assigned managed identity, Function App, and Application Insights
A few minor friction points need to be accounted for during the configurations. az functionapp create wires up Application Insights immediately without being able to opt-out. Some provider namespaces need registration on the Azure Subscription, such as Microsoft.Storage to allow creating the Function App storage account. Some Function App parameters like the instance memory size deserve attention from the start.
While this post is only about implementing a minimalistic Azure Function App, it already allows setting up a working local development environment that unlocks more advanced Azure Functions developments.