Run a Telegram bot
A long-polling bot is a background worker: it asks Telegram for updates and does not need a public HTTP endpoint. Run it with containerPort=0 and one replica per bot token.
Test status: seven local tests with mocked Telegram responses pass. We have not checked this guide with a real bot in the cloud. Use a separate test bot and complete the message and restart checks below before relying on it. See scenario test results.
Before you start
Create a bot with BotFather and keep its token private. You need Python 3.13, Lizard CLI, and a project you can deploy to. A bot using long polling must not have an active webhook; check its current configuration before changing how it receives updates.
The example files include worker.py, a Dockerfile, and local unit tests. The tests do not call Telegram. The echo handler stores its offset in memory; it is not an exactly-once processing system.
Check locally
From the example directory:
python3 -m unittest -vThe checks cover text replies, ignored updates, empty polls, and failed sends. Run the bot locally with TELEGRAM_BOT_TOKEN set only if you intend to send replies. Stop that local process before starting the hosted worker.
Deploy as a worker
lizard init --name telegram-bot
lizard add --service botSign in with lizard login if a command reports that authentication is required. Set TELEGRAM_BOT_TOKEN on the bot service before deploying. You can use the dashboard’s variables editor, or put the value in a local .env file and import it:
lizard secrets import --service bot < .env
lizard run --service bot -- python3 check_bot.py
lizard up --service bot --port 0The preflight checks the token with getMe and refuses a bot with an active webhook. It does not remove or change that webhook. A second polling process can still conflict: stop any local copy before deployment.
The example excludes .env* from uploads. Keep the token out of source files and screenshots. On macOS with Lizard CLI 0.3.92, prefix the upload command with COPYFILE_DISABLE=1. Read the final deployment event and logs even if the command exits successfully.
For an existing service, set worker mode explicitly:
lizard service set bot --set containerPort=0After changing worker mode on a running service, apply it with lizard redeploy --service bot. Check deployment events before requesting another build. Worker mode skips HTTP port checks and the load-balancer route; do not add a dummy web server just to make this process look like an HTTP app.
Verify the result
lizard ps --json
lizard logs --service bot --json
lizard events --jsonThe log should show Telegram worker started. Send a message to your bot and check for one echo reply. Then stop and restart the worker during an approved test window and confirm it reconnects. A worker that is marked running still needs this application-level check.
Common failures
| Symptom | Check |
|---|---|
| Process exits immediately | Set TELEGRAM_BOT_TOKEN on the consuming service. |
| HTTP health check never passes | Worker mode must use containerPort=0. |
| Updates stop or conflict errors appear | Only one polling process should use this token; check for a local process, another replica, or an active webhook. |
| Repeated replies after a failure | The demo does not persist offsets or deduplicate update IDs. Add durable idempotency before handling irreversible actions. |
| Frequent retries | Check network access, token validity, Telegram limits, and your handler. Do not print request URLs: they contain the token. |
State and cost
For durable bot state, connect Managed Postgres. Store processed update IDs and make handlers safe to retry. Long polling can keep the worker active between messages; budget from pricing and observed resource use, not from message count alone.
Related guides
See scenario test results for checked versions, cloud results and remaining limits.
Updated