Deployment¶
A mojogram bot is a single native process. Two ways to run it in production: long-polling, which needs nothing but the binary and an outbound connection, or webhooks, which need a public HTTPS endpoint.
Polling¶
The simplest deploy. No inbound ports, no TLS, no proxy. The process long-polls
Telegram and handles updates in its own loop. Run it under whatever keeps a
process alive: a systemd unit, a restart: always container, supervisord.
export BOT_TOKEN="123456:your-token"
pixi run mojo run -I . yourbot.mojo
If one process can't keep up, polling doesn't shard cleanly (two pollers fight over the same offset), so for scale switch to webhooks.
Webhooks behind nginx¶
The webhook server handles one connection at a time, since Mojo 1.0 has no
threads. You get throughput by running several worker processes behind a proxy
that terminates TLS and round-robins across them. The deploy/ folder has a
working stack for exactly this.
flowchart LR
TG([Telegram]) -->|HTTPS POST| NG[nginx<br/>TLS, round-robin]
NG --> B1[worker :8080]
NG --> B2[worker :8080]
NG --> B3[worker :8080]
Three files drive it:
deploy/Dockerfilebuilds an Ubuntu image, installs pixi and the MAX/Mojo toolchain, copies the package, and runsexamples/webhook_bot.mojo. The first build pulls the toolchain, which is large (about a GB), so expect a slow initial build.deploy/nginx.conflistens on 443, terminates TLS, and proxies to thebotservice. Docker DNS resolvesbot:8080to every replica, so nginx round-robins for free.deploy/docker-compose.ymlwires the two together. The bot service has no host port; only nginx can reach it.
Bring it up¶
# put your TLS cert where nginx expects it
mkdir -p deploy/certs
cp fullchain.pem privkey.pem deploy/certs/
export BOT_TOKEN="123456:your-token"
docker compose -f deploy/docker-compose.yml up --build --scale bot=3
--scale bot=3 runs three worker processes. Bump the number to match your load.
Register the webhook once¶
Telegram needs to know where to POST. Point it at your domain, and set a secret token so only Telegram's requests are accepted:
curl "https://api.telegram.org/bot$BOT_TOKEN/setWebhook?\
url=https://YOUR_DOMAIN&secret_token=YOUR_SECRET"
Then construct the server with the same secret. It compares the value against
Telegram's X-Telegram-Bot-Api-Secret-Token header and drops anything that
doesn't match:
var srv = WebhookServer(Bot(token), 8080, secret_token="YOUR_SECRET")
while True:
handle(srv.context(srv.next()))
Telegram only POSTs to HTTPS, and it wants a valid certificate. Use a real one or Let's Encrypt; a self-signed cert won't be accepted in production.
Reproducible builds¶
Mojo is pre-1.0 and the toolchain moves between nightlies. Commit your
pixi.lock (or uv.lock) so a deploy six weeks from now resolves the same
compiler you tested against. Without a lockfile a rebuild can pull a newer
nightly that breaks the build.
Checking curl at startup¶
The whole transport is the system curl. The Docker image installs it, but if
you deploy somewhere hand-rolled, call curl_available() once at boot so a
missing binary fails loudly instead of on the first API call:
from mojogram.http import curl_available
if not curl_available():
raise Error("curl is not on PATH; mojogram needs it for HTTP")