Deploy a Python (Django or Flask) web app with PostgreSQL in Azure

In this tutorial, you'll deploy a data-driven Python web app (Django or Flask) to Azure App Service with the Azure Database for PostgreSQL relational database service. Azure App Service supports Python 3.7 or higher in a Linux server environment.

An architecture diagram showing an App Service with a PostgreSQL database in Azure.

To complete this tutorial, you'll need:

Sample application

Sample Python applications using the Flask and Django framework are provided to help you follow along with this tutorial. To deploy them without running them locally, skip this part.

To run the application locally, make sure you have Python 3.7 or higher and PostgreSQL installed locally. Then, download or clone the app:

git clone https://github.com/Azure-Samples/msdocs-flask-postgresql-sample-app.git

Go to the application folder:

cd msdocs-python-flask-webapp-quickstart

Create an .env file as shown below using the .env.sample file as a guide. Set the value of DBNAME to the name of an existing database in your local PostgreSQL instance. Set the values of DBHOST, DBUSER, and DBPASS as appropriate for your local PostgreSQL instance.

DBNAME=<database name>
DBHOST=<database-hostname>
DBUSER=<db-user-name>
DBPASS=<db-password>

Create a SECRET_KEY value for your app by running the following command at a terminal prompt: python -c 'import secrets; print(secrets.token_hex())'.

Set the returned value as the value of SECRET_KEY in the .env file.

SECRET_KEY=<secret-key>

Create a virtual environment for the app:

py -m venv .venv
.venv\scripts\activate

Install the dependencies:

pip install -r requirements.txt

Run the sample application with the following commands:

# Run database migration
flask db upgrade
# Run the app at http://127.0.0.1:5000
flask run

1. Create App Service and PostgreSQL

In this step, you create the Azure resources. The steps used in this tutorial create a set of secure-by-default resources that include App Service and Azure Database for PostgreSQL. For the creation process, you'll specify:

  • The Name for the web app. It's the name used as part of the DNS name for your webapp in the form of https://<app-name>.azurewebsites.net.
  • The Region to run the app physically in the world.
  • The Runtime stack for the app. It's where you select the version of Python to use for your app.
  • The Hosting plan for the app. It's the pricing tier that includes the set of features and scaling capacity for your app.
  • The Resource Group for the app. A resource group lets you group (in a logical container) all the Azure resources needed for the application.

Sign in to the Azure portal and follow these steps to create your Azure App Service resources.

Step 1. In the Azure portal:

  1. Enter "web app database" in the search bar at the top of the Azure portal.
  2. Select the item labeled Web App + Database under the Marketplace heading. You can also navigate to the creation wizard directly.

Step 2. In the Create Web App + Database page, fill out the form as follows.

  1. Resource Group → Select Create new and use a name of msdocs-python-postgres-tutorial.
  2. Region → Any Azure region near you.
  3. Namemsdocs-python-postgres-XYZ where XYZ is any three random characters. This name must be unique across Azure.
  4. Runtime stackPython 3.10.
  5. DatabasePostgreSQL - Flexible Server is selected by default as the database engine. The server name and database name are also set by default to appropriate values.
  6. Hosting planBasic. When you're ready, you can scale up to a production pricing tier later.
  7. Select Review + create.
  8. After validation completes, select Create.

Step 3. The deployment takes a few minutes to complete. Once deployment completes, select the Go to resource button. You're taken directly to the App Service app, but the following resources are created:

  • Resource group → The container for all the created resources.
  • App Service plan → Defines the compute resources for App Service. A Linux plan in the Basic tier is created.
  • App Service → Represents your app and runs in the App Service plan.
  • Virtual network → Integrated with the App Service app and isolates back-end network traffic.
  • Azure Database for PostgreSQL flexible server → Accessible only from within the virtual network. A database and a user are created for you on the server.
  • Private DNS zone → Enables DNS resolution of the PostgreSQL server in the virtual network.

2. Verify connection settings

The creation wizard generated the connectivity variables for you already as app settings. App settings are one way to keep connection secrets out of your code repository. When you're ready to move your secrets to a more secure location, here's an article on storing in Azure Key Vault.

Step 1. In the App Service page, in the left menu, select Configuration.

Step 2. In the Application settings tab of the Configuration page, verify that AZURE_POSTGRESQL_CONNECTIONSTRING is present. That will be injected into the runtime environment as an environment variable.

Step 3. In a terminal or command prompt, run the following Python script to generate a unique secret: python -c 'import secrets; print(secrets.token_hex())'. Copy the output value to use in the next step.

Step 4. In the Application settings tab of the Configuration page, select New application setting. Name the setting SECRET_KEY. Paste the value from the previous value. Select OK.

Step 5. Select Save.

Having issues? Check the Troubleshooting guide.

3. Deploy sample code

In this step, you'll configure GitHub deployment using GitHub Actions. It's just one of many ways to deploy to App Service, but also a great way to have continuous integration in your deployment process. By default, every git push to your GitHub repository will kick off the build and deploy action.

Step 1. In a new browser window:

  1. Sign in to your GitHub account.
  2. Navigate to https://github.com/Azure-Samples/msdocs-flask-postgresql-sample-app.
  3. Select Fork.
  4. Select Create fork.

Step 2. In the GitHub page, open Visual Studio Code in the browser by pressing the . key.

Step 3. In Visual Studio Code in the browser, open azureproject/production.py in the explorer. See the environment variables being used in the production environment, including the app settings that you saw in the configuration page.

Step 4. Back in the App Service page, in the left menu, select Deployment Center.

Step 5. In the Deployment Center page:

  1. In Source, select GitHub. By default, GitHub Actions is selected as the build provider.
  2. Sign in to your GitHub account and follow the prompt to authorize Azure.
  3. In Organization, select your account.
  4. In Repository, select msdocs-flask-postgresql-sample-app.
  5. In Branch, select main.
  6. Keep the default option selected to Add a workflow.
  7. In the top menu, select Save. App Service commits a workflow file into the chosen GitHub repository, in the .github/workflows directory.

Step 6. In the Deployment Center page:

  1. Select Logs. A deployment run is already started.
  2. In the log item for the deployment run, select Build/Deploy Logs.

Step 7. You're taken to your GitHub repository and see that the GitHub action is running. The workflow file defines two separate stages, build and deploy. Wait for the GitHub run to show a status of Complete. It takes about 5 minutes.

Having issues? Check the Troubleshooting guide.

4. Generate database schema

With the PostgreSQL database protected by the virtual network, the easiest way to run Flask database migrations is in an SSH session with the App Service container.

Step 1. Back in the App Service page, in the left menu, select SSH.

  1. Select Go.

Step 2. In the SSH terminal, run flask db upgrade. If it succeeds, App Service is connecting successfully to the database. Only changes to files in /home can persist beyond app restarts. Changes outside of /home aren't persisted.

5. Browse to the app

Step 1. In the App Service page:

  1. From the left menu, select Overview.
  2. Select the URL of your app. You can also navigate directly to https://<app-name>.azurewebsites.net.

Step 2. Add a few restaurants to the list. Congratulations, you're running a web app in Azure App Service, with secure connectivity to Azure Database for PostgreSQL.

6. Stream diagnostic logs

Azure App Service captures all messages output to the console to help you diagnose issues with your application. The sample app includes print() statements to demonstrate this capability as shown below.

@app.route('/', methods=['GET'])
def index():
    print('Request for index page received')
    restaurants = Restaurant.query.all()
    return render_template('index.html', restaurants=restaurants)

Step 1. In the App Service page:

  1. From the left menu, select App Service logs.
  2. Under Application logging, select File System.
  3. In the top menu, select Save.

Step 2. From the left menu, select Log stream. You see the logs for your app, including platform logs and logs from inside the container.

Learn more about logging in Python apps in the series on setting up Azure Monitor for your Python application.

7. Clean up resources

When you're finished, you can delete all of the resources from your Azure subscription by deleting the resource group.

Step 1. In the search bar at the top of the Azure portal:

  1. Enter the resource group name.
  2. Select the resource group.

Step 2. In the resource group page, select Delete resource group.

Step 3.

  1. Enter the resource group name to confirm your deletion.
  2. Select Delete.

A screenshot of the confirmation dialog for deleting a resource group in the Azure portal. :

Troubleshooting

Listed below are issues you may encounter while trying to work through this tutorial and steps to resolve them.

I can't connect to the SSH session

If you can't connect to the SSH session, then the app itself has failed to start. Check the diagnostic logs for details. For example, if you see an error like KeyError: 'AZURE_POSTGRESQL_CONNECTIONSTRING', it may mean that the environment variable is missing (you may have removed the app setting).

I get an error when running database migrations

If you encounter any errors related to connecting to the database, check if the app settings (AZURE_POSTGRESQL_CONNECTIONSTRING) have been changed. Without that connection string, the migrate command can't communicate with the database.

Provision and deploy using the Azure Developer CLI

Sample Python application templates using the Flask and Django framework are provided for this tutorial. The Azure Developer CLI greatly streamlines the process of provisioning application resources and deploying code on Azure. For a more step-by-step approach using the Azure portal and other tools, toggle to the Azure portal approach at the top of the page.

The Azure Developer CLI (azd) provides end-to-end support for project initialization, provisioning, deploying, monitoring and scaffolding a CI/CD pipeline to run against real Azure resources. You can use azd to provision and deploy the resources for the sample application in an automated and streamlined way.

Follow the steps below to setup the Azure Developer CLI and provision and deploy the sample application:

  1. Install the Azure Developer CLI. For a full list of supported installation options and tools, visit the installation guide.

    powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression"
    
  2. Run the azd up command to clone, provision and deploy the app resources. Provide the name of the template you wish to use for the --template parameter. The azd up command will also prompt you to login to Azure and provide a name and location for the app.

    azd up --template msdocs-flask-postgresql-sample-app
    
  3. When the azd up command finishes running, the URL for your deployed web app in the console will be printed. Click, or copy and paste the web app URL into your browser to explore the running app and verify that it is working correctly. All of the Azure resources and application code were set up for you by the azd up command.

    The name of the resource group that was created is also displayed in the console output. Locate the resource group in the Azure portal to see all of the provisioned resources.

    A screenshot showing the resources deployed by the Azure Developer CLI.

The Azure Developer CLI also enables you to configure your application to use a CI/CD pipeline for deployments, setup monitoring functionality, and even remove the provisioned resources if you want to tear everything down. For more information about these additional workflows, visit the project README.

Explore the completed azd project template workflow

The sections ahead review the steps that azd handled for you in more depth. You can explore this workflow to better understand the requirements for deploying your own apps to Azure. When you ran azd up, the Azure Developer CLI completed the following steps:

Note

You can also use the steps outlined in the Azure portal version of this flow to gain additional insights into the tasks that azd completed for you.

1. Cloned and initialized the project

The azd up command cloned the sample app project template to your machine. The project template includes the following components:

  • Source code: The code and assets for a Flask or Django web app that can be used for local development or deployed to Azure.
  • Bicep files: Infrastructure as code (IaC) files that are used by azd to create the necessary resources in Azure.
  • Configuration files: Essential configuration files such as azure.yaml that are used by azd to provision, deploy and wire resources together to produce a fully fledged application.

2. Provisioned the Azure resources

The azd up command created all of the resources for the sample application in Azure using the Bicep files in the infra folder of the project template. Bicep is a declarative language used to manage Infrastructure as Code in Azure. Some of the key resources and configurations created by the template include:

  • Resource group: A resource group was created to hold all of the other provisioned Azure resources. The resource group keeps your resources well organized and easier to manage. The name of the resource group is based off of the environment name you specified during the azd up initialization process.
  • Azure Virtual Network: A virtual network was created to enable the provisioned resources to securely connect and communicate with one another. Related configurations such as setting up a private DNS zone link were also applied.
  • Azure App Service plan: An App Service plan was created to host App Service instances. App Service plans define what compute resources are available for one or more web apps.
  • Azure App Service: An App Service instance was created in the new App Service plan to host and run the deployed application. In this case a Linux instance was created and configured to run Python apps. Additional configurations were also applied to the app service, such as setting the Postgres connection string and secret keys.
  • Azure Database for PostgresSQL: A Postgres database and server were created for the app hosted on App Service to connect to. The required admin user, network and connection settings were also configured.
  • Azure Application Insights: Application insights was set up and configured for the app hosted on the App Service. This service enables detailed telemetry and monitoring for your application.

You can inspect the Bicep files in the infra folder of the project to understand how each of these resources were provisioned in more detail. The resources.bicep file defines most of the different services created in Azure. For example, the App Service plan and App Service web app instance were created and connected using the following Bicep code:

resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
  name: '${prefix}-service-plan'
  location: location
  tags: tags
  sku: {
    name: 'B1'
  }
  properties: {
    reserved: true
  }
}

resource web 'Microsoft.Web/sites@2022-03-01' = {
  name: '${prefix}-app-service'
  location: location
  tags: union(tags, { 'azd-service-name': 'web' })
  kind: 'app,linux'
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      alwaysOn: true
      linuxFxVersion: 'PYTHON|3.10'
      ftpsState: 'Disabled'
      appCommandLine: 'startup.sh'
    }
    httpsOnly: true
  }
  identity: {
    type: 'SystemAssigned'
  }

The Azure Database for PostgreSQL was also created using the following Bicep:

resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' = {
  location: location
  tags: tags
  name: pgServerName
  sku: {
    name: 'Standard_B1ms'
    tier: 'Burstable'
  }
  properties: {
    version: '12'
    administratorLogin: 'postgresadmin'
    administratorLoginPassword: databasePassword
    storage: {
      storageSizeGB: 128
    }
    backup: {
      backupRetentionDays: 7
      geoRedundantBackup: 'Disabled'
    }
    network: {
      delegatedSubnetResourceId: virtualNetwork::databaseSubnet.id
      privateDnsZoneArmResourceId: privateDnsZone.id
    }
    highAvailability: {
      mode: 'Disabled'
    }
    maintenanceWindow: {
      customWindow: 'Disabled'
      dayOfWeek: 0
      startHour: 0
      startMinute: 0
    }
  }

  dependsOn: [
    privateDnsZoneLink
  ]
}

3. Deployed the application

The azd up command also deployed the sample application code to the provisioned Azure resources. The Developer CLI understands how to deploy different parts of your application code to different services in Azure using the azure.yaml file at the root of the project. The azure.yaml file specifies the app source code location, the type of app, and the Azure Service that should host that app.

Consider the following azure.yaml file. These configurations tell the Azure Developer CLI that the Python code that lives at the root of the project should be deployed to the created App Service.

name: flask-postgresql-sample-app
metadata:
  template: flask-postgresql-sample-app@0.0.1-beta
services:
  web:
    project: .
    language: py
    host: appservice

Remove the resources

Once you are finished experimenting with your sample application, you can run the azd down command to remove the app from Azure. Removing resources helps to avoid unintended costs or unused services in your Azure subscription.

azd down

Frequently asked questions

How much does this setup cost?

Pricing for the created resources is as follows:

How do I connect to the PostgreSQL server that's secured behind the virtual network with other tools?

  • For basic access from a commmand-line tool, you can run psql from the app's SSH terminal.
  • To connect from a desktop tool, your machine must be within the virtual network. For example, it could be an Azure VM that's connected to one of the subnets, or a machine in an on-premises network that has a site-to-site VPN connection with the Azure virtual network.
  • You can also integrate Azure Cloud Shell with the virtual network.

How does local app development work with GitHub Actions?

Using the autogenerated workflow file from App Service as an example, each git push kicks off a new build and deployment run. From a local clone of the GitHub repository, you make the desired updates and push to GitHub. For example:

git add .
git commit -m "<some-message>"
git push origin main

How is the Django sample configured to run on Azure App Service?

Note

If you are following along with this tutorial with your own app, look at the requirements.txt file description in each project's README.md file (Flask, Django) to see what packages you'll need.

The Django sample application configures settings in the azureproject/production.py file so that it can run in Azure App Service. These changes are common to deploying Django to production, and not specific to App Service.

  • Django validates the HTTP_HOST header in incoming requests. The sample code uses the WEBSITE_HOSTNAME environment variable in App Service to add the app's domain name to Django's ALLOWED_HOSTS setting.

    # Configure the domain name using the environment variable
    # that Azure automatically creates for us.
    ALLOWED_HOSTS = [os.environ['WEBSITE_HOSTNAME']] if 'WEBSITE_HOSTNAME' in os.environ else []
    
  • Django doesn't support serving static files in production. For this tutorial, you use WhiteNoise to enable serving the files. The WhiteNoise package was already installed with requirements.txt, and its middleware is added to the list.

    
    # WhiteNoise configuration
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        # Add whitenoise middleware after the security middleware
        'whitenoise.middleware.WhiteNoiseMiddleware',
    

    Then the static file settings are configured according to the Django documentation.

    STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
    STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
    

For more information, see Production settings for Django apps.

Next steps

Advance to the next tutorial to learn how to secure your app with a custom domain and certificate.

Learn how App Service runs a Python app: