Deploying a FastAPI App as an Azure Function: A Step-by-Step Guide

By on 17 June 2024

In this article I will show you how to deploy a FastAPI app as a function in Azure. Prerequisites are that you have an Azure account and have the Azure CLI installed (see here).

Setup Azure

First you need to login to your Azure account:

$ az login

It should show your subscriptions and you select the one you want to use.

Create a resource group

Like this:

$ az group create --name myResourceGroup --location eastus

A resource group is a container that holds related resources for an Azure solution (of course use your own names throughout this guide).

It comes back with the details of the resource group you just created.

Create a storage account

Using this command:

$ az storage account create --name storage4pybites --location eastus --resource-group myResourceGroup --sku Standard_LRS

Note that for me, to get this working, I had to first make an MS Storage account, which I did in the Azure portal.

The storage account name can only contain lowercase letters and numbers. Also make sure it’s unique enough (e.g. mystorageaccount was already taken).

Upon success it comes back with the details of the storage account you just created.

Create a function app

Now, create a function app.

$ az functionapp create --resource-group myResourceGroup --consumption-plan-location eastus --runtime python --runtime-version 3.11 --functions-version 4 --name pybitesWebhookSample --storage-account storage4pybites --os-type Linux

Your Linux function app 'pybitesWebhookSample', that uses a consumption plan has been successfully created but is not active until content is published using Azure Portal or the Functions Core Tools.
Application Insights "pybitesWebhookSample" was created for this Function App. You can visit <URL> to view your Application Insights component
App settings have been redacted. Use `az webapp/logicapp/functionapp config appsettings list` to view.
{
    ...JSON response...
}

A bit more what these switches mean:

  • --consumption-plan-location is the location of the consumption plan.
  • --runtime is the runtime stack of the function app, here Python.
  • --runtime-version is the version of the runtime stack, here 3.11 (3.12 was not available at the time of writing).
  • --functions-version is the version of the Azure Functions runtime, here 4.
  • --name is the name of the function app (same here: make it something unique / descriptive).
  • --storage-account is the name of the storage account we created.
  • --os-type is the operating system of the function app, here Linux (Windows is the default but did not have the Python runtime, at least for me).

Create your FastAPI app

To demo this from scratch, let’s create a simple FastAPI webhook app (the associated repo is here).

$ mkdir fastapi_webhook
$ cd fastapi_webhook
$ mkdir webhook

First, create a requirements.txt file adding the fastapi and azure-functions dependencies. The latter is required to run FastAPI on Azure Functions.

Then create an __init__.py file inside the webhook folder with the FastAPI app code:

import azure.functions as func

from fastapi import FastAPI

app = FastAPI()


@app.post("/webhook")
async def webhook(payload: dict) -> dict:
    """
    Simple webhook that just returns the payload.
    Normally you would do something with the payload here.
    And add some security like secret key validation (HMAC for example).
    """
    return {"status": "ok", "payload": payload}


async def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    return await func.AsgiMiddleware(app).handle_async(req, context)

This took a bit of trial and error to get it working, because it was not evident that I had to use AsgiMiddleware (created by Anthony Shaw 😍) as this article highlighted.

Next, create a host.json file in the root of the project for the Azure Functions host:

{
  "version": "2.0",
  "extensions": {
    "http": {
      "routePrefix": ""
    }
  }
}

local.settings.json file for local development:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "python"
  }
}

And you’ll need a function.json file in the webhook directory that defines the configuration for the Azure Function:

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}

I also included a quick test script in the root of the project:

import sys
import requests

url = "http://localhost:7071/webhook" if len(sys.argv) < 2 else sys.argv[1]
data = {"key": "value"}

resp = requests.get(url)
print(resp)

resp = requests.post(url, json=data)
print(resp)
print(resp.text)

If you run this script, you should see a 404 for the GET request (not implemented) and a 200 for the POST request. For remote testing I can run the script with the live webhook URL (see below).

Run the function locally

To verify that the function works locally run it with:

$ func start

Then run the test script:

$ python test.py
<Response [404]>
<Response [200]>
{"status":"ok","payload":{"key":"value"}}

Awesome!

Deploy it to Azure

Time to deploy the function to Azure:

$ func azure functionapp publish pybitesWebhookSample

Getting site publishing info...
[2024-06-17T16:06:08.032Z] Starting the function app deployment...
Creating archive for current directory...
...
...
Remote build succeeded!
[2024-06-17T16:07:35.491Z] Syncing triggers...
Functions in pybitesWebhookSample:
    webhook - [httpTrigger]
        Invoke url: https://pybiteswebhooksample.azurewebsites.net/webhook

Let’s see if it also works on Azure:

$ python test.py https://pybiteswebhooksample.azurewebsites.net/webhook
<Response [404]>
<Response [200]>
{"status":"ok","payload":{"key":"value"}}

Cool, it works!

If something goes wrong remotely, you can check the logs:

$ az webapp log tail --name pybitesWebhookSample --resource-group myResourceGroup

And when you make changes, you can redeploy with:

$ func azure functionapp publish pybitesWebhookSample

That’s it, a FastAPI app running as an Azure Function! I hope this article helps you if you want to do the same. If you have any questions, feel free to reach out to me on X | Fosstodon | LinkedIn -> @bbelderbos.

Want a career as a Python Developer but not sure where to start?