Build and deploy a Python web app with Azure Container Apps and PostgreSQL

This article is part of a tutorial about how to containerize and deploy a Python web app to Azure Container Apps. Container Apps enables you to deploy containerized apps without managing complex infrastructure.

In this part of the tutorial, you learn how to containerize and deploy a Python sample web app (Django or Flask). Specifically, you build the container image in the cloud and deploy it to Azure Container Apps. You define environment variables that enable the container app to connect to an Azure Database for PostgreSQL - Flexible Server instance, where the sample app stores data.

This service diagram highlights the components covered in this article: building and deploying a container image.

A screenshot of the services in the Tutorial - Deploy a Python App on Azure Container Apps. Section highlighted is what is covered in this article.

Get the sample app

Fork and clone the sample code to your developer environment.

Step 1. Go to the GitHub repository of the sample app (Django or Flask) and select Fork.

Follow the steps to fork the directory to your GitHub account. You can also download the code repo directly to your local machine without forking or a GitHub account, however, you won't be able to set up CI/CD discussed later in the tutorial.

Step 2. Use the git clone command to clone the forked repo into the python-container folder:

# Django
git clone https://github.com/$USERNAME/msdocs-python-django-azure-container-apps.git python-container

# Flask
# git clone https://github.com/$USERNAME/msdocs-python-flask-azure-container-apps.git python-container

Step 3. Change directory.

cd python-container

Build a container image from web app code

After following these steps, you'll have an Azure Container Registry that contains a Docker container image built from the sample code.

Azure CLI commands can be run in the Azure Cloud Shell or on a workstation with the Azure CLI installed.

Step 1. Create a resource group with the az group create command.

az group create \
--name pythoncontainer-rg \
--location <location>

<location> is one of the Azure location Name values from the output of the command az account list-locations -o table.

Step 2. Create a container registry with the az acr create command.

az acr create \
--resource-group pythoncontainer-rg \
--name <registry-name> \
--sku Basic \
--admin-enabled

<registry-name> must be unique within Azure, and contain 5-50 alphanumeric characters.

You can view the credentials created for admin with:

az acr credential show \
--name <registry-name> \
--resource-group pythoncontainer-rg

Step 3. Sign in to the registry using the az acr login command.

az acr login --name <registry-name>

The command adds "azurecr.io" to the name to create the fully qualified registry name. If successful, you'll see the message "Login Succeeded". If you're accessing the registry from a subscription different from the one in which the registry was created, use the --suffix switch.

Step 4. Build the image with the az acr build command.

az acr build \
--registry <registry-name> \
--resource-group pythoncontainer-rg \
--image pythoncontainer:latest .

Note that:

  • The dot (".") at the end of the command indicates the location of the source code to build. If you aren't running this command in the sample app root directory, specify the path to the code.

  • If you are running the command in Azure Cloud Shell, use git clone to first pull the repo into the Cloud Shell environment first and change directory into the root of the project so that dot (".") is interpreted correctly.

  • If you leave out the -t (same as --image) option, the command queues a local context build without pushing it to the registry. Building without pushing can be useful to check that the image builds.

Step 5. Confirm the container image was created with the az acr repository list command.

az acr repository list --name <registry-name>

Create a PostgreSQL Flexible Server instance

The sample app (Django or Flask) stores restaurant review data in a PostgreSQL database. In these steps, you create the server that will contain the database.

Azure CLI commands can be run in the Azure Cloud Shell or on a workstation with the Azure CLI installed.

Step 1. Use the az postgres flexible-server create command to create the PostgreSQL server in Azure. It isn't uncommon for this command to run for a few minutes to complete.

az postgres flexible-server create \
   --resource-group pythoncontainer-rg \
   --name <postgres-server-name>  \
   --location <location> \
   --admin-user <admin-username> \
   --admin-password <admin-password> \
   --sku-name Standard_D2s_v3 \
   --public-access 0.0.0.0 
  • "pythoncontainer-rg" → The resource group name used in this tutorial. If you used a different name, change this value.

  • <postgres-server-name> → The PostgreSQL database server name. This name must be unique across all Azure. The server endpoint is "https://<postgres-server-name>.postgres.database.azure.com". Allowed characters are "A"-"Z", "0"-"9", and "-".

  • <location> → Use the same location used for the web app. <location> is one of the Azure location Name values from the output of the command az account list-locations -o table.

  • <admin-username> → Username for the administrator account. It can't be "azure_superuser", "admin", "administrator", "root", "guest", or "public". Use "demoadmin" for this tutorial.

  • <admin-password> Password of the administrator user. It must contain 8 to 128 characters from three of the following categories: English uppercase letters, English lowercase letters, numbers, and non-alphanumeric characters.

    Important

    When creating usernames or passwords do not use the "$" character. Later you create environment variables with these values where the "$" character has special meaning within the Linux container used to run Python apps.

  • <sku-name> → The name of the pricing tier and compute configuration, for example "Standard_D2s_v3". For more information, see Azure Database for PostgreSQL pricing. To list available SKUs, use az postgres flexible-server list-skus --location <location>.

  • <public-access> → Use "0.0.0.0", which allows public access to the server from any Azure service, such as Container Apps.

Note

If you plan on working the PostgreSQL server from your local workstation with tools other than Azure CLI, you'll need to add a firewall rule with the az postgres flexible-server firewall-rule create command.

Create a database on the server

At this point, you have a PostgreSQL server. In this section, you create a database on the server.

You can use the PostgreSQL interactive terminal psql in your local environment, or in the Azure Cloud Shell, which is also accessible in the Azure portal. When working with psql, it's often easier to use the Cloud Shell because all the dependencies are included for you in the shell.

Step 1. Connect to the database with psql.

psql --host=<postgres-server-name>.postgres.database.azure.com \
     --port=5432 \
     --username=demoadmin@<postgres-server-name> \
     --dbname=postgres

Where <postgres-server-name> is the name of the PostgreSQL server. The command will prompt you for the admin password.

If you have trouble connecting, restart the database and try again. If you're connecting from your local environment, your IP address must be added to the firewall rule list for the database service.

Step 2. Create the database.

At the postgres=> prompt type:

CREATE DATABASE restaurants_reviews;

The semicolon (";") at the end of the command is necessary. To verify that the database was successfully created, use the command \c restaurants_reviews. Type \? to show help or \q to quit.

You can also connect to Azure PostgreSQL Flexible server and create a database using Azure Data Studio or any other IDE that supports PostgreSQL.

Deploy the web app to Container Apps

Container apps are deployed to Container Apps environments, which act as a secure boundary. In the following steps, you create the environment, a container inside the environment, and configure the container so that the website is visible externally.

Step 1. Sign in to Azure and authenticate, if needed.

az login

Step 2. Install or upgrade the extension for Azure Container Apps withe az extension add command.

az extension add --name containerapp --upgrade

Step 3. Create a Container Apps environment with the az containerapp env create command.

az containerapp env create \
--name python-container-env \
--resource-group pythoncontainer-rg \
--location <location>

<location> is one of the Azure location Name values from the output of the command az account list-locations -o table.

Step 4. Get the sign-in credentials for the Azure Container Registry.

az acr credential show -n <registry-name>

Use the username and one of the passwords returned from the output of the command.

Step 5. Create a container app in the environment with the az containerapp create command.

az containerapp create \
--name python-container-app \
--resource-group pythoncontainer-rg \
--image <registry-name>.azurecr.io/pythoncontainer:latest \
--environment python-container-env \
--ingress external \
--target-port 8000 \
--registry-server <registry-name>.azurecr.io \
--registry-username <registry-username> \
--registry-password <registry-password> \
--env-vars <env-variable-string>
--query properties.configuration.ingress.fqdn

<env-variable-string> is a string composed of space-separated values in the key="value" format with the following values.

  • AZURE_POSTGRESQL_HOST=<postgres-server-name>.postgres.database.azure.com
  • AZURE_POSTGRESQL_DATABASE=restaurants_reviews
  • AZURE_POSTGRESQL_USERNAME=demoadmin
  • AZURE_POSTGRESQL_PASSWORD=<db-password>
  • RUNNING_IN_PRODUCTION=1
  • AZURE_SECRET_KEY=<YOUR-SECRET-KEY>

Generate AZURE_SECRET_KEY value using output of python -c 'import secrets; print(secrets.token_hex())'.

Here's an example: --env-vars AZURE_POSTGRESQL_HOST="my-postgres-server.postgres.database.azure.com" AZURE_POSTGRESQL_DATABASE="restaurants_reviews" AZURE_POSTGRESQL_USERNAME="demoadmin" AZURE_POSTGRESQL_PASSWORD="somepassword" RUNNING_IN_PRODUCTION="1" AZURE_SECRET_KEY=asdfasdfasdf.

Step 7. For Django only, migrate and create database schema. (In the Flask sample app, it's done automatically, and you can skip this step.)

Connect with the az containerapp exec command:

az containerapp exec \
--name python-container-app \
--resource-group pythoncontainer-rg

Then, at the shell command prompt type python manage.py migrate.

You don't need to migrate for revisions of the container.

Step 8. Test the website.

The az containerapp create command you entered previously outputs an application URL you can use to browse to the app. The URL ends in "azurecontainerapps.io". Navigate to the URL in a browser. Alternatively, you can use the az containerapp browse command.

Here's an example of the sample website after adding a restaurant and two reviews.

Screenshot showing an example of the sample website built in this tutorial.

Troubleshoot deployment

  • You forgot the Application Url to access the website.

    • In the Azure portal, go to the Overview page of the Container App and look for the Application Url.
    • In VS Code, go to the Azure extension and select the Container Apps section. Expand the subscription, expand the container environment, and when you find the container app, right-click python-container-app and select Browse.
    • With Azure CLI, use the command az containerapp show -g pythoncontainer-rg -n python-container-app --query properties.configuration.ingress.fqdn.
  • In VS Code, the Build Image in Azure task returns an error.

    • If you see the message "Error: failed to download context. Please check if the URL is incorrect." in the VS Code Output window, then refresh the registry in the Docker extension. To refresh, select the Docker extension, go to the Registries section, find the registry and select it.
    • If you run the Build Image in Azure task again, check to see if your registry from a previous run exists and if so, use it.
  • In the Azure portal during the creation of a Container App, you see an access error that contains "Cannot access ACR '<name>.azurecr.io'".

    • This error occurs when admin credentials on the ACR are disabled. To check admin status in the portal, go to your Azure Container Registry, select the Access keys resource, and ensure that Admin user is enabled.
  • Your container image doesn't appear in the Azure Container Registry.

    • Check the output of the Azure CLI command or VS Code Output and look for messages to confirm success.
    • Check that the name of the registry was specified correctly in your build command with the Azure CLI or in the VS Code task prompts.
    • Make sure your credentials aren't expired. For example, in VS Code, find the target registry in the Docker extension and refresh. In Azure CLI, run az login.
  • Website returns "Bad Request (400)".

    • Check the PostgreSQL environment variables passed in to the container. The 400 error often indicates that the Python code can't connect to the PostgreSQL instance.
    • The sample code used in this tutorial checks for the existence of the container environment variable RUNNING_IN_PRODUCTION, which can be set to any value like "1".
  • Website returns "Not Found (404)".

    • Check the Application Url on the Overview page for the container. If the Application Url contains the word "internal", then ingress isn't set correctly.
    • Check the ingress of the container. For example, in Azure portal, go to the Ingress resource of the container and make sure HTTP Ingress is enabled and Accepting traffic from anywhere is selected.
  • Website doesn't start, you see "stream timeout", or nothing is returned.

    • Check the logs.
      • In the Azure portal, go to the Container App's Revision management resource and check the Provision Status of the container.
        • If "Provisioning", then wait until provisioning has completed.
        • If "Failed", then select the revision and view the console logs. Choose the order of the columns to show "Time Generated", "Stream_s", and "Log_s". Sort the logs by most-recent first and look for Python stderr and stdout messages in the "Stream_s" column. Python 'print' output will be stdout messages.
      • With the Azure CLI, use the az containerapp logs show command.
    • If using the Django framework, check to see if the restaurants_reviews tables exist in the database. If not, use a console to access the container and run python manage.py migrate.

Next step