GuidesRun a Telegram bot

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 -v

The 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 bot

Sign 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 0

The 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=0

After 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 --json

The 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

SymptomCheck
Process exits immediatelySet TELEGRAM_BOT_TOKEN on the consuming service.
HTTP health check never passesWorker mode must use containerPort=0.
Updates stop or conflict errors appearOnly one polling process should use this token; check for a local process, another replica, or an active webhook.
Repeated replies after a failureThe demo does not persist offsets or deduplicate update IDs. Add durable idempotency before handling irreversible actions.
Frequent retriesCheck 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.

See scenario test results for checked versions, cloud results and remaining limits.

Updated