Minimalistic Azure Function App creation

Azure
Azure Functions
Python
Windows
A minimalistic local-to-cloud Function App creation and deployment to Azure.
Published

May 29, 2026

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

%%{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"]

Setup local development environment

Install Azure Functions Core Tools

Follow the instructions Install the Azure Functions Core Tools.

Verify the installation:

func --version
4.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:

  1. Install Node.js from nodejs.org
  2. Install Azurite with npm
npm install -g azurite

Verify the installation:

azurite --version
3.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 V2
The 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 . /f
C:\...\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)

  1. Start a local Azurite storage emulator:

    azurite --silent --location azurite --debug azurite/debug.log
    Azurite 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
  2. Start the Function App with func start:

    func start
    Azure 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 python
The 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-RestMethod instead 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 --version
azure-cli                         2.86.0
...

Create required Azure components

This documentation provides a list of required Azure resources to create before deploying a Function App.

  1. Create an Azure account

  2. Use/Create a Subscription on the Azure portal manually — named subscription_demos here

  3. Log in to the Azure account and select the subscription with Azure CLI:

    az login
    Select 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)
  4. Create a Resource Group (logical container for related resources) — named func-app-demos-rg here

    • Select the best available region for the Function App (Flex Consumption) with the az functionapp command at functions-flex-supported-regions-clisoutheastasia in 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"
    }
  5. Create a Storage Account (used by the Functions host to maintain state and other information about your functions) — named httpazfuncappsa here.

    Register the Subscription to use Microsoft.Storage

    az provider register --namespace Microsoft.Storage

    Create 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
    }
  6. Create a user-assigned managed identity (used by the Functions host to connect to the default storage account) — named httpazfuncappsa-user here.

    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 $storageId
    WARNING: 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"
    }
  7. Create a Function App (provides the environment for executing the Function code) — named httpazfuncapp here.

    Register the Subscription to use Microsoft.Web

    az provider register --namespace Microsoft.Web

    Create 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=Demo
    Creating 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 create automatically 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 the APPLICATIONINSIGHTS_CONNECTION_STRING from 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=Demo were added on top of the defaults in the documentation for improved security, minimized resource usage, and better tracking of the Function App in Azure.
  8. Upgrade the Function App default storage account to user-assigned managed identity. Replace the default AzureWebJobsStorage connection string setting with several settings prefixed with AzureWebJobsStorage__.

    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 httpazfuncapp
Getting 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 -AutoSize
Name      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-keys
Functions 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.