Run a Python bot for free on Azure Web App Service
Pre-requisite: have a bot. You can use mehov/bot-clock as an example. It works with Telegram, so you will need TELEGRAM_BOT_TOKEN
and TELEGRAM_CHAT_ID
- see the bot's readme for more info.
Create a new Web App
Go to Azure Portal → App Services and create a new Web App:
- Publish: Code
- Runtime stack: Python
- Operating System: Linux
- Database, Deployment, Monitor + secure: disable or reject everything
- Networking → Enable public access: On
Disable Deployment for now - you will configure it later after the app has been created.
Configure environment
Go to Settings → Environment Variables
- set
TELEGRAM_BOT_TOKEN
andTELEGRAM_CHAT_ID
; see the bot's readme for more info
Configure Startup: gunicorn
Under Settings → Configuration → General settings → Startup Command:
chmod +x $APP_PATH/setup.sh && $APP_PATH/setup.sh "$WEBSITE_HOSTNAME" && gunicorn --bind=$HOST:$PORT app:app
The above runs setup.sh
on startup. The script ensures the CRON command is set up. It then starts the Azure app using gunicorn
- more info on that below under Gotchas. $WEBSITE_HOSTNAME
, $HOST
and $PORT
are retrieved from the environment.
Configure Startup: manual
Sometimes you may want to handle gunicorn yourself. For example, if you want to create and run an instance of Application.builder()
from telegram.ext
and have it share the event loop with Flask. For that you will need to define the uvicorn.Server
yourself and it would look like this:
import asyncio
import telegram
import uvicorn
from asgiref.wsgi import WsgiToAsgi
from flask import Flask
from telegram.ext import (
Application,
)
flask_app = Flask(__name__)
telegram_app = Application.builder().token('TELEGRAM_BOT_TOKEN').updater(None).build()
async def main():
webserver = uvicorn.Server(
config=uvicorn.Config(
app=WsgiToAsgi(flask_app),
port=8000,
use_colors=False,
host='0.0.0.0',
)
)
async with telegram_app:
await telegram_app.start()
await webserver.serve()
await telegram_app.stop()
if __name__ == '__main__':
asyncio.run(main())
Under Settings → Configuration → General settings → Startup Command:
chmod +x $APP_PATH/setup.sh && $APP_PATH/setup.sh "$WEBSITE_HOSTNAME" && HOME="/home/site/wwwroot" python3 app.py
Similarly to the gunicorn startup command, this one begins with setting up cron, and then it just starts the script directly (making sure it has Azure's persistent location as $HOME
)
Configure deployment
Still under Settings → Configuration → General settings:
- set SCM Basic Auth Publishing Credentials to On - when disabled, Github actions that rely on HTTP basic auth won't work
- (optional) enable or disable FTP and SSH to your preference
Then go to App Services → Your New App → Deployment → Deployment Center. Open Settings.
- Source: External Git (instead of choosing Github - this way you don't have to use Github Actions/Workflows, Azure picks up the commits by itself)
- Build Provider: App Service Build Service
- Repository: https://github.com/mehov/bot-clock.git
- Branch: master
If you want Azure to have access to your private repositories, authorise Azure to your Github account.
Automatic deployment
If you want your app to be deployed to Azure automatically on each commit, you will have to set up a webhook in your repo settings.
- Get the secrets from Azure. Go to Deployment → Deployment Center → Manage Publish Profile. In the pop-up click Download publish profile. In the XML file that downloads, in the entry where it says profileName="Web Deploy" and publishMethod="MSDeploy", you're looking for the following values:
- publishUrl:
{your-app}.scm.azurewebsites.net:443
- userName:
${your-app}
,$
is important - userPWD:
{BigRandomPassword}
- publishUrl:
-
Go to
https://github.com/{you}/{repo}/settings/hooks
and create a new webhook that looks like this:https://{userName}:{userPWD}@{publishUrl}/deploy
More info:
Gotchas
Because Startup Command is not empty Azure won't automatically start the app
When you create an app, Startup Command is normally empty, and so Azure automatically detects the web app environment and then autostarts the app. If a WSGI-compatible app, e.g. based on Flask, is detected, Azure uses WSGI servers like Gunicorn to handle concurrent requests and manage threading, as Flask’s built-in server (app.run()
) is not suitable for production environments.
But if you override Startup Command, you must manually start your web server after any setup scripts.
If you set Startup Command that only runs some housekeeping script and exits, Azure won’t autostart the app because it assumes you’re handling it yourself. In other words, if your custom Startup Command exits, Azure stops because there’s no running process.
Autostart? Azure looks for app.py
with app
variable in it
If Startup Command is empty and Azure Web App Service detects your app as Python and Flask powered, the default launch command is gunicorn -b 0.0.0.0:8000 app:app
, where app:app
is MODULE_NAME:VARIABLE_NAME
:
MODULE_NAME
: The Python module (file) that contains the application,app.py
by default.VARIABLE_NAME
: The WSGI application instance (a Python variable inside the module),app
by default.
For the above launch command to work, your main script needs to be app.py
and the WSGI-compatible instance in it needs to be in the variable named app
. If you import it and it has a different name where you import it from, you can alias it:
from src.Http import http as app
Here's detailed info on the Azure logic: azureossd.github.io/2023/01/30/Troubleshooting-'failed-to-find-attribute'-errors-on-Python-Linux-App-Services
You don't have to call app.run()
in your python script
Azure will handle it automatically.