Deploying a Django App to Azure App Service

Last updated February 28th, 2023

In this tutorial, we'll look at how to securely deploy a Django app to Azure App Service.

Contents

Objectives

By the end of this tutorial, you should be able to:

  1. Explain what Azure App Service is and how it works.
  2. Deploy a Django App to Azure App Service.
  3. Spin up a Postgres instance on Azure.
  4. Set up persistent storage with Azure Storage.
  5. Link a custom domain to your web app and serve it on HTTPS.

What is Azure App Service?

Azure App Service allows you to quickly and easily create enterprise-ready web and mobile apps for any platform or device and deploy them on scalable and reliable cloud infrastructure. It natively supports Python, .NET, .NET Core, Node.js, Java, PHP, and containers. They have built-in CI/CD, security features, and zero downtime deployments.

Azure App Service offers great scaling capabilities, allowing applications to scale up or down automatically based on traffic and usage patterns. Azure also guarantees a 99.95% SLA.

If you're a new customer you can get $200 free credit to test Azure.

Why App Service?

  • Great scaling capabilities
  • Integrates well with Visual Studio
  • Authentication via Azure Active Directory
  • Built-in SSL/TLS certificate
  • Monitoring and alerts

Project Setup

In this tutorial, we'll be deploying a simple image hosting application called django-images.

Check your understanding by deploying your own Django application as you follow along with the tutorial.

First, grab the code from the repository on GitHub.

$ git clone [email protected]:duplxey/django-images.git
$ cd django-images

Create a new virtual environment and activate it:

$ python3 -m venv venv && source venv/bin/activate

Install the requirements and migrate the database:

(venv)$ pip install -r requirements.txt
(venv)$ python manage.py migrate

Run the server:

(venv)$ python manage.py runserver

Open your favorite web browser and navigate to http://localhost:8000. Make sure everything works correctly by using the form on the right to upload an image. After you upload an image, you should see it displayed in the table:

django-images Application Preview

Configure Django Project

In this section of the tutorial, we'll configure the Django project to work with App Service.

Environment variables

We shouldn't store secrets in the source code, so let's utilize environment variables. The easiest way to do this is to use a third-party Python package called python-dotenv. Start by adding it to requirements.txt:

python-dotenv==1.0.0

Feel free to use a different package for handling environment variables like django-environ or python-decouple.

For Django to initialize the environment change, update the top of settings.py like so:

# core/settings.py

import os
from pathlib import Path

from dotenv import load_dotenv

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

load_dotenv(BASE_DIR / '.env')

Next, load SECRET_KEY, DEBUG, ALLOWED_HOSTS, and other settings from the environment:

# core/settings.py

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG', '0').lower() in ['true', 't', '1']

ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(' ')
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS').split(' ')

SECURE_SSL_REDIRECT = \
    os.getenv('SECURE_SSL_REDIRECT', '0').lower() in ['true', 't', '1']
if SECURE_SSL_REDIRECT:
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Database

To use Postgres instead of SQLite, we first need to install the database adapter.

Add the following line to requirements.txt:

psycopg2-binary==2.9.5

Later, when we spin up a Postgres instance, Azure will provide us with a database connection string. It will have the following format:

dbname=<db_name> host=<db_host> port=5432 user=<db_user> password=<db_password>

Since this string is pretty awkward to utilize with Django, we'll split it into the following env variables: DBNAME, DBHOST, DBUSER, DBPASS.

Navigate to core/settings.py and change DATABASES like so:

# core/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DBNAME'),
        'HOST': os.environ.get('DBHOST'),
        'USER': os.environ.get('DBUSER'),
        'PASSWORD': os.environ.get('DBPASS'),
        'OPTIONS': {'sslmode': 'require'},
    }
}

Azure App Service also requires sslmode so make sure to enable it.

Gunicorn

Moving along, let's install Gunicorn, a production-grade WSGI server that it's going to be used in production instead of Django's development server.

Add it to requirements.txt:

gunicorn==20.1.0

That's it for the Django configuration!

Deploy App

In this section of the tutorial, we'll deploy the web app to Azure App Service. Go ahead and sign up for an Azure account if you don't already have one.

Project Initialization

From the dashboard, use the search bar to search for "Web App + Database". You should see it within the "Marketplace" section:

Azure Web App + Database Search

Click it and you should get redirected to the app creation form.

Use the form to create the project, app, and database.

Project details

  1. Subscription: Leave as default
  2. Resource group: django-images-group
  3. Region: The region the closest to you

Web App Details

  1. Name: django-images
  2. Runtime stack: Python 3.11

Database

  1. Database engine: PostgreSQL - Flexible Server
  2. Server name: django-images-db
  3. Database name: django-images
  4. Hosting: Up to you

After you've filled out all the details, click on the "Review" button and then "Create".

It should take about five minutes to set up. Once done, navigate to the newly created resource by clicking on the "Go to resource" button.

Azure App Service Dashboard

Take note of the app URL since we'll need it to configure ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, and a few other Django settings.

App Configuration

For the app to work, we need to add all the environment variables we used in our Django project.

Navigate to your App Service app and select "Settings > Configuration" on the sidebar.

Azure App Service Configuration

You'll notice that an application setting named AZURE_POSTGRESQL_CONNECTIONSTRING has already been added. This is because we used "Web App + Database" to initialize the project.

The connection string contains all the information required to connect to the database. As mentioned before, let's split it into multiple variables and add them as separate application settings:

DBNAME=<db_name>
DBHOST=<db_host>
DBUSER=<db_user>
DBPASS=<db_pass>

Make sure to replace the placeholders with your actual credentials.

If your password ends with $ make sure to include it. That $ is part of the password, not a regex anchor.

Next, add the following additional variables:

DEBUG=1
SECRET_KEY=w7a8a@lj8nax7tem0caa2f2rjm2ahsascyf83sa5alyv68vea
ALLOWED_HOSTS=localhost 127.0.0.1 [::1] <your_app_url>
CSRF_TRUSTED_ORIGINS=https://<your_app_url>
SECURE_SSL_REDIRECT=0

Make sure to replace <your_app_url> with the app URL from the previous step.

Don't worry about DEBUG and other insecure settings. We'll change them later!

Once you've added all the application settings, click "Save" to update and restart your App Service app. Wait a minute or two for the restart to complete and then move to the next step.

Deploy Code

To deploy your code to Azure App Service, you'll first need to push it to GitHub, Bitbucket, Azure Repos or another git-based version control system. Go ahead and do that if you haven't already.

We'll be using GitHub in this tutorial.

Navigate to your App Service app and select "Deployment > Deployment Center" on the sidebar. Select the source you'd like to use and authenticate with your third-party account if you haven't already.

Azure Deployment Center

Next, fill out the form:

  1. Source: GitHub
  2. Organization: Your personal account or organization
  3. Repository: The repository you'd like to deploy
  4. Branch: The branch you'd like to deploy

Lastly, click "Save".

Azure will now set up a GitHub Action deployment pipeline, for deploying your application. From now on, every time you push your code to the selected branch, your app will automatically redeploy.

If you navigate to your GitHub repository, you'll notice a new folder named ".github/workflows". There will also be a GitHub action workflow running. After the workflow run is done, try visiting your app URL in your favorite web browser.

When you visit your newly deployed app for the first time, App Service can take a bit of time to "wake up". Give it a few minutes and try again.

If your app depends on the database migrations, you'll probably get an error since we haven't migrated the database yet. We'll do it in the next step.

SSH

App Service makes it easy for us to SSH into our server right from the browser.

To SSH, navigate to your App Service app and select "Development Tools > SSH" on the sidebar. Next, click on the "Go" button. Azure will open a new browser window with an active SSH connection to the server:

   _____
  /  _  \ __________ _________   ____
 /  /_\  \\___   /  |  \_  __ \_/ __ \
/    |    \/    /|  |  /|  | \/\  ___/
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/
A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
Python 3.9.16
Note: Any data outside '/home' is not persisted
(antenv) root@6066faafff94:/tmp/8db11d9b11cc42a#

Let's migrate the database and create a superuser:

(antenv)$ python manage.py migrate
(antenv)$ python manage.py createsuperuser

Nice! At this point your web application should be fully working.

Persistent Storage

Azure App Service offers an ephemeral filesystem. This means that your data isn't persistent and might vanish when your application shuts down or is redeployed. This is extremely bad if your app requires files to stick around.

To set up persistent storage for static and media files, we can use Azure Storage.

Navigate to your Azure dashboard and search for "Azure Storage". Then select "Storage accounts".

Azure Storage Index

Create Storage Account

To use Azure Storage, you first need to create a storage account. Click on "Create storage account", and create a new storage account with the following details:

  1. Subscription: Leave as default
  2. Resource group: django-images-group
  3. Storage account name: Pick a custom name
  4. Region: The same region as your app
  5. Performance: Up to you
  6. Redundancy: Leave as default

Leave everything else as default, review, and save. Take note of the storage account name, since we'll need it later in the tutorial.

Once the storage account is successfully created, navigate to it. Then select "Security + Networking > Access keys" in the sidebar and grab one of the keys.

Azure Storage Account Key

Create Containers

To better organize our storage we'll create two separate containers.

First, navigate back to the "Overview" and then click "Blob service".

Go ahead and create two containers, one named "static" and another one named "media". They should both have "Public access level" set to "Blob (anonymous read access for blobs only)".

Configure App

Next, navigate to your App Service app configuration and add the following two application settings:

AZURE_ACCOUNT_NAME=<your_storage_account_name>
AZURE_ACCOUNT_KEY=<your_storage_account_key>

Click "Save" and wait for your application to restart.

Configure Django

To utilize Azure Storage, we'll use a third-party package called django-storages.

Add the following lines to requirements.txt

django-storages==1.13.2
azure-core==1.26.3
azure-storage-blob==12.14.1

Next, go to core/settings.py and change static and media files settings like so:

# core/settings.py

DEFAULT_FILE_STORAGE = 'core.azure_storage.AzureMediaStorage'
STATICFILES_STORAGE = 'core.azure_storage.AzureStaticStorage'

AZURE_ACCOUNT_NAME = os.getenv('AZURE_ACCOUNT_NAME')
AZURE_ACCOUNT_KEY = os.getenv('AZURE_ACCOUNT_KEY')
AZURE_CUSTOM_DOMAIN = f'{AZURE_ACCOUNT_NAME}.blob.core.windows.net'

STATIC_URL = f'https://{AZURE_CUSTOM_DOMAIN}/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'

MEDIA_URL = f'https://{AZURE_CUSTOM_DOMAIN}/media/'
MEDIA_ROOT = BASE_DIR / 'mediafiles'

Since we're using two separate containers, we'll need to define our own AzureMediaStorage and AzureStaticStorage. Create a new file called azure_storage.py in the "core" directory (next to settings.py) with the following contents:

# core/azure_storage.py

import os
from storages.backends.azure_storage import AzureStorage


class AzureMediaStorage(AzureStorage):
    account_name = os.getenv('AZURE_ACCOUNT_NAME')
    account_key = os.getenv('AZURE_ACCOUNT_KEY')
    azure_container = 'media'
    expiration_secs = None


class AzureStaticStorage(AzureStorage):
    account_name = os.getenv('AZURE_ACCOUNT_NAME')
    account_key = os.getenv('AZURE_ACCOUNT_KEY')
    azure_container = 'static'
    expiration_secs = None

Commit your code and push it to the VCS.

After your app redeploys, SSH into the server and try to collect the static files:

(antenv)$ python manage.py collectstatic

You have requested to collect static files at the destination
location as specified in your settings.

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel: yes

141 static files copied.

To make sure the static and media files work, navigate to your app's /admin and check if CSS has been loaded. Next, try to upload an image.

Custom Domain

To link a custom domain to your app, first navigate to your app dashboard and then select "Settings > Custom Domains" on the sidebar. After that, click "Add custom domain".

Then, add a custom domain with the following details:

  1. Domain provider: All other domain services
  2. TLS/SSL Certificate: App Service Managed Certificate
  3. TLS/SSL type: SNI SSL
  4. Domain: Your domain (e.g., azure.testdriven.io)
  5. Hostname record type: CNAME

After you enter all the details, Azure will ask you to validate your domain ownership. To do that, you'll need to navigate to your domain registrar's DNS settings and add a new "CNAME Record", pointing to your app URL and a TXT record like so:

+----------+--------------+------------------------------------+-----------+
| Type     | Host         | Value                              | TTL       |
+----------+--------------+------------------------------------+-----------+
| CNAME    | <some host>  | <your_app_url>                     | Automatic |
+----------+--------------+------------------------------------+-----------+
| TXT      | asuid.azure  | <your_txt_value>                   | Automatic |
+----------+--------------+------------------------------------+-----------+

Example:

+----------+--------------+------------------------------------+-----------+
| Type     | Host         | Value                              | TTL       |
+----------+--------------+------------------------------------+-----------+
| CNAME    | azure        | django-images.azurewebsites.net    | Automatic |
+----------+--------------+------------------------------------+-----------+
| TXT      | asuid.azure  | BXVJAHNLY3JLDG11Y2H3B3C6KQASDFF    | Automatic |
+----------+--------------+------------------------------------+-----------+

Wait a few minutes for the DNS changes to propagate, and then click on "Validate". Once the validation has been completed, click "Add".

Azure will link a custom domain to the app and issue an SSL certificate. Your custom domain should be displayed in the table with the status "Secured".

If your domain's status is "No binding" or you get an error saying "Failed to create App Service Managed Certificate for ...", click "Add binding", leave everything as default, and then click "Validate". In case it fails, try again in a few minutes.

The last thing we need to do is to change ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS and enable SECURE_SSL_REDIRECT. Navigate to your App Service app configuration and change them like so:

ALLOWED_HOSTS=localhost 127.0.0.1 [::1] <your_custom_domain>
CSRF_TRUSTED_ORIGINS=https://<your_custom_domain>
SECURE_SSL_REDIRECT=1

Your web app should now be accessible at your custom domain on HTTPS.

Conclusion

In this tutorial, we've successfully deployed a Django app to Azure App Service. We've taken care of the Postgres database, configured static and media file serving, linked a custom domain name, and enabled HTTPS. You should now have a basic idea of how Azure works and be able to deploy your own Django apps.

Grab the final source code from the GitHub repo.

Future steps

  1. Look into Azure App Service Monitoring and Logging.
  2. Learn how to use Azure Command-Line Interface.

Nik Tomazic

Nik Tomazic

Nik is a software developer from Slovenia. He's interested in object-oriented programming and web development. He likes learning new things and accepting new challenges. When he's not coding, Nik's either swimming or watching movies.

Share this tutorial

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.