<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Wick</title>
    <description>The latest articles on DEV Community by John Wick (@codenamew).</description>
    <link>https://dev.to/codenamew</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4043176%2Fc4e85b98-139d-4c0c-a3be-1988390f4d72.jpg</url>
      <title>DEV Community: John Wick</title>
      <link>https://dev.to/codenamew</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codenamew"/>
    <language>en</language>
    <item>
      <title>How to Run a Python Bot on Fly.io Without It Sleeping</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:13:41 +0000</pubDate>
      <link>https://dev.to/codenamew/how-to-run-a-python-bot-on-flyio-without-it-sleeping-1ag0</link>
      <guid>https://dev.to/codenamew/how-to-run-a-python-bot-on-flyio-without-it-sleeping-1ag0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tutorial for deploying a python bot on Fly.io with proper health checks and no unwanted machine suspension, using StayPresent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to Run a Python Bot on Fly.io Without It Sleeping
&lt;/h2&gt;

&lt;p&gt;Fly.io deploys apps as lightweight VMs ("Machines") close to your users, and its free allowance is a popular choice for small bots. It also has a specific behavior worth understanding before you deploy: Fly can automatically stop machines when they're idle, and restart them on the next incoming request — great for a web app, potentially disruptive for a bot that needs to stay connected continuously. This tutorial covers deploying a Python bot on Fly.io correctly, avoiding both the standard HTTP-port issue and Fly's specific auto-stop behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;How Fly.io's Health Checks Work&lt;/li&gt;
&lt;li&gt;Fly's Auto-Stop/Auto-Start Behavior&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Wrap Your Bot's Entry Point&lt;/li&gt;
&lt;li&gt;Step 3: Configure &lt;code&gt;fly.toml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Step 4: Disable Auto-Stop for a Bot Machine&lt;/li&gt;
&lt;li&gt;Step 5: Deploy&lt;/li&gt;
&lt;li&gt;Verifying It's Actually Staying Up&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How Fly.io's Health Checks Work
&lt;/h2&gt;

&lt;p&gt;Like most modern platforms, Fly can be configured to check your service over HTTP and restart it if it stops responding. A Discord or Telegram bot doesn't open an HTTP port on its own, so without something bridging that gap, a configured health check has nothing to actually verify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fly's Auto-Stop/Auto-Start Behavior
&lt;/h2&gt;

&lt;p&gt;This is the part that's specific to Fly and worth calling out: Fly Machines can be configured to automatically stop when idle and start again on the next request — a cost-saving feature that makes sense for a typical web app, but works against a bot that's supposed to maintain a continuous connection to Discord or Telegram in the background, with no incoming HTTP requests driving that connection at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# requirements.txt
staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Wrap Your Bot's Entry Point
&lt;/h2&gt;

&lt;p&gt;Your bot logic in &lt;code&gt;bot.py&lt;/code&gt; stays unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Configure &lt;code&gt;fly.toml&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-bot"&lt;/span&gt;

&lt;span class="nn"&gt;[env]&lt;/span&gt;
  &lt;span class="py"&gt;PORT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt;

&lt;span class="nn"&gt;[[services]]&lt;/span&gt;
  &lt;span class="py"&gt;internal_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
  &lt;span class="py"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tcp"&lt;/span&gt;

  &lt;span class="nn"&gt;[[services.ports]]&lt;/span&gt;
    &lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="py"&gt;handlers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nn"&gt;[[services.tcp_checks]]&lt;/span&gt;
    &lt;span class="py"&gt;interval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"15s"&lt;/span&gt;
    &lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2s"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives Fly the HTTP surface it's looking for to consider your machine healthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Disable Auto-Stop for a Bot Machine
&lt;/h2&gt;

&lt;p&gt;This is the step specific to Fly that's easy to miss. In &lt;code&gt;fly.toml&lt;/code&gt;, make sure auto-stop isn't configured against your bot service — a bot needs to stay running continuously, not spin down between requests the way a typical stateless web app would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[services]]&lt;/span&gt;
  &lt;span class="py"&gt;internal_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
  &lt;span class="py"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tcp"&lt;/span&gt;
  &lt;span class="py"&gt;auto_stop_machines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="py"&gt;auto_start_machines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="py"&gt;min_machines_running&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;min_machines_running = 1&lt;/code&gt; specifically ensures Fly always keeps at least one instance of your bot running, rather than scaling it down to zero during a quiet period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Deploy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fly deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verifying It's Actually Staying Up
&lt;/h2&gt;

&lt;p&gt;Check &lt;code&gt;fly status&lt;/code&gt; to confirm your machine is showing as &lt;code&gt;started&lt;/code&gt; consistently, not cycling between &lt;code&gt;stopped&lt;/code&gt; and &lt;code&gt;started&lt;/code&gt;. You can also hit &lt;code&gt;/health&lt;/code&gt; directly via your app's Fly URL to confirm StayPresent's HTTP server is responding. On a recent StayPresent version, &lt;code&gt;/status&lt;/code&gt; gives you an ongoing uptime view, which is the clearest way to confirm the bot is staying connected over time rather than restarting repeatedly.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need &lt;code&gt;staypresent.cron()&lt;/code&gt; on Fly.io too?&lt;/strong&gt;&lt;br&gt;
Generally no, if &lt;code&gt;auto_stop_machines = false&lt;/code&gt; and &lt;code&gt;min_machines_running = 1&lt;/code&gt; are set correctly — those settings are what actually control Fly's idle behavior, unlike platforms where self-pinging is the main lever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work the same for a Telegram bot as a Discord bot?&lt;/strong&gt;&lt;br&gt;
Yes — the fix addresses the HTTP port and auto-stop behavior generically, regardless of which bot library or platform API your bot talks to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will &lt;code&gt;min_machines_running = 1&lt;/code&gt; cost more than the free tier allows?&lt;/strong&gt;&lt;br&gt;
Fly's specific pricing and free allowances change over time — check Fly's current pricing page for your usage before assuming either way.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Running a Python bot reliably on Fly.io means solving two separate things: giving Fly's health check an HTTP port to verify (via &lt;code&gt;staypresent.run()&lt;/code&gt;), and explicitly disabling Fly's auto-stop behavior so your bot isn't treated like a scale-to-zero web app. Both together keep your bot connected and online continuously, the way a bot actually needs to run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Fixing "Address Already in Use" When Redeploying a Python Bot</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:13:17 +0000</pubDate>
      <link>https://dev.to/codenamew/fixing-address-already-in-use-when-redeploying-a-python-bot-l4b</link>
      <guid>https://dev.to/codenamew/fixing-address-already-in-use-when-redeploying-a-python-bot-l4b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tutorial for diagnosing and fixing OSError: Address already in use in a python bot deployment — common causes and the actual fixes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fixing "Address Already in Use" When Redeploying a Python Bot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OSError: [Errno 98] Address already in use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error shows up right when you least want it — usually right after a redeploy, right when you're trying to confirm a fix actually worked. Here's what it actually means, the most common causes in a bot deployment specifically, and how to resolve each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What This Error Actually Means&lt;/li&gt;
&lt;li&gt;Cause 1: A Previous Process Didn't Fully Exit&lt;/li&gt;
&lt;li&gt;Cause 2: Calling a Server-Starting Function Twice&lt;/li&gt;
&lt;li&gt;Cause 3: Two Services Configured for the Same Port&lt;/li&gt;
&lt;li&gt;Diagnosing Which Cause You Have&lt;/li&gt;
&lt;li&gt;Fix: Killing a Leftover Process Locally&lt;/li&gt;
&lt;li&gt;Fix: Checking for a Duplicate &lt;code&gt;run()&lt;/code&gt; Call&lt;/li&gt;
&lt;li&gt;Fix: Platform-Level Port Conflicts&lt;/li&gt;
&lt;li&gt;Preventing This Going Forward&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What This Error Actually Means
&lt;/h2&gt;

&lt;p&gt;At the OS level, only one process can bind to a given &lt;code&gt;(host, port)&lt;/code&gt; combination at a time. This error means something is already listening on the port your bot is trying to use — whether that's a leftover process from a previous run, a genuine second server trying to start in the same process, or (less commonly) a platform-level conflict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 1: A Previous Process Didn't Fully Exit
&lt;/h2&gt;

&lt;p&gt;The most common cause during local development: you stopped your bot with &lt;code&gt;Ctrl+C&lt;/code&gt;, but the OS hasn't fully released the port yet, or a child process from a previous run is still alive in the background.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;span class="c"&gt;# OSError: [Errno 98] Address already in use&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cause 2: Calling a Server-Starting Function Twice
&lt;/h2&gt;

&lt;p&gt;If you're using StayPresent and see this error, it's worth checking whether &lt;code&gt;staypresent.run()&lt;/code&gt; is being called more than once in the same process — accidentally, in a script that imports itself, or from two different code paths. As of recent StayPresent versions, this specific case is caught with a clearer &lt;code&gt;RuntimeError&lt;/code&gt; explaining the actual issue &lt;em&gt;before&lt;/em&gt; it ever reaches the OS-level bind attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# RuntimeError: run() already called in this process
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're seeing the raw &lt;code&gt;OSError&lt;/code&gt; instead of that clearer message, you're likely on an older version, or the second "server start" is coming from somewhere other than StayPresent's own &lt;code&gt;run()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 3: Two Services Configured for the Same Port
&lt;/h2&gt;

&lt;p&gt;On a hosting platform, this can happen if two separate deployments (or two processes within one deployment, like a leftover worker from a bad redeploy) are both configured to bind the same port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnosing Which Cause You Have
&lt;/h2&gt;

&lt;p&gt;Locally, check what's actually using the port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS/Linux&lt;/span&gt;
lsof &lt;span class="nt"&gt;-i&lt;/span&gt; :8080

&lt;span class="c"&gt;# Windows&lt;/span&gt;
netstat &lt;span class="nt"&gt;-ano&lt;/span&gt; | findstr :8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something unexpected shows up, that's your leftover process. If nothing shows up but the error persists, the cause is more likely a duplicate &lt;code&gt;run()&lt;/code&gt; call within your own code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix: Killing a Leftover Process Locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS/Linux — replace &amp;lt;PID&amp;gt; with the process ID from lsof&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; &amp;lt;PID&amp;gt;

&lt;span class="c"&gt;# Windows — replace &amp;lt;PID&amp;gt; with the PID from netstat&lt;/span&gt;
taskkill /PID &amp;lt;PID&amp;gt; /F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fix: Checking for a Duplicate &lt;code&gt;run()&lt;/code&gt; Call
&lt;/h2&gt;

&lt;p&gt;Search your codebase for every call to &lt;code&gt;staypresent.run()&lt;/code&gt; (or any raw &lt;code&gt;app.run()&lt;/code&gt;/&lt;code&gt;serve()&lt;/code&gt; call, if you're not using StayPresent). A common trigger is an entry-point script being imported by something else, causing top-level code — including a &lt;code&gt;run()&lt;/code&gt; call — to execute a second time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Guard against accidental double-execution on import
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fix: Platform-Level Port Conflicts
&lt;/h2&gt;

&lt;p&gt;If this only happens on your hosting platform and not locally, check for a leftover deployment or process still bound to the same port from a previous, incomplete redeploy. Most platforms fully tear down the old process before starting the new one, but a stuck or hung previous deployment can occasionally linger — a manual restart of the service (not just a redeploy) often clears this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing This Going Forward
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Always guard your entry point with &lt;code&gt;if __name__ == "__main__":&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read the port from the environment (&lt;code&gt;os.getenv("PORT", 8080)&lt;/code&gt;) rather than hardcoding it, so local testing and deployment don't collide over the same fixed value.&lt;/li&gt;
&lt;li&gt;If you're on an older StayPresent version and want the clearer duplicate-call error instead of the raw OS error, upgrading is worth it specifically for that improved diagnostic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this mean my bot's code is broken?&lt;/strong&gt;&lt;br&gt;
Not necessarily — it's frequently an environment issue (a leftover process) rather than a logic bug, especially during local development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can two bots share the same port?&lt;/strong&gt;&lt;br&gt;
No — each service needs its own distinct port. If you're running multiple bots, they should share one StayPresent deployment (one port, multiple supervised bot processes) rather than each trying to bind the same port independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does StayPresent raise &lt;code&gt;RuntimeError&lt;/code&gt; instead of letting this OS error happen?&lt;/strong&gt;&lt;br&gt;
A duplicate &lt;code&gt;run()&lt;/code&gt; call is a common, easy mistake, and the raw &lt;code&gt;OSError: Address already in use&lt;/code&gt; gives no indication of the actual cause. Catching it earlier with a clearer message saves the debugging time this tutorial is otherwise about.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;"Address already in use" almost always traces back to one of three things: a leftover process from a previous run, an accidental duplicate call to whatever starts your server, or a platform-level conflict from an incomplete redeploy. Checking for a lingering process locally, guarding your entry point against double execution, and reading your port from the environment resolves the overwhelming majority of cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>How to Give Your Bot a Public Uptime Page Your Users Can Check</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:12:52 +0000</pubDate>
      <link>https://dev.to/codenamew/how-to-give-your-bot-a-public-uptime-page-your-users-can-check-4k1</link>
      <guid>https://dev.to/codenamew/how-to-give-your-bot-a-public-uptime-page-your-users-can-check-4k1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tutorial for adding a public-facing uptime page to a Discord or Telegram bot, so users can check status themselves instead of asking you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to Give Your Bot a Public Uptime Page Your Users Can Check
&lt;/h2&gt;

&lt;p&gt;"Is the bot down?" is one of the most common messages a bot maintainer gets, and answering it manually — every time, in every server, for every person who asks — doesn't scale past a handful of users. This tutorial covers giving your bot a public page people can check themselves, with real uptime history instead of just your word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why a Status Page Beats Answering DMs&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Confirm the Default Page Is Already Live&lt;/li&gt;
&lt;li&gt;Step 3: Customize the Page for Your Bot&lt;/li&gt;
&lt;li&gt;Step 4: Share the Link Where Users Will Find It&lt;/li&gt;
&lt;li&gt;Step 5 (Optional): Decide What's Public vs Admin-Only&lt;/li&gt;
&lt;li&gt;Step 6 (Optional): Name Multiple Bots Clearly&lt;/li&gt;
&lt;li&gt;Verifying What Users Actually See&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why a Status Page Beats Answering DMs
&lt;/h2&gt;

&lt;p&gt;Every time someone asks "is the bot down" and you have to manually check and reply, that's a small tax on your time — and if you're asleep, at work, or just not looking at Discord, the honest answer they get is silence, which reads as worse than an actual outage. A public page answers the question the moment it's asked, without you doing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Confirm the Default Page Is Already Live
&lt;/h2&gt;

&lt;p&gt;If you're already running your bot through StayPresent, you likely already have this — a status page is served automatically at &lt;code&gt;/status&lt;/code&gt; the moment &lt;code&gt;staypresent.run()&lt;/code&gt; starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# https://your-app.example.com/status is already live
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No extra code required for the basic version — uptime percentages, current status, and incident history are all generated automatically from data StayPresent already tracks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Customize the Page for Your Bot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyBot Status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;copyright&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyBot Team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;footer_links&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Support Server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://discord.gg/yourinvite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Share the Link Where Users Will Find It
&lt;/h2&gt;

&lt;p&gt;Once it's live, the actual work is just making sure people know it exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pin it in your support channel.&lt;/li&gt;
&lt;li&gt;Add it to your bot's &lt;code&gt;/about&lt;/code&gt; or &lt;code&gt;/help&lt;/code&gt; command output.&lt;/li&gt;
&lt;li&gt;Link it in your bot's Discord "About Me" or bio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The page itself requires no maintenance after this — it updates live, automatically, with no manual step on your end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 (Optional): Decide What's Public vs Admin-Only
&lt;/h2&gt;

&lt;p&gt;By default, the page shows overall status, uptime, and plain-language incident descriptions to everyone — appropriate for a public audience. Deeper technical detail (exit codes, a recent log tail) is gated behind an admin key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STATUS_ADMIN_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set your own key so only you (not a randomly generated, log-only key) can access that deeper view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 (Optional): Name Multiple Bots Clearly
&lt;/h2&gt;

&lt;p&gt;If your project actually runs several bots behind the scenes, label each one so the public page makes sense to someone who isn't familiar with your file structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyBot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Backup Bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verifying What Users Actually See
&lt;/h2&gt;

&lt;p&gt;Open the page in a private/incognito browser window (so you're seeing it the way a logged-out visitor would) and confirm: the overall status is clear, uptime numbers are showing, and nothing sensitive (internal exit codes, log content) is visible without logging in as admin.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this require my bot's own database?&lt;/strong&gt;&lt;br&gt;
No — everything on the page comes from data StayPresent already tracks for its own crash recovery; there's nothing separate to store or maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can users see exactly why the bot crashed?&lt;/strong&gt;&lt;br&gt;
Not by default — the public view shows that an incident happened and its plain-language status, not raw exit codes or logs. That detail is reserved for the admin view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will this slow my bot down?&lt;/strong&gt;&lt;br&gt;
No — serving the status page is a lightweight part of the same HTTP server StayPresent already runs for your platform's health check.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A public uptime page turns "is the bot down?" from a question only you can answer into something users can check themselves, any time, with real data instead of a guess. With StayPresent, the basic version needs no setup at all — &lt;code&gt;/status&lt;/code&gt; is live the moment your bot is — and a few optional lines let you brand it and control exactly what's public.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Add Crash Recovery to Any Python Script in One Line</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:10:44 +0000</pubDate>
      <link>https://dev.to/codenamew/add-crash-recovery-to-any-python-script-in-one-line-51p5</link>
      <guid>https://dev.to/codenamew/add-crash-recovery-to-any-python-script-in-one-line-51p5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tutorial for adding automatic crash recovery to an existing python script with zero code changes to the script itself, using StayPresent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Add Crash Recovery to Any Python Script in One Line
&lt;/h2&gt;

&lt;p&gt;If you have a Python script that runs continuously — a bot, a scraper, a queue worker — and it currently just dies and stays dead the moment something throws an unhandled exception, this tutorial covers the fastest way to fix that: automatic restarts, with a sensible ceiling so a genuine crash loop doesn't run forever, and without touching a single line of the script itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why Python Doesn't Restart Itself&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Wrap Your Script (No Code Changes)&lt;/li&gt;
&lt;li&gt;Step 3: Confirm It Restarts on a Real Crash&lt;/li&gt;
&lt;li&gt;Step 4: Tune the Restart Behavior&lt;/li&gt;
&lt;li&gt;Step 5 (Optional): Prevent Endless Crash Loops&lt;/li&gt;
&lt;li&gt;Step 6 (Optional): Reset the Counter After Stability&lt;/li&gt;
&lt;li&gt;What Counts as a "Clean Exit" (No Restart)&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Python Doesn't Restart Itself
&lt;/h2&gt;

&lt;p&gt;Once an unhandled exception reaches the top of a Python script, the interpreter exits — nothing built into the language relaunches it. For a one-off script that's exactly the right behavior. For something meant to run indefinitely, it means a single uncaught error ends its uptime until a human manually restarts it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Wrap Your Script (No Code Changes)
&lt;/h2&gt;

&lt;p&gt;Say your script is &lt;code&gt;worker.py&lt;/code&gt;. It stays completely untouched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;web_server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;web_server=False&lt;/code&gt; is the key detail here — it gives you crash recovery with &lt;strong&gt;no HTTP server at all&lt;/strong&gt;, which is exactly what you want for a script that doesn't need to satisfy a hosting platform's port requirement (a local script, a VPS process with no platform-level health check, or anything you're deliberately keeping off the network).&lt;/p&gt;

&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Confirm It Restarts on a Real Crash
&lt;/h2&gt;

&lt;p&gt;Test it deliberately — add a line that raises an exception partway through &lt;code&gt;worker.py&lt;/code&gt;, run &lt;code&gt;main.py&lt;/code&gt;, and watch the console. You should see the crash logged, a short delay, and the script relaunching automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Tune the Restart Behavior
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;web_server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;restart_on_crash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_restarts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;restart_delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max_restarts&lt;/code&gt; caps &lt;em&gt;consecutive&lt;/em&gt; crashes — this is what stops a genuine crash loop (a missing config value, a bad credential) from restarting forever.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restart_delay&lt;/code&gt; adds a pause before each relaunch, useful if the crash was caused by something transient like a rate limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5 (Optional): Prevent Endless Crash Loops
&lt;/h2&gt;

&lt;p&gt;If your script hits &lt;code&gt;max_restarts&lt;/code&gt;, &lt;code&gt;staypresent.run()&lt;/code&gt; exits the whole process with the script's original exit code — this matters if you're also running under Docker or systemd, since it lets that outer layer's own restart policy act as a final backstop instead of the process quietly staying dead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 (Optional): Reset the Counter After Stability
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;web_server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_restarts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;restart_reset_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your script stays up for at least 60 seconds after a restart, its crash counter resets to zero — so a script that crashes once every few days doesn't slowly creep toward its restart ceiling from unrelated, infrequent failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Counts as a "Clean Exit" (No Restart)
&lt;/h2&gt;

&lt;p&gt;An exit code of &lt;code&gt;0&lt;/code&gt; is treated as intentional and never triggers a restart — useful if your script has legitimate logic to shut itself down on purpose (a scheduled one-time job, a manual stop condition):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="c1"&gt;# inside worker.py, when the script should stop on purpose
&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# StayPresent will NOT restart this
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything else — an unhandled exception, &lt;code&gt;sys.exit(1)&lt;/code&gt;, a segfault — is treated as a crash and restarted according to your configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need an HTTP server for this to work?&lt;/strong&gt;&lt;br&gt;
No — &lt;code&gt;web_server=False&lt;/code&gt; gives you crash recovery with zero HTTP surface, which is the right choice for a script that doesn't need to satisfy a platform health check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work for scripts with multi-line entry points, not just simple loops?&lt;/strong&gt;&lt;br&gt;
Yes — &lt;code&gt;staypresent.run()&lt;/code&gt; launches your script as a subprocess exactly the way &lt;code&gt;python worker.py&lt;/code&gt; would from the command line; it doesn't care what's inside it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I add crash recovery to more than one script at once?&lt;/strong&gt;&lt;br&gt;
Yes — pass a list: &lt;code&gt;staypresent.run(["worker1.py", "worker2.py"], web_server=False)&lt;/code&gt;, each supervised independently.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Adding real crash recovery to an existing Python script doesn't require rewriting anything inside it — &lt;code&gt;staypresent.run("worker.py", web_server=False)&lt;/code&gt; gives you automatic restarts, a sensible ceiling against crash loops, and a counter that resets after genuine stability, all without a single line of change to the script being supervised.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>How to Detect a Frozen Python Bot (Not Just a Crashed One)</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:10:18 +0000</pubDate>
      <link>https://dev.to/codenamew/how-to-detect-a-frozen-python-bot-not-just-a-crashed-one-1919</link>
      <guid>https://dev.to/codenamew/how-to-detect-a-frozen-python-bot-not-just-a-crashed-one-1919</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tutorial on detecting a python bot that's deadlocked or stuck without crashing, using heartbeat monitoring — with a working code example.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to Detect a Frozen Python Bot (Not Just a Crashed One)
&lt;/h2&gt;

&lt;p&gt;Most reliability setups only catch one kind of failure: the process exiting. A bot that's deadlocked, stuck on an API call with no timeout, or spinning in a broken loop never exits at all — it just sits there, "running" by every normal check, doing nothing useful. This tutorial covers detecting that specific failure mode, which standard crash recovery is structurally blind to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why Crash Detection Misses This&lt;/li&gt;
&lt;li&gt;What a Frozen Bot Actually Looks Like From Outside&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Add a Heartbeat Call to Your Bot's Loop&lt;/li&gt;
&lt;li&gt;Step 3: Set a Heartbeat Timeout&lt;/li&gt;
&lt;li&gt;Step 4: Choosing the Right Timeout Value&lt;/li&gt;
&lt;li&gt;Step 5: Confirming It Works&lt;/li&gt;
&lt;li&gt;What Happens When a Hang Is Detected&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Crash Detection Misses This
&lt;/h2&gt;

&lt;p&gt;Standard restart logic reacts to a non-zero exit code. A deadlock or an infinite loop with a broken exit condition never produces one — the process is technically alive the entire time it's stuck, so nothing about exit-code-based monitoring ever triggers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Frozen Bot Actually Looks Like From Outside
&lt;/h2&gt;

&lt;p&gt;From a hosting dashboard's perspective, a frozen bot and a healthy one look identical: process running, memory allocated, no errors in the log because nothing is throwing one. The only real signal something's wrong is the complete &lt;em&gt;absence&lt;/em&gt; of anything happening — no new messages processed, no API calls made, no progress at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Add a Heartbeat Call to Your Bot's Loop
&lt;/h2&gt;

&lt;p&gt;In your bot script, call &lt;code&gt;staypresent.heartbeat()&lt;/code&gt; once per iteration of your main loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# worker.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;do_one_unit_of_work&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place it near the top of each iteration — this way, if the work itself hangs, the &lt;em&gt;next&lt;/em&gt; expected heartbeat is what catches it.&lt;/p&gt;

&lt;p&gt;For an async, event-driven bot (discord.py, Pyrogram), call it from a periodic background task instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;heartbeat_task&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Set a Heartbeat Timeout
&lt;/h2&gt;

&lt;p&gt;In your entry point, pass &lt;code&gt;heartbeat_timeout&lt;/code&gt; to &lt;code&gt;run()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;heartbeat_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;worker.py&lt;/code&gt; goes more than 30 seconds without calling &lt;code&gt;heartbeat()&lt;/code&gt;, StayPresent treats it as hung, terminates it, and restarts it through the normal crash-recovery pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Choosing the Right Timeout Value
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;heartbeat_timeout&lt;/code&gt; comfortably above your normal iteration time, including realistic worst-case latency — a slow API call, a large batch. If your loop typically completes in 2–5 seconds, &lt;code&gt;heartbeat_timeout=30&lt;/code&gt; gives generous headroom for occasional slowness while still catching a genuine freeze well before it goes unnoticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Confirming It Works
&lt;/h2&gt;

&lt;p&gt;You can test this deliberately before relying on it in production — temporarily add a line that sleeps past your configured timeout without calling &lt;code&gt;heartbeat()&lt;/code&gt;, and confirm the bot gets terminated and restarted as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# temporary test — remove after confirming
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# exceeds heartbeat_timeout=30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the &lt;code&gt;"staypresent"&lt;/code&gt; logger output; you should see the hang detected and the process restarted.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When a Hang Is Detected
&lt;/h2&gt;

&lt;p&gt;The behavior mirrors a real crash exactly: the process is terminated, logged, and handed to &lt;code&gt;restart_on_crash&lt;/code&gt;/&lt;code&gt;max_restarts&lt;/code&gt;/&lt;code&gt;restart_delay&lt;/code&gt; — the same recovery path, just triggered by silence instead of a bad exit code. On a recent StayPresent version, this also shows up as its own distinct "unresponsive" incident on the &lt;code&gt;/status&lt;/code&gt; page, separate from a plain crash, so you can tell the two failure modes apart after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does every bot need heartbeat monitoring?&lt;/strong&gt;&lt;br&gt;
No — it's opt-in. A bot with no &lt;code&gt;heartbeat_timeout&lt;/code&gt; set is only monitored for actual crashes, exactly as before.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can different bots have different timeout values?&lt;/strong&gt;&lt;br&gt;
Yes — &lt;code&gt;heartbeat_timeout&lt;/code&gt; is set per &lt;code&gt;run()&lt;/code&gt; call (or per bot, if using &lt;code&gt;bots=[...]&lt;/code&gt; with different configurations).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if my bot legitimately has slow, variable-length operations?&lt;/strong&gt;&lt;br&gt;
Set the timeout based on your slowest realistic case, or call &lt;code&gt;heartbeat()&lt;/code&gt; more granularly between sub-steps of a long operation rather than only once per full iteration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A frozen process is a genuinely different failure mode from a crash, and it's invisible to anything only watching exit codes. &lt;code&gt;staypresent.heartbeat()&lt;/code&gt; combined with &lt;code&gt;heartbeat_timeout&lt;/code&gt; closes that gap — your bot proves it's actually making progress, not just technically running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Deploy a Telegram Bot on Koyeb Without It Going Idle</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:09:51 +0000</pubDate>
      <link>https://dev.to/codenamew/deploy-a-telegram-bot-on-koyeb-without-it-going-idle-4d26</link>
      <guid>https://dev.to/codenamew/deploy-a-telegram-bot-on-koyeb-without-it-going-idle-4d26</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A step-by-step tutorial to deploy a Telegram bot on Koyeb with proper health checks and zero idle downtime, using StayPresent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploy a Telegram Bot on Koyeb Without It Going Idle
&lt;/h2&gt;

&lt;p&gt;Koyeb is a solid choice for hosting a Telegram bot — fast deploys, a generous free tier, support for both Git-based and Docker-based builds. But like most PaaS platforms, it expects your service to respond over HTTP, and a Pyrogram or python-telegram-bot script doesn't do that on its own. This tutorial walks through deploying a Telegram bot on Koyeb the right way, so it stays healthy and online instead of idling out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why Telegram Bots Go Idle on Koyeb&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Wrap Your Bot's Entry Point&lt;/li&gt;
&lt;li&gt;Step 3: Read Koyeb's Assigned Port&lt;/li&gt;
&lt;li&gt;Step 4: Choose Your Deploy Path (Git or Docker)&lt;/li&gt;
&lt;li&gt;Step 5: Set Koyeb's Health Check Path&lt;/li&gt;
&lt;li&gt;Step 6 (Optional): Prevent Scale-to-Zero Idle&lt;/li&gt;
&lt;li&gt;Verifying the Deployment&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Telegram Bots Go Idle on Koyeb
&lt;/h2&gt;

&lt;p&gt;A Telegram bot polls or listens for updates from Telegram's own servers — it's an outbound connection, not an inbound one. Koyeb's health checks expect the opposite: something listening on a port it can reach. Without that, Koyeb has no way to distinguish "this bot is working perfectly" from "this bot is broken," and treats the deployment as unhealthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# requirements.txt
staypresent[prod]
pyrogram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Wrap Your Bot's Entry Point
&lt;/h2&gt;

&lt;p&gt;Keep your bot's actual logic in &lt;code&gt;bot.py&lt;/code&gt;, untouched. Create &lt;code&gt;main.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Read Koyeb's Assigned Port
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Choose Your Deploy Path (Git or Docker)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Git-based (buildpack):&lt;/strong&gt; Koyeb detects &lt;code&gt;requirements.txt&lt;/code&gt; and builds automatically. Set the run command to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Docker-based&lt;/strong&gt; (useful if your bot needs system packages like &lt;code&gt;ffmpeg&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "main.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt; is identical either way — only the deployment mechanism changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Set Koyeb's Health Check Path
&lt;/h2&gt;

&lt;p&gt;In Koyeb's service settings, point the health check explicitly at &lt;code&gt;/health&lt;/code&gt; rather than the default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# /health still returns {"status": "ok"} independently of the above
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps platform health checks stable even if you later change what &lt;code&gt;/&lt;/code&gt; serves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 (Optional): Prevent Scale-to-Zero Idle
&lt;/h2&gt;

&lt;p&gt;If your specific Koyeb service type is subject to scale-to-zero behavior you want to avoid, add a self-ping against your own public URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-app.koyeb.app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verifying the Deployment
&lt;/h2&gt;

&lt;p&gt;Deploy, then check Koyeb's dashboard — the service should report healthy rather than cycling through restarts. Visit &lt;code&gt;https://your-app.koyeb.app/health&lt;/code&gt; directly to confirm it returns &lt;code&gt;{"status": "ok"}&lt;/code&gt;. On a recent StayPresent version, &lt;code&gt;/status&lt;/code&gt; also gives you a live dashboard showing uptime over time, which is the easiest way to confirm the fix is holding rather than just working once.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this work with python-telegram-bot instead of Pyrogram?&lt;/strong&gt;&lt;br&gt;
Yes — the fix is entirely about the entry point, not which Telegram library your bot uses internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need Docker for a simple bot?&lt;/strong&gt;&lt;br&gt;
No — Git-based buildpack deploys handle standard Python dependencies fine; Docker is only needed for extra system-level packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I reuse this &lt;code&gt;main.py&lt;/code&gt; if I later move to Render or Railway?&lt;/strong&gt;&lt;br&gt;
Yes — as long as &lt;code&gt;PORT&lt;/code&gt; is read from the environment, the same file works unchanged across platforms following the same convention.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A Telegram bot going idle on Koyeb almost always comes down to a missing HTTP port, not a problem with the bot's own polling logic. Wrapping your existing bot in &lt;code&gt;staypresent.run()&lt;/code&gt;, reading &lt;code&gt;PORT&lt;/code&gt; dynamically, and pointing Koyeb's health check at &lt;code&gt;/health&lt;/code&gt; solves it completely — Git-based or Docker-based, with no changes to your bot's actual code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>The Easiest Way to Add a Status Page Dashboard to Your Python Worker</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 20 Aug 2026 03:07:33 +0000</pubDate>
      <link>https://dev.to/codenamew/the-easiest-way-to-add-a-status-page-dashboard-to-your-python-worker-3lc6</link>
      <guid>https://dev.to/codenamew/the-easiest-way-to-add-a-status-page-dashboard-to-your-python-worker-3lc6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tutorial on adding a live status page with uptime history and incident tracking to a python background worker in under five minutes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Easiest Way to Add a Status Page Dashboard to Your Python Worker
&lt;/h2&gt;

&lt;p&gt;A background worker — a queue processor, a scheduled job runner, an API poller — usually has no visibility layer at all. It either works or it doesn't, and finding out which one requires checking logs or SSHing into wherever it runs. This tutorial covers the fastest way to give any Python worker a real status page: uptime history, incident tracking, and live status, without building any of it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What "Status Page" Actually Means Here&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Wrap Your Worker Script&lt;/li&gt;
&lt;li&gt;Step 3: That's It — Check &lt;code&gt;/status&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Step 4 (Optional): Customize the Page&lt;/li&gt;
&lt;li&gt;Step 5 (Optional): Name Your Worker&lt;/li&gt;
&lt;li&gt;Step 6 (Optional): Restrict Admin Detail&lt;/li&gt;
&lt;li&gt;Deploying It Somewhere Real&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What "Status Page" Actually Means Here
&lt;/h2&gt;

&lt;p&gt;This isn't a static "everything's fine" placeholder page. It's a real dashboard: per-service uptime percentages over 24 hours, 7 days, and 30 days, a merged incident history (crashes, restarts, recoveries), and live current status — all generated automatically from data your worker's supervisor already has, with nothing hand-built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Wrap Your Worker Script
&lt;/h2&gt;

&lt;p&gt;Your existing worker — &lt;code&gt;worker.py&lt;/code&gt;, doing whatever it already does — doesn't need any changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: That's It — Check &lt;code&gt;/status&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is genuinely the entire minimum setup. Run &lt;code&gt;main.py&lt;/code&gt;, and a full status page is already live at &lt;code&gt;http://localhost:8080/status&lt;/code&gt; — uptime tracking, incident history, everything, with zero additional configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# /status is live immediately, no extra code needed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 (Optional): Customize the Page
&lt;/h2&gt;

&lt;p&gt;If you want it branded rather than generic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data Pipeline Status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;copyright&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your Team&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;footer_links&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dark&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5 (Optional): Name Your Worker
&lt;/h2&gt;

&lt;p&gt;By default, the status page shows your worker by its filename. For something more readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;services_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Queue Processor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;services_description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Handles incoming job batches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6 (Optional): Restrict Admin Detail
&lt;/h2&gt;

&lt;p&gt;The public page shows overall status and uptime by default. If you want the deeper detail — exit codes, a recent log tail — locked behind a key rather than the auto-generated one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STATUS_ADMIN_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying It Somewhere Real
&lt;/h2&gt;

&lt;p&gt;The exact same code works whether you're testing locally or deploying to Render, Railway, Koyeb, or a VPS — just make sure to read the platform's assigned port dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data Pipeline Status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a database for the uptime history?&lt;/strong&gt;&lt;br&gt;
No — everything shown is derived from data already tracked in-process for crash recovery; there's no separate storage to set up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I add a status page to multiple workers at once?&lt;/strong&gt;&lt;br&gt;
Yes — pass a list or use &lt;code&gt;bots=[...]&lt;/code&gt; in &lt;code&gt;run()&lt;/code&gt;, and each worker gets its own row on the same page automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the status page accessible without any login?&lt;/strong&gt;&lt;br&gt;
The overall status, uptime, and incident summaries are public by design, the same as any real status page. Deeper technical detail (exit codes, log tails) is gated behind an optional admin key.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Adding a real status dashboard to a Python worker doesn't require building anything — &lt;code&gt;staypresent.run("worker.py")&lt;/code&gt; alone gets you a live page at &lt;code&gt;/status&lt;/code&gt; with genuine uptime and incident tracking, and a couple of optional lines let you brand it and control what's public versus admin-only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Deploying Multiple Python Bots to a Single Railway Container</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 20 Aug 2026 03:07:06 +0000</pubDate>
      <link>https://dev.to/codenamew/deploying-multiple-python-bots-to-a-single-railway-container-4hf1</link>
      <guid>https://dev.to/codenamew/deploying-multiple-python-bots-to-a-single-railway-container-4hf1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tutorial for running two or more python bots on Railway inside one container and one service, with independent crash recovery for each.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploying Multiple Python Bots to a Single Railway Container
&lt;/h2&gt;

&lt;p&gt;If you're running more than one Python bot — say, a Telegram ingestion bot and a Discord notification bot that share a database — deploying each as its own Railway service means double the hosting cost and double the configuration for something that's logically one unit. This tutorial covers deploying both bots inside a single Railway container, with each one still getting fully independent crash recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why Two Services Is Usually Overkill&lt;/li&gt;
&lt;li&gt;The Naive Fix and Why It Falls Short&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Structure Your Project&lt;/li&gt;
&lt;li&gt;Step 3: Configure Multiple Bots in One Entry Point&lt;/li&gt;
&lt;li&gt;Step 4: Read Railway's Assigned Port&lt;/li&gt;
&lt;li&gt;Step 5: Deploy as a Single Railway Service&lt;/li&gt;
&lt;li&gt;Verifying Both Bots Are Running&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Two Services Is Usually Overkill
&lt;/h2&gt;

&lt;p&gt;Railway (like most PaaS platforms) charges per service, and each service needs its own configuration, environment variables, and deployment pipeline. If two bots are closely related — sharing a database, a queue, or just conceptually belonging to the same project — running them as two separate Railway services duplicates all of that for no real benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Naive Fix and Why It Falls Short
&lt;/h2&gt;

&lt;p&gt;A common first instinct is a shell script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python telegram_bot.py &amp;amp; python discord_bot.py &amp;amp; &lt;span class="nb"&gt;wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs both, but there's no real process supervision here — if &lt;code&gt;telegram_bot.py&lt;/code&gt; crashes, nothing restarts it, and you still haven't solved Railway's HTTP port requirement, since neither script opens one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# requirements.txt
staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Structure Your Project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── main.py
├── telegram_bot.py
├── discord_bot.py
├── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both bot scripts stay exactly as they are — nothing about their internal logic needs to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Configure Multiple Bots in One Entry Point
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bots&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telegram_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discord_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each bot listed here is launched as its own subprocess, monitored independently — if &lt;code&gt;telegram_bot.py&lt;/code&gt; crashes and restarts, &lt;code&gt;discord_bot.py&lt;/code&gt; is completely unaffected and keeps running.&lt;/p&gt;

&lt;p&gt;If each bot needs different environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telegram_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ingestion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discord_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ROLE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notifications&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Read Railway's Assigned Port
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bots&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telegram_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discord_bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Deploy as a Single Railway Service
&lt;/h2&gt;

&lt;p&gt;Set your Railway start command to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy once. Both bots now run inside the same container, under one Railway service, with one hosting bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying Both Bots Are Running
&lt;/h2&gt;

&lt;p&gt;If you're on a recent StayPresent version, visit &lt;code&gt;/status&lt;/code&gt; on your deployed URL — each bot appears as its own row, with its own uptime and restart count, so you can confirm both are actually healthy at a glance rather than guessing from Railway's own single-service view.&lt;/p&gt;

&lt;p&gt;You can also give each bot its own lightweight endpoint for a quick manual check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;online&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/telegram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;online&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/discord&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does one bot crashing affect the other?&lt;/strong&gt;&lt;br&gt;
No — each bot in the &lt;code&gt;bots&lt;/code&gt; list gets its own restart counter and its own monitoring thread, completely independent of the others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run more than two bots this way?&lt;/strong&gt;&lt;br&gt;
Yes — &lt;code&gt;bots&lt;/code&gt; accepts as many entries as you need, each independently supervised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work the same way on Render or Koyeb?&lt;/strong&gt;&lt;br&gt;
Yes — the same &lt;code&gt;main.py&lt;/code&gt; and multi-bot configuration work unchanged across any platform that follows the standard &lt;code&gt;$PORT&lt;/code&gt; convention.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Running multiple related Python bots on Railway doesn't require multiple services. &lt;code&gt;staypresent.run(bots=[...])&lt;/code&gt; supervises each one independently — separate crash recovery, separate restart counters — while sharing a single container, a single port, and a single deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>devops</category>
      <category>docker</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Stop Your Discord Bot From Sleeping on Render's Free Tier</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 20 Aug 2026 03:06:12 +0000</pubDate>
      <link>https://dev.to/codenamew/how-to-stop-your-discord-bot-from-sleeping-on-renders-free-tier-4aca</link>
      <guid>https://dev.to/codenamew/how-to-stop-your-discord-bot-from-sleeping-on-renders-free-tier-4aca</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A step-by-step tutorial to stop a discord bot from sleeping on Render's free tier — the real cause, the fix, and a working code example.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to Stop Your Discord Bot From Sleeping on Render's Free Tier
&lt;/h2&gt;

&lt;p&gt;You've deployed your Discord bot to Render's free tier, it worked for a bit, and now it's going offline — sometimes after a few minutes, sometimes randomly. This is one of the most common issues developers hit deploying a bot for the first time, and it has a specific, well-understood cause and a fix you can ship in under ten minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why This Happens on Render Specifically&lt;/li&gt;
&lt;li&gt;Confirming This Is Your Actual Problem&lt;/li&gt;
&lt;li&gt;Step 1: Install StayPresent&lt;/li&gt;
&lt;li&gt;Step 2: Wrap Your Bot's Entry Point&lt;/li&gt;
&lt;li&gt;Step 3: Read Render's Assigned Port&lt;/li&gt;
&lt;li&gt;Step 4: Set Your Render Start Command&lt;/li&gt;
&lt;li&gt;Step 5 (Optional): Prevent Inactivity Sleep Specifically&lt;/li&gt;
&lt;li&gt;Verifying It Worked&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why This Happens on Render Specifically
&lt;/h2&gt;

&lt;p&gt;Render's free-tier web services are checked for health over HTTP, and free services also spin down after a period without incoming traffic. A discord.py bot connects &lt;em&gt;outward&lt;/em&gt; to Discord's gateway — it never opens an HTTP port of its own, which is completely normal bot behavior. Render's health checker, seeing nothing respond on the expected port, has no way to know the bot is actually working fine internally. It just sees silence, and reacts accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirming This Is Your Actual Problem
&lt;/h2&gt;

&lt;p&gt;If your bot's entry point goes straight into &lt;code&gt;bot.run(TOKEN)&lt;/code&gt; with nothing else, and Render's dashboard shows the deployment as unhealthy or repeatedly restarting with no matching error in your bot's own logs, this is almost certainly it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install StayPresent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add it to your &lt;code&gt;requirements.txt&lt;/code&gt; as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;staypresent[prod]
discord.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Wrap Your Bot's Entry Point
&lt;/h2&gt;

&lt;p&gt;Keep your existing bot code in &lt;code&gt;bot.py&lt;/code&gt; completely unchanged. Create a new &lt;code&gt;main.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Read Render's Assigned Port
&lt;/h2&gt;

&lt;p&gt;Render injects a &lt;code&gt;PORT&lt;/code&gt; environment variable — your app needs to bind to it dynamically, not a hardcoded value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Set Your Render Start Command
&lt;/h2&gt;

&lt;p&gt;In Render's service settings, set the start command to run your new entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy. Render now has something to check on, and your bot's own logic — the actual discord.py code — didn't change at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 (Optional): Prevent Inactivity Sleep Specifically
&lt;/h2&gt;

&lt;p&gt;Solving the port requirement alone stops Render from marking the deployment unhealthy. If Render's free tier is &lt;em&gt;also&lt;/em&gt; spinning your specific service down after inactivity, add a self-ping targeting your own public URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-app-name.onrender.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has to target your actual &lt;code&gt;onrender.com&lt;/code&gt; URL — pinging &lt;code&gt;0.0.0.0&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt; never leaves the machine and won't count as external activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying It Worked
&lt;/h2&gt;

&lt;p&gt;After deploying, check Render's dashboard — the service should show as healthy rather than restarting in a loop. You can also visit your app's &lt;code&gt;/health&lt;/code&gt; endpoint directly in a browser; it should return &lt;code&gt;{"status": "ok"}&lt;/code&gt;. If you're on a recent version of StayPresent, &lt;code&gt;/status&lt;/code&gt; shows a live status page with uptime history, which is a quick way to confirm the bot is staying up over time rather than just checking once.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need to change my bot's actual Discord logic?&lt;/strong&gt;&lt;br&gt;
No — &lt;code&gt;bot.py&lt;/code&gt; stays exactly as it was. Only the entry point Render runs (&lt;code&gt;main.py&lt;/code&gt;) is new.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work for Telegram bots too?&lt;/strong&gt;&lt;br&gt;
Yes — the same pattern applies to any long-running Python process that doesn't open its own HTTP port, regardless of which platform's API it talks to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will this cost anything?&lt;/strong&gt;&lt;br&gt;
No — StayPresent is MIT licensed and free, and this setup doesn't require upgrading your Render plan.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A Discord bot sleeping on Render's free tier is almost always a missing HTTP port, not a bug in your bot. Wrapping your existing &lt;code&gt;bot.py&lt;/code&gt; in &lt;code&gt;staypresent.run()&lt;/code&gt;, reading Render's &lt;code&gt;PORT&lt;/code&gt; variable, and optionally self-pinging your public URL solves it completely, with zero changes to your bot's actual code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>backend</category>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>"I Could Just Build This Myself" — Sure, But Should You?</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 20 Aug 2026 03:04:37 +0000</pubDate>
      <link>https://dev.to/codenamew/i-could-just-build-this-myself-sure-but-should-you-40mc</link>
      <guid>https://dev.to/codenamew/i-could-just-build-this-myself-sure-but-should-you-40mc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Every piece of StayPresent is individually easy to build. Here's an honest look at what building it yourself actually costs versus installing it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  "I Could Just Build This Myself" — Sure, But Should You?
&lt;/h2&gt;

&lt;p&gt;It's a fair reaction, and an honest one: a Flask server in a background thread isn't hard. Restart logic isn't hard. None of the individual pieces of what StayPresent does are conceptually difficult for a developer who already knows Python. The real question isn't whether you &lt;em&gt;could&lt;/em&gt; build it — it's whether you should, given what it actually costs to build and maintain it properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that's easy to underestimate
&lt;/h2&gt;

&lt;p&gt;The first 80% of a keep-alive server is genuinely quick to write — a Flask route, a background thread, done in twenty minutes. The remaining 20% is where the real time goes: handling a bot that crashes mid-restart-backoff during a shutdown signal without leaking a process. Making sure your logging doesn't clobber the root logger and break something unrelated. Deciding what "hung, not crashed" even means and building a mechanism to detect it. None of these are hard problems individually, but they're the kind of edge cases you only discover by hitting them in production, one at a time, over months.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "building it yourself" actually costs
&lt;/h2&gt;

&lt;p&gt;Say it takes a focused weekend to get a first version working. It probably will — the core idea is simple. What that weekend doesn't include is the six months of intermittent bug reports from your own deployment: the restart that didn't happen because of a race condition you didn't think about, the log line that got swallowed, the status page you don't have because that was never part of the plan and now feels like a whole separate project. The initial build is cheap. The maintenance is where a hand-rolled version quietly becomes a second, ongoing job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get by not building it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every edge case above — the shutdown race condition, the isolated logging, hang detection, a working status page — already exists, already tested against real deployments, already maintained by someone whose actual job (at least for this piece) is making sure it keeps working. You get the twenty-minute version's simplicity with the six-months-later version's robustness, on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  There's no shame in not reinventing this
&lt;/h2&gt;

&lt;p&gt;Plenty of genuinely excellent developers use tools like this specifically &lt;em&gt;because&lt;/em&gt; they understand how the internals work — knowing how to build something yourself doesn't mean you should spend your limited time rebuilding it for the tenth time across your tenth project. Save the "build it myself" energy for the part of your bot that's actually unique — the part nobody else has already solved for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could build this yourself. You probably have better things to build.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>"What's the Catch?" — Why StayPresent Is Actually Free</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 20 Aug 2026 03:04:01 +0000</pubDate>
      <link>https://dev.to/codenamew/whats-the-catch-why-staypresent-is-actually-free-4p0j</link>
      <guid>https://dev.to/codenamew/whats-the-catch-why-staypresent-is-actually-free-4p0j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A tool that handles crash recovery, hang detection, and a full status page sounds like it should cost something. Here's why it doesn't, and won't.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  "What's the Catch?" — Why StayPresent Is Actually Free
&lt;/h2&gt;

&lt;p&gt;It's a reasonable question. A tool that handles crash recovery, hang detection, and generates a real status page — the kind of thing you'd expect to see behind a pricing page with a "Pro" tier — is, in fact, entirely free. Not a free tier that nudges you toward upgrading. Not a trial. Just free. Here's the honest answer to "what's the catch," because the skepticism is fair.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MIT license, plainly stated
&lt;/h2&gt;

&lt;p&gt;StayPresent is released under the MIT License — one of the most permissive open-source licenses that exists. In practice, that means: use it in a personal project, use it in something you're charging money for, modify it, redistribute it, all without paying anything or asking permission. There's no feature gate waiting behind a paywall, and no functionality quietly disabled unless you upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  No account required, no data leaving your deployment
&lt;/h2&gt;

&lt;p&gt;There's no account to create, no API key issued by a third party, no external service your bot phones home to. Everything — the web server, the crash recovery, the status page, the hang detection — runs directly inside your own deployment, on infrastructure you already control. There's genuinely nothing to charge for on the vendor side, because there's no ongoing service being provided from outside your own process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "free forever" is actually plausible here
&lt;/h2&gt;

&lt;p&gt;A lot of "free" developer tools eventually aren't, because they're subsidizing a hosted service somewhere — servers, bandwidth, support staff — and the free tier exists to fund a business built around eventually charging some of its users. StayPresent doesn't have that shape. It's a library, not a hosted service. There's no infrastructure cost scaling with your usage that would ever create pressure to start charging.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're actually trading
&lt;/h2&gt;

&lt;p&gt;The honest trade isn't money — it's that you're running the supervision and monitoring yourself, inside your own deployment, instead of paying someone else to run it for you externally. For a solo bot developer or a small project, that's a genuinely good trade: you get real functionality, you keep full control, and you don't hand a third party visibility into your bot's uptime data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free, and worth checking yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't have to take this on faith — the source is open, the license is public, and there's nothing hidden to eventually find. Install it, read the code if you want to, and use it exactly the way the license says you can: however you want.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>software</category>
      <category>tools</category>
    </item>
    <item>
      <title>If People Pay for Your Bot, They Deserve to Know It's Actually Up</title>
      <dc:creator>John Wick</dc:creator>
      <pubDate>Thu, 20 Aug 2026 02:52:46 +0000</pubDate>
      <link>https://dev.to/codenamew/if-people-pay-for-your-bot-they-deserve-to-know-its-actually-up-4je1</link>
      <guid>https://dev.to/codenamew/if-people-pay-for-your-bot-they-deserve-to-know-its-actually-up-4je1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Selling access to a bot or a Discord service? A public status page is one of the cheapest trust signals you can offer paying customers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  If People Pay for Your Bot, They Deserve to Know It's Actually Up
&lt;/h2&gt;

&lt;p&gt;The moment a bot stops being a hobby project and starts being something people pay for — a premium tier, a subscription, a paid service inside a Discord server — the rules change. "It's usually fine" isn't good enough anymore. People handed you money on the expectation that the thing works, and when it doesn't, silence is the worst possible response.&lt;/p&gt;

&lt;h2&gt;
  
  
  What paying customers actually expect
&lt;/h2&gt;

&lt;p&gt;Nobody expects 100% uptime — reasonable people understand things break. What they expect is to &lt;em&gt;know&lt;/em&gt; when it does. A support DM answered with "yeah it's down, we're on it" lands completely differently than the same DM going unanswered for three hours while the customer wonders if they've been ignored, or worse, scammed. The information itself often matters less than how quickly and openly it's shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap between free and paid bots
&lt;/h2&gt;

&lt;p&gt;A free bot going down for an hour is an inconvenience. A paid bot going down for an hour, with no visibility into why or when it'll be back, is a refund request — and, more damagingly, a screenshot in a review or a public complaint. The bar for reliability &lt;em&gt;communication&lt;/em&gt; goes up the moment money changes hands, even if the bar for actual uptime doesn't move much at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  A public status page does this for free
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;staypresent&lt;/span&gt;

&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyBot Premium Status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;footer_links&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Support&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mysite.com/support&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;staypresent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bot.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is live, automatically, the moment your bot is running — uptime history, incident timeline, current status, all generated from data StayPresent already has. No separate service, no separate bill, and critically, nothing extra for you to maintain when things are already stressful during an actual outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  It answers the question before it's asked
&lt;/h2&gt;

&lt;p&gt;The real value isn't during the outage — it's &lt;em&gt;before&lt;/em&gt; anyone even has to ask. A customer who's curious can check &lt;code&gt;/status&lt;/code&gt; themselves instead of DMing you. That's fewer interruptions for you during an actual incident, and a customer who feels like they have real visibility instead of having to take your word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust is cheaper to build than to rebuild
&lt;/h2&gt;

&lt;p&gt;Setting this up before you ever need it costs a few minutes. Rebuilding trust after a bad, poorly-communicated outage costs a lot more — refunds, churn, reviews you can't take back. If people are paying you for reliability, show them you're actually tracking it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;staypresent[prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your paying customers are already trusting you with their money. Give them somewhere to check that trust is warranted.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
