John Wick

Deploy a railway python bot that never sleeps — port binding, health checks, self-ping, and crash recovery using StayPresent.

Run Python Bots Without Sleep On Railway With StayPresent

Railway auto-detects Python projects and gets you deployed in minutes, but a railway python bot — a Discord bot, Telegram bot, or scraper — still runs into the same core issue every PaaS presents: the platform expects an HTTP-facing process, and your bot's event loop or gateway connection doesn't naturally provide one. This guide covers deploying a bot to Railway that stays online, using StayPresent to fill that gap.

Table of Contents

  1. How Railway Detects and Runs Python Apps
  2. The $PORT Requirement
  3. Setting Up StayPresent
  4. Health Checks in Railway's Dashboard
  5. Self-Ping for Inactivity-Sensitive Plans
  6. Crash Recovery
  7. Full Working Example
  8. Best Practices
  9. Common Mistakes
  10. FAQs
  11. Conclusion

How Railway Detects and Runs Python Apps

Railway auto-detects a Python project from requirements.txt (or pyproject.toml) and builds a container for it automatically via Nixpacks, without requiring a Dockerfile. It then starts your app and expects it to bind to the port Railway assigns via the PORT environment variable, so its own routing and health checks can reach it.

The $PORT Requirement

This is the same pattern seen across virtually every modern PaaS: read PORT from the environment rather than hardcoding it.

import os
import staypresent

staypresent.run(
    "bot.py",
    port=int(os.getenv("PORT", 8080)),
)

Enter fullscreen mode Exit fullscreen mode

If your bot never binds to this port at all — which is normal, expected behavior for a Discord or Telegram bot — Railway's health checks (if enabled) will report the deployment as unhealthy, even though your bot might be functioning perfectly on its own.

Setting Up StayPresent

Install the production extra so Waitress handles serving instead of Flask's development server:

pip install staypresent[prod]

Enter fullscreen mode Exit fullscreen mode

Your main.py:

import os
import staypresent

staypresent.web.json({
    "status": "running",
    "service": "my-telegram-bot",
})

staypresent.run(
    "bot.py",
    port=int(os.getenv("PORT", 8080)),
)

Enter fullscreen mode Exit fullscreen mode

Railway's start command is simply:

python main.py

Enter fullscreen mode Exit fullscreen mode

Health Checks in Railway's Dashboard

If you've enabled Railway's health check feature for a service, you can point it specifically at StayPresent's built-in /health endpoint, which always returns {"status": "ok"} — independent of whatever you've configured at your root route:

staypresent.web.json({"status": "running"})
# /health still returns {"status": "ok"} regardless

Enter fullscreen mode Exit fullscreen mode

This separates platform-level health monitoring from any custom status payload you're serving to humans or your own tooling at /.

Self-Ping for Inactivity-Sensitive Plans

Whether inactivity-based sleeping applies depends on your specific Railway plan and configuration — always check Railway's current pricing and usage documentation before assuming behavior either way. If it does apply to your deployment, staypresent.cron() generates outbound traffic against your own public URL to keep it from being treated as idle:

import staypresent

staypresent.cron(
    "https://my-app.up.railway.app",
    interval=240,
)

Enter fullscreen mode Exit fullscreen mode

As with any platform, this must target the actual public URL — pinging 0.0.0.0 or 127.0.0.1 never leaves the machine and has no effect on inactivity detection.

Crash Recovery

Bots crash for mundane reasons, and Railway won't automatically relaunch a subprocess that dies inside your container unless the container itself exits. StayPresent handles that inner layer directly:

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,
)

Enter fullscreen mode Exit fullscreen mode

If restarts are ultimately exhausted, StayPresent exits the whole process with the bot's original exit code — letting Railway's own deployment restart policy act as a final backstop.

Full Working Example

import os
import staypresent

staypresent.web.json({"status": "running"})

staypresent.cron("https://my-app.up.railway.app", interval=240)

staypresent.run(
    "bot.py",
    port=int(os.getenv("PORT", 8080)),
    threads=8,
    restart_on_crash=True,
    max_restarts=5,
)

Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Always read PORT dynamically — Railway assigns it per-deployment and it can differ between environments.
  • Point any configured health check at /health rather than / if your root route serves something custom.
  • Keep the prod extra installed for any deployment receiving real traffic, not just keep-alive pings.

Common Mistakes

  • Hardcoding port=8080 instead of reading os.getenv("PORT", 8080) — this can work in some configurations and silently fail in others depending on how Railway is set up.
  • Assuming Railway's sleep behavior is identical to Render's. Policies differ between platforms and change over time — verify current behavior in Railway's own docs rather than assuming.
  • Skipping crash recovery because "Railway restarts crashed deployments anyway." Railway's own restart policy operates at the container level; a bot subprocess crashing inside a still-running container is a separate failure mode StayPresent handles directly.

FAQs

Does Railway require a Dockerfile?
No — Railway can auto-detect and build Python projects via Nixpacks without one, though a Dockerfile is also supported if you prefer explicit control.

Can I reuse the same main.py from a Render deployment?
Yes — since both platforms follow the same $PORT convention, the identical entry point typically works unchanged across both.

Do I need staypresent.cron() on Railway?
Only if your specific plan/configuration is subject to inactivity-based sleeping — check Railway's current documentation for your plan before deciding.

Conclusion

A railway python bot stays online the same way a bot on any modern PaaS does: bind to the assigned port, expose a dedicated health endpoint, and let StayPresent supervise the bot process itself. The setup is close to identical across Railway, Render, Koyeb, and Heroku, which is exactly what makes StayPresent a one-time investment rather than per-platform boilerplate.

pip install staypresent[prod]

Enter fullscreen mode Exit fullscreen mode