Use GitHub Actions to deploy a Python Flask web app to Azure App Service on Linux

Published on
Reading time
Authors

In this post I am going to walk through how to quickly get a Python Flask web application deployed into a Web App hosted on Azure's App Service on Linux using GitHub Actions.

Getting Azure Ready

Create an Azure Active Directory (AAD) Service Principal

A Service Principal is a special form of identity in Azure Active Directory that can be used to authorise application components to connect with one another. In the context of software deployments into Azure, a Service Principal is the primary way of authorising external services (like GitHub Actions) to interact with the various Azure service APIs.

The great thing about Service Principal registrations is we can really restrict their scope to the point where they can only interact with a specific Resource Group in Azure as shown in the sample command below.

az ad sp create-for-rbac \
  -n "laps-ghactions" \
  --role contributor \
  --scopes /subscriptions/AZURE_SUB_ID/resourceGroups/RESOURCE_GROUP

If you want to use the above command make sure to change the name ("laps-ghactions") and add your Subscription ID and Resource Group Name, then you can use Azure Cloud Shell to run the command.

Running the command will give you an output similar to the following. The important information you will need is 'appId', 'password' and 'tenant' which we will use later when configuring GitHub Actions.

{
  "appId": "ef037512-0000-0000-0000-000000000000",
  "displayName": "laps-ghactions",
  "name": "http://laps-ghactions",
  "password": "d9ebbc41-0000-0000-0000-000000000000",
  "tenant": "72f988bf-0000-0000-0000-000000000000"
}

Create an Azure App Service on Linux

The best way to run Python on App Service is to use the Linux Service Plans. The below bash script is the quickest way to achieve this.

https://gist.github.com/sjwaight/a105617a766717fda831df70373d92c0

If you want to use this script you can! Open up an Azure Cloud Shell session (and choose Bash) in the Subscription you want to deploy the Service Plan in and then use curl to download the file and then execute it.

curl https://gist.githubusercontent.com/sjwaight/a105617a766717fda831df70373d92c0/raw/8017774e30d6c5366b07b34932c8fa50c9c4a205/createlinuxappservice.sh -o createlinuxappservice.sh
chmod 755 createlinuxappservice.sh
./createlinuxappservice.sh YOUR_WEB_APP YOUR_RESOURCE_GROUP_NAME

OK, we're in a good place now with our Azure infrastructure so let's get going with our GitHub Actions setup.

GitHub Actions Ahoy!

At the time of writing, GitHub Actions are still in private preview so may not be available in your repositories, though you can request access to the beta program. If you do have (or obtain) access you should be aware that Actions tab and visual editor only show for users participating in the preview.

For the purpose of my demo I forked an existing repository containing a Python Flask web app to https://github.com/sjwaight/python-sample-vscode-flask-tutorial.

What you will notice if you open this repository is that it has an additional sub-folder called ".github" compared to the source. This is where Actions workflows are stored. The awesome thing about this is that your CI/CD logic lives right long with your solution and is source-controlled and versioned just the same way!

A copy of the workflow declaration is shown below.

https://gist.github.com/sjwaight/4bb9480939c1a9cd5803cd752b728f33

The workflow consists of multiple steps (actions) which can be any combination of existing actions, such as those from Microsoft for Azure, or you can custom-build your own. The Azure ones from Microsoft are on GitHub, including documentation.

The workflow block defines the trigger (on push) and includes a reference to the last action of the workflow. When creating Actions via the visual designer you don't see any of this but it is generated for you.

The Azure Login action has no dependencies (it has no "needs" property), but requires you to provide three secrets, along with one environment variable (Azure Subscription). Looking at the documentation we can see the environment variable is only required if your Azure AD tenant is associated with more than one Azure Subscription.

Our last step, Deploy to Web App, has a dependency on the Azure Login action and requires you to provide two environment variables - the name of the target Web Application to deploy to, along with the local GitHub reference (or zip file) you wish to deploy.

Keeping secrets out of your workflow

You will notice in the Azure Login step we have three "secrets" listed. Secrets are designed so developers avoid storing credentials or access keys in their workflows.

You set secrets up in the repository you want to deploy under 'Settings' in the tabs at the top of the screen - see the screenshot below.

GitHub Settings - Secrets

Once you have a secret set you cannot view the value - it will only be made available to the Action when it's executed.

While you are here you can setup the three required secrets for the Azure Login action - AZURE_SERVICE_APP_ID, AZURE_SERVICE_PASSWORD, AZURE_SERVICE_TENANT. Use the 'appId', 'password' and 'tenant' you captured earlier in the post for these values.

Run our workflow

As our workflow is triggered by a push we can edit a file (in my case just the README) and the workflow will run. If we switch to the Actions tab we can watch the workflow run and view the run history of the workflow. :cool:

Running GitHub Action Workflow

Checking out the Web App

Let's go an see our Flask web app in all it's glory!

Default Python Holding Page

Oh.... This isn't what I was expecting (it's the default holding page for a new Azure Web App configured to support Python).

Let's figure out what went wrong...

If we take a look at the source Python Flask project readme we can see some discussion around Docker and Gunicorn with a container startup file of startup.txt.

This probably won't make a lot of sense on its own, but I'll share a little of how the Linux App Service offering works.

Under the covers, the Linux App Service is using Docker to surface the multiple runtimes and frameworks it supports, so even if you aren't deploying a containerised application (which in this example we are not) you can still utilise Docker features such as specifying a startup file.

Now we know this how do we fix our problem?

If you return to the Azure portal and navigate to your Web App instance you will find a feature called "Application settings" which you should open.

Towards the top of the settings blade you will find a field called "Startup File". It will be blank when you open the page and you should update the field by putting "startup.txt" in it and then click the Save button.

Application settings

Let's return to our public website and refresh the browser and see what we get.

Successful Python Web App

Yessssssss!

Hopefully this post has given you a good overview of using GitHub Actions to deploy existing Python Flask web applications onto Azure App Service on Linux. If you have any questions, or you'd like to see me do this same web application, but using Docker instead, please leave a comment below.

Happy days! 😎