Deploying Python web apps to Azure App Services

I’ve spoken many times about Django. It’s probably my favorite back-end framework. The more I write web applications the more I appreciate the tooling available and the community.

I, of course, use Azure for hosting my web applications. And you might be wondering — “Hang on… Python on Azure?” Absolutely! You can spin up an instance of Azure App Services running Linux, and deploy your Python web application using Git.

Let’s take a look at how this is done!

Microsoft ❤ Linux

Prerequisites

In writing this article, I made a few assumptions about you, the reader.

As we’re going to see, there are a good number of steps we’ll need to complete. All told, this will probably take around 15 minutes to complete, so once you have your cup of coffee or tea we can get started.

Recipe

Let’s break down what we’re going to need to deploy our Python web application to Azure. At a high level, there’s three main categories of resouces we’ll need to create or configure — the Python app, the Azure resources, and finally the git repository.

  • Python web app

We’ll be using a Hello World app in Flask, but you could work through the same steps for a Django or Bottle application, or a more complex Flask application. I chose Flask for this post as Flask is a smaller framework.

  • Azure resources

There will be a few moving parts we’ll need in Azure. We’ll walk through each one, but to give you a peak as to what’s coming we’ll need a deployment account, Resource Group, an App Service Plan, and a Web App.

  • Git repository

We’ll be using a local git repository for this post as it allows you to quickly publish to Azure. You can integrate GitHub or another public repository host if you desire for CI/CD purposes if you like.

Python web app

Flask

App Service on Linux supports Python 2.7, 3.6 and 3.7, which should cover your application. You’re also free to use whatever web framework (Django, Flask, Bottle, etc.) you like, but the your application needs a two key files to operate correctly.

  • requirements.txt: During the deployment cycle, the daemon will automatically install all packages listed in requirements.txt. As such, any resources your application needs, including your framework (Django, Flask, etc.), should be listed in your requirements.txt file
  • application.py: This will be the entry point to your application. Regardless of the framework your using, name the startup file application.py.

Hello World Flask application

For my purposes, I’m going to use a simple hello world application in Flask, taken straight from the Flask docs.

For starters, create an empty folder if you’ll be following along. We’ll create the two files mentioned above (application.py and requirements.txt) in this folder. You’re free to use whatever editor you like, but I of course like VS Code.

As mentioned above, we’ll need an application.py file, which should contain the code listed below:

# application.pyfrom flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "<h1>Hello World!</h1>"

And we’ll need to create a file named requirements.txt where we’ll list our Python packages — just flask in our case:

# requirements.txt
flask

And that’s it. As I mentioned above, you can follow the same guidelines for Django, or a more complex Flask application. I’m putting forth this simple example to allow you to have an application you can play with for the post.

Azure Resources

There are four main items we’ll need in Azure:

  • Deployment account, which will be used to authenticate when we push our code to Azure
  • Resource group, which will allow us to group components (such as a database if we were using one) into a single management unit
  • App service plan, which will act as our server farm and control the operating system and execution resources
  • Web app, which will be used to configure our runtime provide the endpoint for our end users and for our deployment

Deployment account

Similar to pushing a repository to GitHub, you’ll need credentials to push our code to Azure, or what’s known as a deployment user. Your deployment user will be used when it’s time to push code from your local repository to Azure.

To create a deployment user you’ll need to use theAzure CLI (command line interface). You can choose to install the Azure CLI locally, or you can run it inside of the Azure Portal, which is the route we’ll use.

To launch the Azure CLI in the portal, login to portal.azure.com with your normal Azure credentials, and click Cloud Shell in the upper right-hand corner.

Cloud Shell icon in the upper right hand corner of the Azure Portal

When the shell opens at the bottom of the browser window, use the following command to create your user, where <username> is the name of the user you’d like to create:

az webapp deployment user set --user-name <username>

When you hit enter you’ll be prompted for a password. As always, choose a strong password for this account. Also, make sure you log it in your password manager as you’ll need to to perform the deployment later.

Don’t close the shell as we’ll be using it for the remainder of our setup steps in Azure.

Resource Group

As the name implies, a Resource Group is a group of resources. Resource groups allow you to put related components together for easier management. From a demo and learning perspective, I find resource groups are convenient for quickly cleaning up everything you created when you’re done playing.

Since we’re already using the Azure Cloud Shell from above, let’s stay where we are. I think the command line is best for creating web apps and all the components as it’s a convenient way to set all the various options we’ll need.

So using the same shell you opened above, you can execute the following command to create a resource group. az group create has two main parameters:

  • name: The name of the resource group you wish to create; needs to be unique to your subscription. I’ll be using blogpost, but you can of course update it with the name you wish.
  • location: Where the resource group will be hosted; I’ll be using westus2.

A quick note about the location - You can get a list of all locations available by using the command az account list-locations. You’ll also need to ensure the location supports App Service (Linux). For this post I’ll be using westus2.

az group create --location westus2 --name blogpost

Upon successful completion, the shell will display the JSON results which will resemble the one I’ve shared. (Yours will display the actual subscription ID, which I’ve deleted.)

{
"id": "/subscriptions/id-redacted/resourceGroups/blogpost",
"location": "westus2",
"managedBy": null,
"name": "blogpost",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null,
"type": null
}

App Service plan

Your code will need somewhere to run, and in Azure App Services we define the server space by using an App Service plan. Setting up an App Service plan is similar to setting up a server farm. You’ll define the size of the servers and features it’ll support (Pricing tier in Azure terms), the location (region in Azure terms), and operating system (Linux for hosting Python).

Sticking with the server farm analogy, it is possible to deploy multiple web apps to the same App Service plan. This means you can have multiple applications running in the same space, and you don’t have to pay a separate fee. They will share resources and, more importantly, the OS you chose when creating the App Service plan.

Note: As of the time of this article, App Services on Linux does not support the Free or Shared tiers; Basic is the lowest tier you’ll be able to use for hosting your Python web applications.

Billing note: We will be creating an App Service plan using the Basic SKU. This will incur a charge. If you’re just walking through the steps to see how deployment is done, make sure you delete the resource group when you’re done.

I personally like to create App Service plans via the Azure CLI as I think it’s a little quicker than going to the UI. The key parameters we need are:

  • name: The name of the app service plan. I’ll be sticking with blogpost
  • resource-group: The name of the resource group we want to host our plan, which will also determine the plan’s location. I’ll be using the blogpost group I created above.
  • sku: The pricing tier; must be at least Basic for hosting a Linux based app. You can see the list of available options in the Azure CLI documentation, and full details on pricing and functionality.
  • is-linux: Required for our deployment as we’re looking to use the Linux version of App Services for our Python application
az appservice plan create \
--name blogpost \
--resource-group blogpost \
--sku B1 \
--is-linux

The \ allows for entry of carriage returns, and prettier formatting in blog posts

This process will take a few moments as Azure spins up the necessary resources for our new environment. Upon completion you’ll receive the a JSON reply similar to the truncated one below:

# JSON reply after creating {
"adminSiteName": null,
"freeOfferExpirationTime": "2019-03-14T17:14:05.770000",
"geoRegion": "West US 2",
"hostingEnvironmentProfile": null,
"hyperV": false,
"id": "/subscriptions/idredacted/resourceGroups/blogpost/providers/Microsoft.Web/serverfarms/blogpost",
...snip
}

Azure Web app

The last moving part you’ll need to create is an Azure Web App, which is where you’ll be deploying your site. Your web app will determine your site’s name, the deployment method, and the runtime.

Like before, you can use the UI, but there are several settings you’ll want to configure which would normally require some additional steps. You can streamline the creation by using the Azure CLI. The following options are needed to create a web app to host a Python web app:

  • name: The name of the web app. This will become part of the URL <yourname>.azurewebsites.net. As a result, the name needs to be globally unique. I’ll just be using a placeholder of <yourname> below, which you can update with the name you wish to use for your site.
  • plan: The name of the App Service plan you wish to use. I’ll be using blogpost, which we of course created earlier.
  • resource-group: The name of the resource group you wish to use. I’ll be using the blogpost one created earlier.
  • runtime: Sets the language for your site. In our case this will be Python|3.7.
  • deployment-local-git: Enables git deployment, which is how you’ll be pushing your site to Azure.
az webapp create \
--name <sitename>\
--plan blogpost \
--resource-group blogpost \
--runtime "Python|3.7" \
--deployment-local-git

You’ll again get a huge JSON reply, which I’m not going to paste below. The key is the first line before the JSON starts, which will contain your local git deployment URL. Make sure you log this! We’ll identify this as <yourpath> when we add Azure as a remote Git repository.

Local git is configured with url of 'https://<username>@<sitename>.scm.azurewebsites.net/<sitename>.git'
{
"availabilityState": "Normal",
"clientAffinityEnabled": true,
"clientCertEnabled": false,
"cloningInfo": null,
"containerSize": 0,
"dailyMemoryTimeQuota": 0,
"defaultHostName": "<sitename>.azurewebsites.net",
"deploymentLocalGitUrl": "https://<username>@<sitename>.scm.azurewebsites.net/<sitename>.git",
"enabled": true,
... SNIPPED
}

Git repository and deployment

Git

We have everything created in Azure. Now it’s time to return our focus to our local code base, get our git repository set up, and then perform our deployment.

Local git repository

It is worth highlight it’s possible to deploy via GitHub. But for dev (and blog) purposes I find it easier to directly from a local Git repository to Azure. It’s a faster deployment as we don’t need to wait for the web hook to fire, and for Azure to pull down the code. We simply execute a push, and the deployment executes at that moment.

To get the repository set up, open whatever command shell you like and change to the directory you created at the start of the post. Run the Git commands to init the repository, add all the two files (application.py and requirements.txt), and then commit.

## From the command line
git init
git add .
git commit -m "Initial commit"

For our simple application from above we won’t need a gitignore file, but if you need to create one you can find a great templating tool at gitignore.io.

Deploy the site

You’ve made it! Time to deploy your application. Your Azure Web App will act as a remote repository. You can push your code to Azure now in the same way you’d push to GitHub or another host.

Using Git, we’ll add the remote path, and then push to Azure. You will need to use <yourpath> which you copied from the deployment URL. When you push you will be prompted for the password you created when setting up the deployment account.

## From the command line
git remote add azure <yourpath>

The deployment will take a few moments, and may sit there without providing any feedback for just long enough to make you think it’s not working. Don’t worry, it’s working.

Browse to the site!

Well, wait for just a moment first. Azure will need to do a few things for you behind the scenes before your site is ready. Count to 20, and then open a browser and navigate to https://<yoursitename>.azurewebsites.net.

The deployed website

Congratulations!! You’ve successfully deployed your website!

Cleanup

If you were just playing around and now want to clean everything up, you can delete the resource group in the Azure Cloud Shell by using the az group delete command.

Only do this if you’re looking to delete everything you created in this post!

az group delete --name blogpost

Conclusion

OK, so what did we do?

Well, at the highest level, we deployed a Python web application to Azure App Services by using Git.

At a lower level, we created a basic application, a hosted Linux environment for our code, and then set up Git so we could push our application on up to Azure.

Now it’s time to enhance your application! You can add functionality. Maybe incorporate an Azure SQL Database. Or integrate continuous deployment via GitHub. It’s really up to you!

Happy coding!

Geek @ Microsoft. Web, OSS, bots and developer boy. Husband. Marathoner. Yogi. Father of one four legged child. Opinions are anyone's but mine.