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": ""
}
}
}
A 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.