How to Deploy Discord Bots Like a Professional
Writing a Discord bot with discord.py is the easy part. The part that actually separates a hobby project from something people can rely on is deployment: does it survive a crash? Does it come back online automatically? Can you actually tell when something's wrong? This guide walks through how to deploy discord bot python projects properly on Railway, Render, or Koyeb, using StayPresent to handle process supervision, health checks, and recovery.
Table of Contents
- Why Discord.py Deployment Is Different
- Choosing a Platform: Railway vs Render vs Koyeb
- Structuring Your Project
- Setting Up Crash Recovery
- Logging Without Chaos
- Health Checks and Monitoring
- Full Deployment Example
- Best Practices
- Common Mistakes
- FAQs
- Conclusion
Why Discord.py Deployment Is Different
A discord.py bot runs bot.run(token), which blocks forever inside a WebSocket connection to Discord's gateway. It never opens an HTTP port on its own, which means most cloud platforms — which health-check via HTTP — will treat it as an unresponsive web service unless you add something to answer that check.
That "something" is a small HTTP server running in parallel with your bot process, which is exactly what StayPresent provides.
Choosing a Platform: Railway vs Render vs Koyeb
All three platforms are commonly used for discord.py bots and share the same core requirement: a service listening on a PORT environment variable.
-
Railway auto-detects Python projects and injects
$PORT. -
Render requires the same
$PORTpattern and free-tier services sleep after inactivity. - Koyeb similarly expects an HTTP-facing process for its health checks.
Because the underlying requirement is identical across all three, the same StayPresent setup works on any of them with no changes.
Structuring Your Project
A clean structure separates your bot's logic from the entry point that manages it:
project/
├── main.py # StayPresent entry point
├── bot.py # discord.py bot logic
├── requirements.txt
bot.py contains your normal discord.py code:
import discord
from discord.ext import commands
import os
intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
bot.run(os.environ["DISCORD_TOKEN"])
main.py is the entry point Railway, Render, or Koyeb actually start:
import os
import staypresent
staypresent.web.json({"status": "online", "bot": "discord"})
staypresent.run(
"bot.py",
port=int(os.getenv("PORT", 8080)),
)
Setting Up Crash Recovery
Discord bots crash for mundane reasons — a rate limit, a bad API response, an unhandled exception in a command handler. Rather than letting one bad command take the whole bot offline, configure automatic restarts:
staypresent.run(
"bot.py",
port=int(os.getenv("PORT", 8080)),
restart_on_crash=True,
max_restarts=5,
restart_delay=2.0,
restart_reset_after=60.0,
)
If the bot crashes repeatedly within a short window, max_restarts caps the attempts so you don't end up in a silent infinite crash loop. Once max_restarts is exhausted, StayPresent exits with the bot's original exit code, letting the hosting platform's own recovery policy take over as a final safety net.
Logging Without Chaos
StayPresent keeps its own logs under a dedicated "staypresent" logger instead of touching Python's root logger, so it won't interfere with whatever logging setup your bot already has:
import logging
logging.getLogger("staypresent").setLevel(logging.INFO)
This means you can turn StayPresent's logs up when debugging a deployment issue, and back down once things are stable, without affecting your discord.py logs at all.
Health Checks and Monitoring
StayPresent exposes /health automatically, returning {"status": "ok"}. Point your platform's health check — or an external uptime monitor — at that path rather than /, which you're free to use for a human-readable status page instead:
staypresent.web.html("templates/status.html")
Full Deployment Example
import os
import logging
import staypresent
logging.getLogger("staypresent").setLevel(logging.INFO)
staypresent.web.json({
"status": "online",
"bot": "discord",
})
staypresent.run(
"bot.py",
port=int(os.getenv("PORT", 8080)),
threads=8,
restart_on_crash=True,
max_restarts=5,
restart_delay=2.0,
)
Best Practices
- Keep your Discord token in an environment variable, never hardcoded, and pass extra secrets through
env=ifbot.pyneeds anything beyond what's already in the environment. - Set
restart_reset_aftergenerously (60 seconds is a reasonable default) so a bot that's genuinely stable isn't penalized for one unlucky crash hours later. - Use
bot_argsfor feature flags instead of environment variables when a setting only matters for that one run — pass it as a list, e.g.bot_args=["--sync-commands"].
Common Mistakes
-
Blocking on
bot.run()without any restart supervision, so a single unhandled exception takes the bot offline until someone manually redeploys. - Running the bot's HTTP server and the bot itself as two separate Render services, doubling hosting costs for something StayPresent handles in a single process pair.
-
Ignoring the difference between exit code 0 and a crash. StayPresent treats a clean
sys.exit(0)as intentional and won't restart it — useful for a bot command that deliberately shuts itself down.
FAQs
Does this work with py-cord or other discord.py forks?
Yes — StayPresent doesn't care what's inside bot.py; it just runs the script as a subprocess.
Can I run multiple bots this way?
Each staypresent.run() call manages one bot script per process. For multiple bots, run separate deployments or manage multiple subprocesses inside bot.py itself.
Is this overkill for a small hobby bot?
Not really — the entire setup is a handful of lines, and it saves you from silent downtime the first time your bot hits an unexpected error at 2 a.m.
Conclusion
Deploying a discord.py bot professionally isn't about writing more code — it's about wrapping the bot you already have with proper process supervision, health checks, and logging isolation. StayPresent handles all three in a single staypresent.run() call, so you can focus on bot features instead of babysitting deployments.
pip install staypresent[prod]
Top comments (0)