How to run two bots one dyno style on Render, Railway, or any single-service PaaS deployment without paying for two separate services.
How to Run Two Python Bots on a Single Render or Railway Service
Paying for two separate hosting services just to run two related bots — a Telegram ingestion bot and a processing worker, say — adds up fast, especially on platforms that charge per service. If you've searched for how to run two bots on one dyno (or one Render/Railway service, more generally), here's how to do it properly, with each bot still getting independent crash recovery.
Table of Contents
- Why "One Service, Two Bots" Is Harder Than It Sounds
- The Naive Approach and Its Problem
- The Correct Approach: One Supervisor, Two Subprocesses
- Per-Bot Configuration
- What Happens When One Bot Crashes
- Giving Each Bot Its Own Status Endpoint
- Full Example
- Best Practices
- Common Mistakes
- FAQs
- Conclusion
Why "One Service, Two Bots" Is Harder Than It Sounds
Most PaaS platforms run exactly one process per service, launched from one start command. That's fine for a single bot, but if you have two, the naive instinct is either to pay for two separate services (doubling cost for what's logically one deployment) or to hack something together with & in a shell script — which loses proper crash recovery and process management for both.
The Naive Approach and Its Problem
python telegram_bot.py & python discord_bot.py & wait
This technically runs both, but if telegram_bot.py crashes, nothing restarts it — the & just backgrounds the process, it doesn't supervise it. You also lose the HTTP-port requirement most platforms impose, since neither script opens one on its own.
The Correct Approach: One Supervisor, Two Subprocesses
StayPresent's multi-bot support handles exactly this case — one HTTP server (satisfying the platform's port requirement) and independent supervision for each bot subprocess, all from a single staypresent.run() call:
import staypresent
staypresent.run(["telegram_bot.py", "discord_bot.py"])
This launches both scripts as separate, independently monitored processes, each with its own restart counter — a single main.py entry point, a single deployed service, a single hosting bill.
Per-Bot Configuration
If each bot needs different arguments or environment variables, use bots instead of a bare list:
import staypresent
staypresent.run(bots=[
{"file": "telegram_bot.py", "env": {"BOT_ROLE": "ingestion"}},
{"file": "discord_bot.py", "env": {"BOT_ROLE": "notifications"}},
])
What Happens When One Bot Crashes
This is the part the naive & approach can't give you: each bot is monitored on its own thread with its own restart counter. If telegram_bot.py crashes and restarts, discord_bot.py is completely unaffected and keeps running the entire time.
staypresent.run(
bots=[
{"file": "telegram_bot.py"},
{"file": "discord_bot.py"},
],
max_restarts=5,
restart_delay=2.0,
)
staypresent.run() only returns (or exits the process) once every bot has either finished or permanently failed — so the deployment as a whole stays up as long as at least one bot is still running or restarting.
Giving Each Bot Its Own Status Endpoint
Since both bots share one HTTP server, you can give each one its own status path instead of a single merged root response:
staypresent.web.json({"status": "running", "bots": 2})
staypresent.web.json({"status": "online"}, path="/telegram")
staypresent.web.json({"status": "online"}, path="/discord")
Full Example
import os
import staypresent
staypresent.web.json({"status": "running", "bots": 2})
staypresent.run(
bots=[
{"file": "telegram_bot.py", "env": {"BOT_ROLE": "ingestion"}},
{"file": "discord_bot.py", "env": {"BOT_ROLE": "notifications"}},
],
port=int(os.getenv("PORT", 8080)),
restart_on_crash=True,
max_restarts=5,
)
Deploy this as a single service on Render, Railway, Koyeb, or Heroku, with python main.py as the start command — no per-platform changes needed for the multi-bot part.
Best Practices
- Use
bots(not a barebot_filelist) whenever the two bots genuinely need different configuration — it keeps each bot's settings clearly attached to that bot. - Give each bot its own status endpoint rather than trying to merge both bots' state into a single root JSON payload.
- Keep resource usage in mind — two bots sharing one service also share that service's CPU/memory allocation, so make sure the plan you're on has enough headroom for both combined.
Common Mistakes
-
Using
subprocess.Popenmanually inside your own script to launch a second bot, duplicating whatrun()'s multi-bot support already does correctly, including crash recovery and shutdown coordination. - Assuming one bot's crash brings down the whole service. It doesn't — each bot's supervision is fully independent.
- Forgetting that both bots share the same container's resource limits. If either bot is resource-intensive, verify your plan's CPU/memory allocation covers both running simultaneously.
FAQs
Can I run more than two bots this way?
Yes — bot_file/bot_module accept a list of any length, and bots similarly supports as many entries as you need.
Does each bot get restarted independently, or does a crash restart both?
Independently — a crash and restart in one bot has zero effect on any other bot in the same run() call.
Is there a cost benefit beyond hosting fees?
Often yes — beyond the direct hosting cost of a second service, you also avoid duplicating deployment configuration, environment variable setup, and monitoring for what's logically one unit of related bots.
Conclusion
Running two bots on one dyno (or one Render/Railway service) doesn't require a fragile shell script hack. StayPresent's multi-bot support gives you one HTTP server, one deployment, and fully independent crash recovery for each bot — all from a single staypresent.run() call.
pip install staypresent[prod]
Top comments (0)