Running Python Azure Functions Locally on an M1/M2 Mac

Michael Sharpe
5 min readMay 24

--

Python-based Azure functions are a powerful tool for executing pieces of code or ‘functions’ in response to specific events. However, for developers who are using the latest M1 and M2 Mac machines, running Azure functions locally has posed a challenge due to the shift from Intel to ARM architectures. The latest Azure Function Core Tools still do not support the ARM architecture (Azure Function Core Tools v4).

It is not too easy to find a solution by googling and even Azure forums do not help much. Three solutions have been identified,

  1. Use a virtual machine environment that has the capability of emulating an x86/amd64 machine and running an x86/amd64 VM. This is not ideal as currently, Parallels does not support x86/amd64 emulation. This leaves users with less popular options on MacOS like QEMU.
  2. Install Homebrew twice. It is possible to run the MacOS terminal and some other terminals under Rosetta 2 emulation. This allows an x86/amd64 version of Homebrew to be installed. Then homebrew can be used to install an x86/amd64 version of the Azure Function Core Tools. Again, not the cleanest solution, especially if Homebrew is used for ARM also. Fortunately, x86/amd64 and ARM Homebrew installs are in different places. Some specific settings will be required to pick up the right version of the tools for development. A very good write-up on this method can be found at http://issamben.com/running-python-azure-function-locally-on-an-m1-m2/.
  3. Use Docker with Rosetta 2 emulation. This appears to be the cleanest solution. This is especially true if docker is already familiar. The rest of this article focuses on this approach.

Install and Configure Docker Desktop

Docker Desktop for MacOS can be obtained from www.docker.com and installed normally using guidance on the same site. Once installed a couple of settings need to be applied. These settings are done in the Docker Desktop UI.

Go to the docker settings and check the “Use Virtualization framework” option on the “General Tab”

Virtualization framework Setting

Then select the “Features in development” tab and select the “Use Rosetta for x86/amd64 emulation on Apple Silicon” option.

Rosetta Setting

Docker is now ready to be able to run x86/amd64 images.

Create a Dockerfile

Create a directory for the Dockerfile and some other files. To run the func command, a Python image is required. The Azure Function Core Tools v4 supports Python 3.7, 3.8, 3.9, and 3.10. This example will base the image on the standardpython:3.10 image available on Docker Hub. The Dockerfile should look like this,

FROM python:3.10

COPY pip.conf /etc/pip.conf
ADD start.sh /
RUN chmod +x /start.sh

RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | \
gpg --dearmor > microsoft.asc.gpg && \
mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ && \
wget -q https://packages.microsoft.com/config/debian/10/prod.list && \
mv prod.list /etc/apt/sources.list.d/microsoft-prod.list && \
chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg && \
chown root:root /etc/apt/sources.list.d/microsoft-prod.list && \
apt-get update && \
apt-get install azure-functions-core-tools-4

RUN pip install --upgrade pip

WORKDIR /function

CMD ["/start.sh"]

Starting with the standard python:3.10 image, first, a pip.conf file and a start.sh script are added. The pip.conf will likely depend on the development environment. For example, if artifacts are pulled through Artifactory for example, some pip config will be needed here.

The start.sh file will be explained shortly. The massive wget command is simply setting up the apt-get repository required to install the azure-function-core-tools-4 package from Microsoft.

After that pip is upgraded and the working directory is created. The working directory is where the code for the Azure function will reside. More on that later.

Finally, the command which runs when the container is created from the image is set to start.sh. Ultimately, the start.shscript will run func start.

The start.shfile should look like this,

#!/bin/bash

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
func start

This script is pretty straightforward and should look familiar to what existing documentation suggests when running Azure functions locally. Basically, a venv is created and activated, the packages defined inrequirements.txt are installed and finally, the func start command is run. This command is run in the image, which will be run under Rosetta 2 emulation.

The start.sh script can be tweaked for the targeted project. For example, it's likely there will be local packages and/or whl files to be installed, the script could install these if they exist with minor tweaks. It's just a bash script after all.

Build the Image

To build the image, use the following command

docker build --platform linux/amd64 -f Dockerfile -t python310func .

This command will process the docker file and create a docker image with the Azure Function Core Tools v4 installed. Note the --platform argument. This is what tells docker to run under Rosetta 2 emulation

Run the Container

To create a container and actually run the func start command use the following command

cd /path/to/azure/function/project
docker run --platform linux/amd64 -v $(PWD):/function -p 7071:7071 \
--name azfuncproject python310func

There are some things to note here. First, the --platform argument is to ensure docker runs under emulation again. The -v will actually map the source code into the /function directory in the image. When it runs, it will create a .venv directory and persist it where it would if it were just running locally. This ensures the second execution runs quickly actually. The -p makes the host port to the container port so that the function can be accessed via http://localhost:7071 as normal also. Finally the --name argument gives it a name. This is useful as after the first execution, it's possible to start and stop the container in Docker Desktop directly without dealing with randomly generated names.

Conclusion

This approach can support multiple Python versions, and support multiple concurrent Azure Function Projects simply by tweaking images and naming images/containers appropriately. It's close to the natural command line workflow also. Instead of running func start, a docker run command is executed which in turn runs func start.

--

--

Michael Sharpe

Senior Software Architect located in Houston, Texas