Deploying a Python app on Azure
using Flask, Docker and Azure App Service
My previous article described how we built an AI function for creating a path of similar faces from some starting image towards a celebrity.
This follow-up article will describe the steps needed to bring your AI to a live website on the Azure cloud platform:
- Some Flask basics to use your Python function in a web application
- How to use production-suitable web server Gunicorn to run it
- How to create a Docker image for your application
- How to deploy in using Azure App Service
- Workflow for handling updates
Basic Flask app
Okay, first things first… So the result of the previous blog is that we have some Python scripts containing a function that does the work for creating a ‘picture path’:
We are now gonna build a basic Flask app that assumes the following structure:
- We also build a form to upload a new image
- We build a Flask app using a file
app.pythat links requests to either HTML pages or Python functions
This is what the Flask script (roughly) looks like:
You can run this using
python src/app.py and you get a perfectly fine working web page (well, only after some amount of debugging probably…). So why would we bother with this Gunicorn thing? Short answer, because the Flask webpage says this:
While lightweight and easy to use, Flask’s built-in server is not suitable for production as it doesn’t scale well and by default serves only one request at a time.
Then what’s Gunicorn? Well, from their webpage:
Gunicorn ‘Green Unicorn’ is a Python WSGI HTTP Server for UNIX. It’s a pre-fork worker model. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.
This basically means it is suitable for production… And no worries, it’s very easy to use. All we have to do is install it using
pip install gunicorn and then replace
python src/app.py by
gunicorn src.app:app to run the app (ok, you’ll probably want to include some extra parameters in that call).
Docker + Azure
Ok, so we’ve got a working web app now, great! Only thing left to do is deploy it… Within the Azure platform this works via App Services and specifically, since we’re deploying a web application, a Web App.
As you can see, we can choose between ‘Code’ (in our case with a Python runtime stack) or a Docker Image. I’ve found that replicating the Python environment with required packages/dependencies is easier using Docker than trying to install them via some CLI to the Web App’s VM. So let’s choose Docker here. If you do, in the next screen you need to configure which Docker image should be used and where Azure can get it from:
What’s all that stuff about? Let’s start with creating our own Docker image first and then come back to this.
Creating the Docker image
Our Docker workflow will be as follows:
- Create a Docker file describing what the image should look like
- Build the image from the file
- Publish the image to a registry (so it can be accessed by Azure)
Ok, suppose our project structure has (among others):
srcdir containing the source code and a file
requirements.txtdenoting the required Python packages
then create a
Dockerfile (no extension) in the project root with the following basic setup:
# base image
FROM python:3.6# set web server root as working dir
WORKDIR /home/site/wwwroot# install required packages
COPY requirements.txt .
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
# copy all files
COPY . .
# expose port 8000
# start flask app using Gunicorn
CMD gunicorn -w 4 -b :8000 src.app:app
In addition, it’s good to create a
.dockerignore file to specify (similar to a
.gitignore file) which files should not be copied to the Docker image. This can potentially greatly reduce the image’s size! Just fill in line like this:
Now, from a terminal browse to your project root and run
docker build -t myapp .
This will (try to) build an image based on the Dockerfile in the current directory (that’s what the dot refers to!) and name it ‘myapp’. The first time you run this it will take a long time (installing packages mostly). If you re-build it’ll be faster because it uses cache for unchanged parts.
After building you can run the image locally using:
docker run -p 8000:80 myapp
Remember: we made Gunicorn run the app on port 8000. The
-p 8000:80 forwards port 8000 on the container to port 80 (the default http port) on the host machine. So now we can access the app by visiting localhost in the browser.
Publishing the Docker image
Now we’re going to publish the image to a registry. Azure has a container registry, Docker Hub is Docker’s registry service and there are others. We’ll use Docker Hub in this example. First you need to have an account. Then use it for
Now let’s give our image an appropriate name using the
tag command. Adhere to the format of
docker tag myapp robertvs/myapp:latest
Now we publish it (i.e. upload to the registry) using the
docker push robertvs/myapp
Ok great! The Docker part is done, now back to Azure.
Now we know exactly what to fill in in the previous Azure form!
Now, if you remember we had a
docker run command to start the container and run the app. You can’t specify this command in Azure. It kind of does it by itself.
Q: But then how do we do the port mapping?
A: Under Settings / Configuration add an entry for name WEBSITES_PORT with value 8000. It will automatically map it to port 80.
That’s it! Now your web app is running and accessible via https://myuniqueappname.azurewebsites.net
Ok one more thing… Since your app is never finished after first publication and you’ll want to update it regularly: what is the workflow for this?
First, you need to enable continuous deployment by doing the following:
- In Azure’s page for your web app, go to ‘Container settings’, set ‘Continuous Deployment’ to ‘On’ and copy the webhook url underneath it.
- Go to Docker Hub’s repo page for your image, go to ‘Webhooks’, add a new one and paste Azure’s webhook url under, you guessed it, ‘Webhook URL’.
What this does is create an event listener such that
- whenever you push a new version of this image to Docker Hub
- Docker Hub sends a message to Azure
- Azure pulls the latest version
Once this is in place, after updating our application we can do:
docker build -t myapp . && \
docker tag myapp robertvs/myapp:latest && \
docker push robertvs/myapp:latest
and the live version will be updated automatically.