Deploying Azure Functions as Docker containers unlocks a portable, consistent, and familiar development and deployment experience, allowing you to leverage your existing Docker expertise for serverless applications.
Let’s see this in action. Imagine you have a simple HTTP-triggered Azure Function that returns a "Hello, World!" message.
Here’s the function.json for a basic HTTP trigger:
{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
And the Python code (__init__.py):
import logging
import azure.functions as func
def main(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.",
status_code=200
)
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
)
Now, to containerize this, you’ll need a Dockerfile. The Azure Functions Core Tools provide a base image that simplifies this process.
Here’s a sample Dockerfile for a Python function:
FROM mcr.microsoft.com/azure-functions/python:3.9
COPY . /home/site/wwwroot
RUN chmod +x /home/site/wwwroot && \
cp /home/site/wwwroot/requirements.txt /requirements.txt && \
pip install --no-cache-dir -r /requirements.txt && \
rm /requirements.txt
This Dockerfile does the following:
FROM mcr.microsoft.com/azure-functions/python:3.9: It starts with an official Azure Functions Python runtime image. This image already has the Azure Functions host set up and ready to run your code.COPY . /home/site/wwwroot: It copies your entire project directory (containing__init__.py,function.json, andrequirements.txt) into the designated directory for function code within the container.RUN chmod +x /home/site/wwwroot && \ cp /home/site/wwwroot/requirements.txt /requirements.txt && \ pip install --no-cache-dir -r /requirements.txt && \ rm /requirements.txt: This block is crucial for Python functions. It makes the function app directory executable, copies therequirements.txtto a location wherepipcan easily access it, installs your Python dependencies, and then cleans up the copiedrequirements.txt.
To build and run this locally, you’d use Docker commands:
docker build -t my-azure-function .
docker run -p 8080:80 my-azure-function
This starts your function app inside a Docker container, accessible at http://localhost:8080. You can then test it with curl http://localhost:8080/api/myfunction?name=Docker (assuming your function is named myfunction).
The core problem this solves is ensuring that your serverless function runs in the exact same environment wherever it’s deployed, be it your local machine for development, a staging environment, or production. This eliminates the "it works on my machine" syndrome and provides a predictable execution context. The Azure Functions host, running inside the container, is responsible for discovering your functions, managing triggers, and invoking your code.
When you deploy this container to Azure, you can use various services like Azure Container Instances (ACI) or Azure Kubernetes Service (AKS). For a direct serverless deployment, Azure Functions supports custom container images. You upload your built image to a container registry (like Azure Container Registry) and then configure your Azure Function App to pull and run that image. This means you’re still using the Azure Functions consumption plan or premium plan, but instead of Azure provisioning a standard runtime for you, it pulls and runs your custom container.
The magic behind how Azure Functions discovers and runs your code within a custom container relies on the Azure Functions host (func.exe for .NET, or the Python host for Python). When the container starts, the Functions host looks for a host.json file and function directories within the /home/site/wwwroot path. It then uses the function.json files to understand how each function is triggered and what its inputs/outputs are, and executes the corresponding code. The base images provided by Microsoft are pre-configured to launch this host automatically.
A detail many overlook is how custom handler configurations work for languages that don’t have a direct Azure Functions runtime image, like Go or Rust. For these, you use the generic azure-functions-base image and define a function.json with scriptFile pointing to a custom executable or script, and a handler property specifying the entry point. The Functions host then invokes this handler directly, passing trigger event data and expecting a response in a specific JSON format. This allows you to run virtually any application as an Azure Function in a container.
Once your container is running in Azure Functions, the next step is often to integrate it with other Azure services for more complex workflows, such as using Azure Event Grid to trigger your function or sending data to Azure Cosmos DB.