Contents

Azure WebApp deployment

Preparation

Before deployment, follow the checklist: https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

Whitenoise library for static files

While your app is in development, Django automatically serves any static files. In production, you need to configure a service to serve any static files. One common solution is a WhiteNoise library. (Compared to a static website, there is a dynamic and static part here, which is why you want to isolate the static in a separate service with other characteristics) WhiteNoise is middleware that reads user requests for static files such as CSS or JavaScript. It also ensures the files are served correctly. You registered the middleware by updating the MIDDLEWARE array. Static files need to be collected using ‘python manage.py collectstatic’. However, Azure runs this automatically on deploy.

Overall, two new libraries are needed:

  • whitenoise to serve static files
  • psycopg2 to connect to PostgreSQL, the production database (NOTE for production purposes do not use psycopg2-binary)

New settings file for azure

The values you assign to two core settings, ALLOWED_HOSTS and DATABASES, depend on the environment that hosts the application. The default settings are designed for development.

  1. create a new file in the project called azure.py for the production settings
  2. add imports:
    • import os
    • from .settings import * (so copy the previous settings, then overwrite in this file)
  3. Add the variables:
1
2
ALLOWED_HOSTS = [os.environ['WEBSITE_HOSTNAME']] if 'WEBSITE_HOSTNAME' in os.environ else []
CSRF_TRUSTED_ORIGINS = ['https://'+ os.environ['WEBSITE_HOSTNAME']] if 'WEBSITE_HOSTNAME' in os.environ else []

Note: Azure App Service automatically creates an environmental variable named WEBSITE_HOSTNAME. This variable contains the URL for your website. You can use this variable to determine whether your application is running on Azure.

  1. Configure PostgreSQL for use in Azure
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
hostname = os.environ['DBHOST']
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['DBNAME'],
        'HOST': hostname + ".postgres.database.azure.com",
        'USER': os.environ['DBUSER'],
        'PASSWORD': os.environ['DBPASS'] 
    }
}

Important note: The database connection is for PostgreSQL Flexible Server. For PostgreSQL Single Server, set the USER value to os.environ['DBUSER'] + '@' + hostname.

  1. Add the following code to the bottom of the file to enable whitenoise, which will serve static files.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # Enables whitenoise for serving static files
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')  # you registered a STATIC_ROOT to store static files.
  1. Set the SECRET_KEY with one read from environmental variables by adding the following code.
1
SECRET_KEY = os.getenv('SECRET_KEY')

You’ll create a new secret key after you deploy the application and store it as an application setting.

  1. Override the project settings if we are in production environment Add to end of settings.py:
1
2
3
import os
if 'WEBSITE_HOSTNAME' in os.environ: # Running on Azure
    from .azure import *

Side info

VS code integration

You can deploy to Azure in several ways. One of the most convenient ways is to use one of these extensions for Visual Studio Code:

  • Azure Databases
  • Azure App Service

Azure Databases allows you to create the database server and database. Azure App Service allows you to create, configure, and deploy to the web host.

Database considerations

Django is designed for data-driven web applications. So every Django project usually includes a database. During development, you typically use SQLite, which is a file-based database engine.

SQLite is a perfect solution for development because it requires no special installation or services. But the requirements for production typically include scaling, performance, and reliability. SQLite isn’t designed to manage these production requirements.

Django natively supports many databases, including MySQL, PostgreSQL, and MariaDB. You can also find providers to enable support for SQL Server, MongoDB, and many other databases.

When you created the production settings file, you configured the environment for PostgreSQL. PostgreSQL is one of the most popular databases for Django. It’s also supported by Azure.

DB migrations

Django manages the database schema through migrations. Django can generate the SQL to create, or it can update the schema. Or you can use the makemigrations command to make Django update the database directly.

To run migrations on the database, you can secure-shell (or SSH) into App Service. This method allows you to run commands on the web host the way you run them locally.

(Although developed with SQLite, azure runs PostgreSQL. The database is build using the migrations system / Django ORM! I suspect azure automatically runs these commands, so no extra work needed besides the proper db settings, but not sure given the above.)

General azure notes/tips

Previously encountered problems and errors

Some encountered problems:

  • Css for admin site didnt work. Error: Refused to apply style from ‘<URL>’ because its MIME type (’text/html’) is not a supported stylesheet MIME type, and strict MIME checking is enabled.

Solution: “whitenoise” library should already solve “MIME type” error then CSS is loaded successfully.

Actual deployment

(Microsoft Learn: Lengthy lesson, see https://learn.microsoft.com/en-us/training/modules/django-deployment/6-deploy-azure for full instructions!) Install the vscode azure (app service) extension, if not already. The manage.py file at the project root signals to App Service that you’re deploying a Django web app.

Now add a resource using the extension by clicking the +. Select ‘Create App Service Web App’. Through the same extension options menu (+), create a PostGresQL server, in the resource group where the Web App is located. (find the resource by rightclicking the webapp and then properties, find in the id field) Make sure the webapp and db azure location is the same. Also make sure the WebApp is deployed, and you have a correct requirements.txt while doing so! Otherwise you wont have the correct files or python packages.

Configure a db firewall by starting to typ ’ > PostgreSQL: Configure Firewall’ in the search bar.

Actually create the database on the database server resource: do so by rightclicking on the server.

Firewall settings

Then allow the firewall to give public access when the db is accessed through Azure apps. Now only locally it is accessible.

  • In VS Code, in the RESOURCES of the Azure Tools extension, expand the PostgreSQL Servers (Flexible) node and find the server you created.
  • Right-click the name of your database server and select Open in Portal.
  • Select the Networking resource of the Postgres Server.
  • Select Allow public access from any Azure service within Azure to this server option in the portal from the Networking tab and select Save.

Configure the application settings

The variables in the environment, as accessed by azure.py using e.g. os.environ[‘DBUSER’] need to be set. By doing this, we keep all secrets on the azure server, so very important step!!

To create the first application setting, right-click Application Settings and then select Add New Setting. Follow the instructions. Fill in the following fields (not necessarily those values):

  • DBHOST <The server name you created previously>
  • DBPASS <The password you created previously>
  • DBUSER <user/admin name used while creating the db>
  • DBNAME <the actual db name (not the server)>
  • SECRET_KEY <Generate a secure password>

Create db schema and superuser

The last step in the deployment is to set up the database. In local development, you run python manage.py migrate and python manage.py createsuperuser to create the database schema and superuser. On Azure, you’ll do the same.

  1. In the App Service extension, right-click your app service and then select SSH into Web App. If there are errors, check that the environment vars are created correctly.
  2. Inside the SSH terminal pane, run the following commands to ensure the appropriate libraries are installed and to create the database.

If you can’t connect to SSH from VS Code, here are some steps you can try:

  • Go to the Azure portal page for the App Service and access SSH there. In VS Code, right-click the name of the App Service and select Open in Portal. On the portal page for the App Service, go to the SSH resources in the left resource pane.
  • Check that App Service hosting the web app started correctly. If there was a deployment error or coding issue, the App Service may not have started, and you can’t SSH into it. On the portal page for the App Service, go to the Diagnose and solve problems in the left resource pane to look for issues.
  1. Run database migrations
1
2
python manage.py makemigrations dog_shelters
python manage.py migrate
  1. Create the superuser python manage.py createsuperuser

Provide a name, email address, and password for the superuser.

Enable SSH when deploying from a custom Linux Docker image

https://learn.microsoft.com/en-us/azure/app-service/configure-custom-container?tabs=debian&pivots=container-linux#enable-ssh

Also definitely see (to see with a WORKDIR change): https://stackoverflow.com/questions/62018388/cant-ssh-into-linux-container-on-azure-app-service-ssh-connection-close-er

REQUIRED: WINDOWS/LINUX/GIT fucks up the line endings, fix it or entrypoint.sh wont be found: https://stackoverflow.com/questions/38905135/why-wont-my-docker-entrypoint-sh-execute

  • creating a ‘standard sshd_config file’ means creating a ‘sshd_config’ file without extension.

Follow the instructions, then rebuild using

1
docker compose up --build

Once the SSH is enabled on Azure, it opens on the root. Go to the app folder using:

cd ..
cd app/

Then run the migrations and make the db superuser:

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

For containerized applications

Follow the following link well: https://learn.microsoft.com/en-us/azure/app-service/configure-custom-container?tabs=debian&pivots=container-linux#enable-ssh This post highlights the important stuff.

Configure WEBSITES_PORT port number

As compared to local development, we wont put the port number in the webbrowser. Azure listens to a specific port: port 80. This is not the same as the django default 8000 as we use and link locally, so update this:

1
2
3
az webapp config appsettings set --resource-group <group-name> --name <app-name> --settings WEBSITES_PORT=8000
# In the current case
az webapp config appsettings set --resource-group resourcegroup-westEU-webapp --name offshorewindgis --settings WEBSITES_PORT=8000

Run in the cloud shell.

Check the new settings using:

1
az webapp config appsettings list --resource-group resourcegroup-westEU-webapp --name offshorewindgis

Set all environment variables (differently)

For some reason, we need to expressly define the variables. So the list seen before becomes:

1
2
3
4
5
az webapp config appsettings set --resource-group resourcegroup-westEU-webapp --name offshorewindgis --settings DB_HOST="postgis-server"
az webapp config appsettings set --resource-group resourcegroup-westEU-webapp --name offshorewindgis --settings DBPASS =""
az webapp config appsettings set --resource-group resourcegroup-westEU-webapp --name offshorewindgis --settings DBUSER =""
az webapp config appsettings set --resource-group resourcegroup-westEU-webapp --name offshorewindgis --settings DBNAME =""
az webapp config appsettings set --resource-group resourcegroup-westEU-webapp --name offshorewindgis --settings SECRET_KEY =""

Search for: “Azure Containerized web app cannot reach App environment”