This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can trysigning in orchanging directories.
Access to this page requires authorization. You can trychanging directories.
This article describes howAzure App Service runs Python apps, how you can migrate existing apps to Azure, and how you can customize the behavior of App Service when you need to. Python apps must be deployed with all the requiredpip modules.
The App Service deployment engine automatically activates a virtual environment and runspip install -r requirements.txt
for you when you deploy aGit repository, or when you deploy azip packagewith build automation enabled.
Note
Currently App Service requiresrequirements.txt
in your project's root directory even if you have apyproject.toml
. SeeGenerate requirements.txt from pyproject.toml for recommended approaches.
This guide provides key concepts and instructions for Python developers who use a built-in Linux container in App Service. If you've never used Azure App Service, first follow thePython quickstart andFlask,Django, orFastAPI with PostgreSQL tutorial.
You can use either theAzure portal or the Azure CLI for configuration:
Azure portal, use the app'sSettings >Configuration page as described inConfigure an App Service app in the Azure portal.
Azure CLI: you have two options.
Note
Linux is the only operating system option for running Python apps in App Service. Python on Windows is no longer supported. You can however build your own custom Windows container image and run that in App Service. For more information, seeuse a custom Docker image.
Azure portal: use theGeneral settings tab on theConfiguration page as described inConfigure general settings for Linux containers.
Azure CLI:
Show the current Python version withaz webapp config show:
az webapp config show --resource-group <resource-group-name> --name <app-name> --query linuxFxVersion
Replace<resource-group-name>
and<app-name>
with the names appropriate for your web app.
Set the Python version withaz webapp config set
az webapp config set --resource-group <resource-group-name> --name <app-name> --linux-fx-version "PYTHON|3.11"
Show all Python versions that are supported in Azure App Service withaz webapp list-runtimes:
az webapp list-runtimes --os linux | grep PYTHON
You can run an unsupported version of Python by building your own container image instead. For more information, seeuse a custom Docker image.
Outdated runtimes are deprecated by the maintaining organization or found to have significant vulnerabilities. Accordingly, they're removed from the create and configure pages in the portal. When an outdated runtime is hidden from the portal, any app that's still using that runtime continues to run.
If you want to create an app with an outdated runtime version that's no longer shown on the portal, use the Azure CLI, ARM template, or Bicep. These deployment alternatives let you create deprecated runtimes that have been removed in the portal, but are still being supported.
If a runtime is fully removed from the App Service platform, your Azure subscription owner receives an email notice before the removal.
Note
When Python applications are deployed with build automation, content will be deployed to and served from/tmp/<uid>
, not under/home/site/wwwroot
. This content directory can be access through theAPP_PATH
environment variable. Any additional files created at runtime should be written to a location under/home
or usingBring Your Own Storage for persistence. More information on this behavior can be foundhere.
App Service's build system, called Oryx, performs the following steps when you deploy your app, if the app settingSCM_DO_BUILD_DURING_DEPLOYMENT
is set to1
:
Run a custom pre-build script, if that step is specified by thePRE_BUILD_COMMAND
setting. (The script can itself run other Python and Node.js scripts, pip and npm commands, and Node-based tools like yarn, for example,yarn install
andyarn build
.)
Runpip install -r requirements.txt
. Therequirements.txt file must be present in the project's root folder. Otherwise, the build process reports the error: "Could not find setup.py or requirements.txt; Not running pip install."
Ifmanage.py is found in the root of the repository (indicating a Django app), runmanage.py collectstatic. However, if theDISABLE_COLLECTSTATIC
setting istrue
, this step is skipped.
Run custom post-build script, if that step is specified by thePOST_BUILD_COMMAND
setting. (Again, the script can run other Python and Node.js scripts, pip and npm commands, and Node-based tools.)
By default, thePRE_BUILD_COMMAND
,POST_BUILD_COMMAND
, andDISABLE_COLLECTSTATIC
settings are empty.
To disable running collectstatic when building Django apps, set theDISABLE_COLLECTSTATIC
setting totrue
.
To run pre-build commands, set thePRE_BUILD_COMMAND
setting to contain either a command, such asecho Pre-build command
, or a path to a script file, relative to your project root, such asscripts/prebuild.sh
. All commands must use relative paths to the project root folder.
To run post-build commands, set thePOST_BUILD_COMMAND
setting to contain either a command, such asecho Post-build command
, or a path to a script file, relative to your project root, such asscripts/postbuild.sh
. All commands must use relative paths to the project root folder.
For other settings that customize build automation, seeOryx configuration.
To access the build and deployment logs, seeAccess deployment logs.
For more information on how App Service runs and builds Python apps in Linux, seeHow Oryx detects and builds Python apps.
Note
ThePRE_BUILD_SCRIPT_PATH
andPOST_BUILD_SCRIPT_PATH
settings are identical toPRE_BUILD_COMMAND
andPOST_BUILD_COMMAND
and are supported for legacy purposes.
A setting namedSCM_DO_BUILD_DURING_DEPLOYMENT
, if it containstrue
or1
, triggers an Oryx build that happens during deployment. The setting istrue
when you deploy by using Git, the Azure CLI commandaz webapp up
, and Visual Studio Code.
Note
Always use relative paths in all pre- and post-build scripts because the build container in which Oryx runs is different from the runtime container in which the app runs. Never rely on the exact placement of your app project folder within the container (for example, that it's placed undersite/wwwroot).
App Service does not directly supportpyproject.toml
at the moment. If you're using tools like Poetry or uv, the recommended approach is to generate a compatiblerequirements.txt
before deployment in your project's root:
UsingPoetry with theexport plugin:
poetry export -f requirements.txt --output requirements.txt --without-hashes
Usinguv:
uv export --format requirements-txt --no-hashes --output-file requirements.txt
Existing web applications can be redeployed to Azure as follows:
Source repository: Maintain your source code in a suitable repository like GitHub, which enables you to set up continuous deployment later in this process.
Database: If your app depends on a database, create the necessary resources on Azure as well.
App service resources: Create a resource group, App Service plan, and App Service web app to host your application. You can do this easily by running the Azure CLI commandaz webapp up
. Or, you can create and deploy resources as shown in theFlask,Django, orFastAPI with PostgreSQL tutorial. Replace the names of the resource group, App Service plan, and web app to be more suitable for your application.
Environment variables: If your application requires any environment variables, create equivalentApp Service application settings. These App Service settings appear to your code as environment variables, as described inAccess environment variables.
App startup: Review the sectionContainer startup process later in this article to understand how App Service attempts to run your app. App Service uses the Gunicorn web server by default, which must be able to find your app object orwsgi.py folder. If you need to, you canCustomize the startup command.
Continuous deployment: Set up continuous deployment from GitHub Actions, Bitbucket, or Azure Repos as described in the articleContinuous deployment to Azure App Service. Or, set up continuous deployment from Local Git as described in the articleLocal Git deployment to Azure App Service.
Custom actions: To perform actions within the App Service container that hosts your app, such as Django database migrations, you canconnect to the container through SSH. For an example of running Django database migrations, seeTutorial: Deploy a Django web app with PostgreSQL - generate database schema.
With these steps completed, you should be able to commit changes to your source repository and have those updates automatically deployed to App Service.
For a production environment like Azure App Service, Django apps should follow Django'sDeployment checklist.
The following table describes the production settings that are relevant to Azure. These settings are defined in the app'ssetting.py file.
Django setting | Instructions for Azure |
---|---|
SECRET_KEY | Store the value in an App Service setting as described onAccess app settings as environment variables. You can alternativelystore the value as a secret in Azure Key Vault. |
DEBUG | Create aDEBUG setting on App Service with the value 0 (false), then load the value as an environment variable. In your development environment, create aDEBUG environment variable with the value 1 (true). |
ALLOWED_HOSTS | In production, Django requires that you include the app's URL in theALLOWED_HOSTS array ofsettings.py. You can retrieve this URL at runtime with the codeos.environ['WEBSITE_HOSTNAME'] . App Service automatically sets theWEBSITE_HOSTNAME environment variable to the app's URL. |
DATABASES | Define settings in App Service for the database connection and load them as environment variables to populate theDATABASES dictionary. You can alternatively store the values (especially the username and password) asAzure Key Vault secrets. |
If your Django web app includes static front-end files, first follow the instructions onmanaging static files in the Django documentation.
For App Service, you then make the following modifications:
Consider using environment variables (for local development) and App Settings (when deploying to the cloud) to dynamically set the DjangoSTATIC_URL
andSTATIC_ROOT
variables. For example:
STATIC_URL = os.environ.get("DJANGO_STATIC_URL", "/static/")STATIC_ROOT = os.environ.get("DJANGO_STATIC_ROOT", "./static/")
DJANGO_STATIC_URL
andDJANGO_STATIC_ROOT
can be changed as necessary for your local and cloud environments. For example, if the build process for your static files places them in a folder nameddjango-static
, then you can setDJANGO_STATIC_URL
to/django-static/
to avoid using the default.
If you have a pre-build script that generates static files in a different folder, include that folder in the DjangoSTATICFILES_DIRS
variable so that Django'scollectstatic
process finds them. For example, if you runyarn build
in your front-end folder, and yarn generates abuild/static
folder containing static files, then include that folder as follows:
FRONTEND_DIR = "path-to-frontend-folder" STATICFILES_DIRS = [os.path.join(FRONTEND_DIR, 'build', 'static')]
Here,FRONTEND_DIR
is used to build a path to where a build tool like yarn is run. You can again use an environment variable and App Setting as desired.
Addwhitenoise
to yourrequirements.txt file.WhiteNoise (whitenoise.evans.io) is a Python package that makes it simple for a production Django app to serve its own static files. WhiteNoise specifically serves those files that are found in the folder specified by the DjangoSTATIC_ROOT
variable.
In yoursettings.py file, add the following line for WhiteNoise:
STATICFILES_STORAGE = ('whitenoise.storage.CompressedManifestStaticFilesStorage')
Also modify theMIDDLEWARE
andINSTALLED_APPS
lists to include WhiteNoise:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', # Add whitenoise middleware after the security middleware 'whitenoise.middleware.WhiteNoiseMiddleware', # Other values follow]INSTALLED_APPS = [ "whitenoise.runserver_nostatic", # Other values follow]
If your Flask web app includes static front-end files, first follow the instructions onmanaging static files in the Flask documentation. For an example of serving static files in a Flask application, see thesample Flask application on GitHub.
To serve static files directly from a route on your application, you can use thesend_from_directory
method:
from flask import send_from_directory@app.route('/reports/<path:path>')def send_report(path): return send_from_directory('reports', path)
When deployed to App Service, Python apps run within a Linux Docker container that's defined in theApp Service Python GitHub repository. You can find the image configurations inside the version-specific directories.
This container has the following characteristics:
Apps are run using theGunicorn WSGI HTTP Server, using the extra arguments--bind=0.0.0.0 --timeout 600
.
You can provide configuration settings for Gunicorn bycustomizing the startup command.
To protect your web app from accidental or deliberate DDOS attacks, Gunicorn is run behind an Nginx reverse proxy as described inDeploying Gunicorn.
By default, the base container image includes only the Flask web framework, but the container supports other frameworks that are WSGI-compliant and compatible with Python 3.6+, such as Django.
To install other packages, such as Django, create arequirements.txt file in the root of your project that specifies your direct dependencies. App Service then installs those dependencies automatically when you deploy your project.
Therequirements.txt filemust be in the project root for dependencies to be installed. Otherwise, the build process reports the error: "Could not find setup.py or requirements.txt; Not running pip install." If you encounter this error, check the location of your requirements file.
App Service automatically defines an environment variable namedWEBSITE_HOSTNAME
with the web app's URL, such asmsdocs-hello-world.azurewebsites.net
. It also definesWEBSITE_SITE_NAME
with the name of your app, such asmsdocs-hello-world
.
npm and Node.js are installed in the container so you can run Node-based build tools, such as yarn.
During startup, the App Service on Linux container runs the following steps:
The following sections provide extra details for each option.
For Django apps, App Service looks for a file namedwsgi.py
within your app code, and then runs Gunicorn using the following command:
# <module> is the name of the folder that contains wsgi.pygunicorn --bind=0.0.0.0 --timeout 600 <module>.wsgi
If you want more specific control over the startup command, use acustom startup command, replace<module>
with the name of folder that containswsgi.py, and add a--chdir
argument if that module isn't in the project root. For example, if yourwsgi.py is located underknboard/backend/config from your project root, use the arguments--chdir knboard/backend config.wsgi
.
To enable production logging, add the--access-logfile
and--error-logfile
parameters as shown in the examples forcustom startup commands.
For Flask, App Service looks for a file namedapplication.py orapp.py and starts Gunicorn as follows:
# If application.pygunicorn --bind=0.0.0.0 --timeout 600 application:app# If app.pygunicorn --bind=0.0.0.0 --timeout 600 app:app
If your main app module is contained in a different file, use a different name for the app object. If you want to provide other arguments to Gunicorn, use acustom startup command.
If the App Service doesn't find a custom command, a Django app, or a Flask app, then it runs a default read-only app, located in theopt/defaultsite folder and shown in the following image.
If you deployed code and still see the default app, seeTroubleshooting - App doesn't appear.
You can control the container's startup behavior by providing either a custom startup command or multiple commands in a startup command file. A startup command file can use whatever name you choose, such asstartup.sh,startup.cmd,startup.txt, and so on.
All commands must use relative paths to the project root folder.
To specify a startup command or command file:
Azure portal: select the app'sConfiguration page, then selectGeneral settings. In theStartup Command field, place either the full text of your startup command or the name of your startup command file. Then selectSave to apply the changes. SeeConfigure general settings for Linux containers.
Azure CLI: use theaz webapp config set command with the--startup-file
parameter to set the startup command or file:
az webapp config set --resource-group <resource-group-name> --name <app-name> --startup-file "<custom-command>"
Replace<custom-command>
with either the full text of your startup command or the name of your startup command file.
App Service ignores any errors that occur when processing a custom startup command or file, then continues its startup process by looking for Django and Flask apps. If you don't see the behavior you expect, check that your startup command or file is error-free, and that a startup command file is deployed to App Service along with your app code. You can also check thediagnostic logs for more information. Also check the app'sDiagnose and solve problems page on theAzure portal.
Added Gunicorn arguments: The following example adds the--workers=4
argument to a Gunicorn command line for starting a Django app:
# <module-path> is the relative path to the folder that contains the module# that contains wsgi.py; <module> is the name of the folder containing wsgi.py.gunicorn --bind=0.0.0.0 --timeout 600 --workers=4 --chdir <module_path> <module>.wsgi
For more information, seeRunning Gunicorn. If you're using auto-scale rules to scale your web app up and down, you should also dynamically set the number of Gunicorn workers using theNUM_CORES
environment variable in your startup command, for example:--workers $((($NUM_CORES*2)+1))
. For more information on setting the recommended number of Gunicorn workers, seethe Gunicorn FAQ.
Enable production logging for Django: Add the--access-logfile '-'
and--error-logfile '-'
arguments to the command line:
# '-' for the log files means stdout for --access-logfile and stderr for --error-logfile.gunicorn --bind=0.0.0.0 --timeout 600 --workers=4 --chdir <module_path> <module>.wsgi --access-logfile '-' --error-logfile '-'
These logs will appear in theApp Service log stream.
For more information, seeGunicorn logging.
Custom Flask main module: By default, App Service assumes that a Flask app's main module isapplication.py orapp.py. If your main module uses a different name, then you must customize the startup command. For example, if you have a Flask app whose main module ishello.py and the Flask app object in that file is namedmyapp
, then the command is as follows:
gunicorn --bind=0.0.0.0 --timeout 600 hello:myapp
If your main module is in a subfolder, such aswebsite
, specify that folder with the--chdir
argument:
gunicorn --bind=0.0.0.0 --timeout 600 --chdir website hello:myapp
Use a non-Gunicorn server: To use a different web server, such asaiohttp, use the appropriate command as the startup command or in the startup command file:
python3.7 -m aiohttp.web -H localhost -P 8080 package.module:init_func
App settings are values stored in the cloud specifically for your app, as described inConfigure app settings. These settings are available to your app code as environment variables and accessed using the standardos.environ pattern.
For example, if you've created an app setting calledDATABASE_SERVER
, the following code retrieves that setting's value:
db_server = os.environ['DATABASE_SERVER']
In App Service,TLS/SSL termination happens at the network load balancers, so all HTTPS requests reach your app as unencrypted HTTP requests. If your app logic needs to check if the user requests are encrypted or not, inspect theX-Forwarded-Proto
header.
if 'X-Forwarded-Proto' in request.headers and request.headers['X-Forwarded-Proto'] == 'https':# Do something when HTTPS is used
Popular web frameworks let you access theX-Forwarded-*
information in your standard app pattern. For example, in Django you can use theSECURE_PROXY_SSL_HEADER to tell Django to use theX-Forwarded-Proto
header.
You can access the console logs generated from inside the container.
To turn on container logging, run the following command:
az webapp log config --name <app-name> --resource-group <resource-group-name> --docker-container-logging filesystem
Replace<app-name>
and<resource-group-name>
with names that are appropriate for your web app.
After you turn on container logging, run the following command to see the log stream:
az webapp log tail --name <app-name> --resource-group <resource-group-name>
If console logs don't appear immediately, check again in 30 seconds.
To stop log streaming at any time, selectCtrl+C.
To access logs through the Azure portal, selectMonitoring >Log stream on the left side menu for your app.
When you deploy your code, App Service performs the build process described earlier in the sectionCustomize build automation. Because the build runs in its own container, build logs are stored separately from the app's diagnostic logs.
Use the following steps to access the deployment logs:
Build issues such as incorrect dependencies inrequirements.txt and errors in pre- or post-build scripts will appear in these logs. Errors also appear if your requirements file isn't namedrequirements.txt or doesn't appear in the root folder of your project.
To open a direct SSH session with your container, your app should be running.
Use theaz webapp ssh command.
If you're not yet authenticated, you're required to authenticate with your Azure subscription to connect. Once authenticated, you see an in-browser shell, where you can run commands inside your container.
Note
Any changes that you make outside the/home
directory are stored in the container itself and don't persist beyond an app restart.
To open a remote SSH session from your local machine, seeOpen SSH session from remote shell.
When you're successfully connected to the SSH session, you should see the message "SSH CONNECTION ESTABLISHED" at the bottom of the window. If you see errors such as "SSH_CONNECTION_CLOSED" or a message that the container is restarting, an error might be preventing the app container from starting. SeeTroubleshooting for steps to investigate possible issues.
When deploying Python applications on Azure App Service for Linux, you might need to handle URL rewrites within your application. This is particularly useful for ensuring specific URL patterns are redirected to the correct endpoints without relying on external web server configurations. For Flask applications,URL processors and custom middleware can be used to achieve this. In Django applications, the robustURL dispatcher allows for efficient management of URL rewrites.
In general, the first step in troubleshooting is to use App Service diagnostics:
Next, examine both thedeployment logs and theapp logs for any error messages. These logs often identify specific issues that can prevent app deployment or app startup. For example, the build can fail if yourrequirements.txt file has the wrong filename or isn't present in your project root folder.
The following sections provide guidance for specific issues.
You see the default app after deploying your own app code. Thedefault app appears because you either haven't deployed your app code to App Service, or App Service failed to find your app code and ran the default app instead.
Restart the App Service, wait 15-20 seconds, and check the app again.
UseSSH to connect directly to the App Service container and verify that your files exist undersite/wwwroot. If your files don't exist, use the following steps:
SCM_DO_BUILD_DURING_DEPLOYMENT
with the value of 1, redeploy your code, wait a few minutes, then try to access the app again. For more information on creating app settings, seeConfigure an App Service app in the Azure portal.If your files exist, then App Service wasn't able to identify your specific startup file. Check that your app is structured as App Service expects forDjango orFlask, or use acustom startup command.
You see the message "Service Unavailable" in the browser. The browser has timed out waiting for a response from App Service, which indicates that App Service started the Gunicorn server, but the app itself didn't start. This condition could indicate that the Gunicorn arguments are incorrect, or that there's an error in the app code.
Refresh the browser, especially if you're using the lowest pricing tiers in your App Service plan. The app might take longer to start up when you use free tiers, for example, and becomes responsive after you refresh the browser.
Check that your app is structured as App Service expects forDjango orFlask, or use acustom startup command.
Examine theapp log stream for any error messages. The logs will show any errors in the app code.
The log stream shows "Could not find setup.py or requirements.txt; Not running pip install.": The Oryx build process failed to find yourrequirements.txt file.
If you see an error likeModuleNotFoundError: No module named 'example'
, then Python couldn't find one or more of your modules when the application started. This error most often occurs if you deploy your virtual environment with your code. Virtual environments aren't portable, so a virtual environment shouldn't be deployed with your application code. Instead, let Oryx create a virtual environment and install your packages on the web app by creating an app setting,SCM_DO_BUILD_DURING_DEPLOYMENT
, and setting it to1
. This setting will force Oryx to install your packages whenever you deploy to App Service. For more information, seethis article on virtual environment portability.
When attempting to run database migrations with a Django app, you might see "sqlite3. OperationalError: database is locked." The error indicates that your application is using a SQLite database, for which Django is configured by default, rather than using a cloud database such as Azure Database for PostgreSQL.
Check theDATABASES
variable in the app'ssettings.py file to ensure that your app is using a cloud database instead of SQLite.
If you're encountering this error with the sample inTutorial: Deploy a Django web app with PostgreSQL, check that you completed the steps inVerify connection settings.
Passwords don't appear in the SSH session when typed: For security reasons, the SSH session keeps your password hidden when you type. The characters are being recorded, however, so type your password as usual and selectEnter when done.
Commands in the SSH session appear to be cut off: The editor might not be word-wrapping commands, but they should still run correctly.
Static assets don't appear in a Django app: Ensure that you've enabled theWhiteNoise module.
You see the message, "Fatal SSL Connection is Required": Check any usernames and passwords used to access resources (such as databases) from within the app.
Was this page helpful?
Was this page helpful?