<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Judoscale Dev Blog</title>
    <description>The Judoscale Dev Blog</description>
    <link>https://judoscale.com/</link>
    <language>en-us</language>
    <item>
      <title>Autoscaling on a Sovereign Cloud: Judoscale for Scalingo</title>
      <description>Autoscale web and worker containers on Scalingo with Judoscale, bringing queue-time autoscaling to a sovereign European cloud.</description>
      <pubDate>Mon, 7 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/judoscale-now-supports-scalingo</link>
      <guid>https://judoscale.com/blog/judoscale-now-supports-scalingo</guid>
      <author>Adam McCrea</author>
      <content:encoded>
        <![CDATA[<p>Judoscale now supports <a href="https://scalingo.com" target="_blank" rel="noopener">Scalingo</a>. If you’ve used Heroku, Scalingo will feel familiar: push your code, run web and worker containers, and let the platform handle the infrastructure underneath. Scalingo is a sovereign European cloud: it hosts apps in Paris datacenters and keeps your infrastructure under European jurisdiction. For public-sector and regulated workloads, its SecNumCloud-backed region goes further by running on French infrastructure that meets France’s strict <a href="https://cyber.gouv.fr/enjeux-technologiques/cloud/" target="_blank" rel="noopener">SecNumCloud security standard</a>.</p>

<p>We built this integration because several customers migrated from Heroku to Scalingo and wanted to bring queue-time autoscaling with them. Yespark was one of those customers:</p>
<blockquote><p>Judoscale has been live in our Scalingo app for several weeks now with really solid results. Autoscaling is doing its job.</p>

<p>— Navid Emad, Full-Stack Developer at Yespark</p>
</blockquote>
<p>This integration works just like our other platform integrations. Judoscale reads request queue time from web containers and job queue time from workers, then changes the container count through Scalingo’s API. Scalingo’s native autoscaler can use traffic and response metrics for web containers, but non-web containers are limited to CPU, RAM, and swap. I/O-heavy jobs can back up your job queues while CPU stays flat, so Judoscale scales workers based on job queues instead.</p>

<p><figure>
  <img alt="Judoscale scaling charts for a Scalingo application" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/4a79dc2a-e349-49ee-73f8-651820647b00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/4a79dc2a-e349-49ee-73f8-651820647b00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/4a79dc2a-e349-49ee-73f8-651820647b00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Get started by creating a <a href="/sign-up">free Judoscale account</a> and connecting your Scalingo app using an API token. We support Ruby, Python, Node.js, and Java apps, along with their common background job systems. See our <a href="/docs/scalingo-getting-started">Scalingo setup docs</a> for the full walkthrough.</p>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Rails For Marketing Sites in the AI Era</title>
      <description>Two developer opinions Rails vs JavaScript for modern marketing sites, with AI-generated code shaping stack choice, costs, and maintenance.</description>
      <pubDate>Tue, 1 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/just-use-rails-revisited</link>
      <guid>https://judoscale.com/blog/just-use-rails-revisited</guid>
      <author>Adam McCrea and Jon Sully</author>
      <content:encoded>
        <![CDATA[<p>A couple of years ago, we rewrote Judoscale’s marketing site from NextJS to Ruby on Rails. I enjoyed the process so much that <a href="https://www.youtube.com/watch?v=blzd9MEyzcA" target="_blank" rel="noopener">I made a video</a> sharing how it worked, and Jon <a href="https://judoscale.com/blog/post-jamstack-just-use-rails" target="_blank" rel="noopener">wrote a spicy piece</a> arguing that Rails was a perfect fit for mostly-static marketing sites like ours, and that you shouldn’t waste your time with JavaScript frameworks that were all the rage at the time.</p>

<p>A lot has changed since then (duh!), and I wanted to revisit this topic to share my current take. Jon’s gonna chime in later with his take as a bit of a rebuttal. It’s healthy to disagree, but rare to see it in a blog post. Let us know what you think of the format!</p>

<p><figure>
  <img alt="Pencil-style illustration of two men squaring off playfully in a boxing ring, one wearing orange gloves and the other purple, with a silhouetted audience in the background." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ca0093ae-b522-44c7-7c99-4cc4ef501400/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ca0093ae-b522-44c7-7c99-4cc4ef501400/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ca0093ae-b522-44c7-7c99-4cc4ef501400/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<h2 id="adam-s-take">Adam’s take</h2>

<p>I recently built a new marketing site for my wife’s <a href="https://groundedrootnutrition.com" target="_blank" rel="noopener">dietitian practice</a>, which is why this is so top-of-mind for me. The previous site was a Rails app that I built, and she wanted to redesign the site. Of course a redesign does not warrant a <em>rebuild</em>, but we’ll get there. Let’s first take a step back.</p>

<p>When I say “a lot has changed”, obviously I&rsquo;m talking about AI. If you&rsquo;re reading this, you&rsquo;re probably a developer using AI in your daily coding. For me right now in August 2026, AI is writing nearly 100% of my code, whether it&rsquo;s for personal use or for Judoscale.</p>

<p>Most of the code I write (generate, command-into-existence, whatever) is Ruby. AI is <em>fantastic</em> at writing Ruby, particularly Ruby on Rails. But since I&rsquo;m not handwriting code anymore, I care a lot less about the languages and frameworks used, especially if I’m not reading the code.</p>

<p>“Whoa, hold up! You’re not reading the code?!”</p>

<p>Well, it depends&hellip; For the Judoscale app itself, which is responsible for our customers’ data and keeping their applications online, I am absolutely reading the code. For personal projects and projects that don’t involve customer data, not so much. Let’s take a marketing site like the one you’re reading right now. It’s mostly a blog, some landing pages, a <a href="https://judoscale.com/tools/paas-pricing-calculator" target="_blank" rel="noopener">fancy price comparison tool</a>, and documentation. Why bother reading every line of code?</p>

<p>So as I approached redesigning my wife’s website, I knew AI would be generating the code and that I wouldn’t be reading it. All I cared about was the user experience of the site itself. This meant that a <em>redesign</em> and a <em>rebuild</em> were essentially the same amount of effort, and if I’m considering a rebuild, I should reassess the stack.</p>

<p>Since I didn’t care much about the language or framework used, the decision really came down to hosting. A Rails app is a persistent process that needs to be running 24/7. A PaaS like Heroku (or Render, or Railway, etc.) makes it easier, but it’s still something to manage. I’d also need a database to store some of the site content, and asset storage. More things to manage, more things to pay for.</p>

<p>Instead, I went with <a href="https://docs.emdashcms.com/introduction/" target="_blank" rel="noopener">EmDash CMS</a> hosted on Cloudflare Workers. EmDash is built on Astro, which is what I would’ve used if we didn’t need a CMS. Cloudflare is already sitting in front of the site for DNS and CDN, so <em>hosting</em> the site there feels like a no-brainer. Assets live in Cloudflare R2, and the site content lives in D1 (Cloudflare’s serverless SQL database service).</p>

<p>Not only is everything in one place, but at her scale (and probably most marketing sites), it’s completely free. Cost is really a secondary concern here, though. The big win for me is simplicity. There’s nothing I need to keep up and running, nothing I need to update over time.</p>

<p>And of course AI agents know Astro and Cloudflare very well, so the build and deploy experiences were amazingly smooth. I never read a line of code, and I don’t need to. The site just works.</p>

<p>To be clear, I’m not anti-Rails or anti-PaaS for marketing sites or anything else. They are still amazing tools that I use all the time. But for me, AI has made serverless <del>cool</del> useful again, at least for marketing sites. And that means Cloudflare workers running JavaScript.</p>

<h2 id="jon-s-rebuttal">Jon’s rebuttal</h2>

<p>Alright, hold on; we need to zoom out! If the question is, “Should you use Rails for a marketing site in the AI era?” then I think Adam’s take is “I would vibe code”. As in, he wouldn’t even deliberately decide which stack to use because he moved himself up the engineering ladder (out of ‘engineering’ I would even say 🌶️). I guess that answers the question… “don’t choose a stack at all, just use AI ✨.” But I don’t like that answer. Not always, at least.</p>

<p>My rebuttal has two pillars. Let me start on the simpler one, which Adam actually agrees with (I think). <strong>Marketing sites should be dynamic, not static</strong>. In our post a few years back, we deliberately chose to use Rails for several reasons, but we didn’t talk much about the static vs. dynamic axiom. In the years since I’ve come to realize: the ask will always come. What you make a truly static site today <em>will</em> be asked for dynamic features. Maybe not tomorrow. Maybe not this year. But next, or the one after, it will. Static sites either die or live long enough need dynamic things. Adam’s AI went with emDash CMS with Cloudflare persistence so it agrees, apparently!</p>

<p><figure>
  <img alt="Gif of the quote from Batman, “You either die a hero or live long enough to become a villain”" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExNTR3ZXF1eWxoMzhremk0eGppMXJ2ZXE4bXU0dzhueTY5NXpkb2x0cCZlcD12MV9naWZzX3JlbGF0ZWQmY3Q9Zw/102ynnMn7HhJSM/giphy.gif">
  
</figure>
</p>

<p>The second pillar of my rebuttal is about reading code… but I think there’s nuance here. Ultimately, I think drawing the line of “should I read the code?” around customer data is a mistake. That’s a very ‘engineer’-shaped definition of important software. If this is a hobby site, or something where you genuinely don’t care how it directs and guides users, then sure: vibe away. Have AI build it in Astro, Rails, PHP… COBOL! I don’t particularly care either. If the output looks great and you’re comfortable throwing the whole thing away if it doesn’t, the implementation really is an implementation detail.</p>

<p>But a marketing site for a business is different. It makes money. Maybe not directly, but most people at a business will understand that how a marketing site directs, informs, catches, and guides users is a direct factor in the money-making process. Even the most die-hard “provide value! don’t just seek to make money!!” folks inevitably understand that user attention is a finite resource and one must play the game (a little).</p>

<p>We engineers tend to reserve our scrutiny for the parts of the system that <em>feel</em> like engineering: databases, background jobs, APIs, authentication, concurrency, performance. We care deeply about their mechanics because we understand how subtle implementation choices can affect the business. But we forget that our sales and marketing teams feel exactly that same way about the mechanics of a boring-old marketing site that doesn’t handle customer data directly.</p>

<p>They care about page speed. They care about analytics firing correctly. They care about attribution surviving a signup flow. They care about canonical URLs, redirects, structured data, accessibility, forms, experiments, conversion funnels, and the fifty tiny details between someone landing on a page and becoming a customer. A marketing site can “work just fine” while quietly missing a few of those steps and churning visitors that were genuinely interested in your product/service.</p>

<p>To clarify: this is still a “software correctness” concern!! The failure mode just isn’t a 500 or Sentry error. And it’s usually <em>harder</em> to track than hard-errors.</p>

<p>So I don’t think “there’s no customer data here” gets us all the way to “therefore I don’t need to read the code.” If the software materially participates in your business, <strong>its implementation matters</strong>. And if its implementation matters enough to read, suddenly the language and framework aren’t irrelevant anymore.</p>

<p>This isn’t to say you shouldn’t use AI to help you build your marketing site! You should! But you should read everything and consider it thoughtfully. You should bring your full intentionality and engineering prowess to that marketing site. </p>

<p>So&hellip; Should you use Rails for a marketing site in the AI era? If Rails is the tool you’re most familiar with, I think you should. But there’s a second question now: Should AI just build my marketing site? And I think the answer is (maybe unsurprisingly): not if you’re a revenue-generating business depending on that marketing site to bring you customers and explain your product/service!</p>

<p>P.S. Sorry Amy — tell Adam to rebuild your marketing site again but care more about conversion this time!! (Kidding, of course)</p>

<hr>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Judoscale on Tour: Render Has the Pieces, Not the Workflow</title>
      <description>Hands-on review of migrating a production app to Render: costs, egress pitfalls, deployment workflow tradeoffs, and real-world pros/cons.</description>
      <pubDate>Tue, 7 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/judoscale-on-tour-render</link>
      <guid>https://judoscale.com/blog/judoscale-on-tour-render</guid>
      <author>Jon Sully</author>
      <content:encoded>
        <![CDATA[<p><strong>Judoscale &lsquo;On Tour&rsquo; Series</strong></p>

<ol>
<li><a href="/blog/judoscale-on-tour-ode-to-heroku">&ldquo;The Friction Model&rdquo; &amp; <strong>Heroku</strong></a></li>
<li><strong>Render</strong> <em>(This page!)</em></li>
<li><strong>Railway</strong> (Coming soon&hellip;)</li>
<li><strong>Fly</strong> (Coming soon&hellip;)</li>
<li><strong>Northflank</strong> (Coming soon&hellip;)</li>
<li><strong>Digital Ocean</strong> (Coming soon&hellip;)</li>
<li><strong>Amazon ECS Fargate</strong> (Coming soon&hellip;)</li>
</ol>

<hr>

<p>After about a month of migrating, tweaking, and configuring, our opinions are in! We successfully migrated our full production and staging application stacks to Render. We tried as many bells and whistles as we could, let things run for a non-trivial amount of time, and have some interesting thoughts to share with you.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p></p>

<p>The “Judoscale Tour” is our exploration of today’s PaaS offerings via deliberately migrating our real production app, our staging systems, and our development workflows to each of said offerings in sequence. It’s a big endeavor, but we want to know first-hand what each PaaS service on the market truly <em>feels like</em>. And we want to relay that information to you with receipts! We outlined our strategy and rubric in our first edition, the more-reflective, “<a href="/blog/judoscale-on-tour-ode-to-heroku">An Ode To Heroku</a>”.</p>

  </div>
</div>

<p><figure>
  <img alt="Illustration of the Render logo beside a single server rack emerging from stylized clouds." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/4294c89c-3ebf-4f5c-5c9d-17119dc18500/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/4294c89c-3ebf-4f5c-5c9d-17119dc18500/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/4294c89c-3ebf-4f5c-5c9d-17119dc18500/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<h2 id="tl-dr">Tl;dr:</h2>

<p>There’s no need to make you wait if you want a quick conclusion: our thesis landed a little differently than we first thought, but in the same ballpark… <strong>Moving from Heroku to Render means you’ll probably save a good chunk of change (likely 30-40% depending on what types of Heroku Dynos you currently pay for and run), but Render isn’t as simple or easy as Heroku. You <em>will</em> trade some of your development and maintenance time for those infrastructure dollars. You will own more.</strong> Render has a few neat features, but it simply doesn’t nail the simplicity that Heroku’s always had. That’s our tl;dr. But there’s a <em>lot</em> of nuance there, so we’d encourage you to read on.</p>

<h2 id="the-mental-model">The Mental Model</h2>

<p>Since The Judoscale Tour is all about exploring potential alternatives and migration paths away from Heroku, we want to preface our thoughts and results with a quick sidebar of how the mental model for a given platform might differ from Heroku’s. In the case of Render it’s… tricky. Heroku’s primitive object upon which everything is built is the “app” — a collection of processes which are themselves distinct (<code>web</code>, <code>fast-worker</code>, <code>slow-worker</code>, <code>clock</code>, etc.), but which collectively act together to make up an <em>application</em>. Render, on the other hand, took the path of the processes themselves being first-class citizens; top-level constructs they call “Services”. For sure, that allows for greater flexibility and control in how things operate together, but it also means that certain <em>app-level</em> lifecycle events are overlooked and have to be built / coordinated yourself. You’ll see what we mean as we go over some specifics here, but just understand that a collection of disparate services (which have no workflow connection to one another) can be annoying.</p>

<p><figure>
  <img alt="Illustration comparing two software architecture models. On the left, several small service-like characters sit together inside a single rounded container, with arrows converging toward a shared center to suggest one coordinated application. On the right, similar characters stand on separate platforms with crisscrossing arrows between them, conveying independently managed services and a more fragmented deployment model." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/365bf46a-2621-4929-2349-76c5b6f4e300/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/365bf46a-2621-4929-2349-76c5b6f4e300/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/365bf46a-2621-4929-2349-76c5b6f4e300/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            This image may be a little over-emphasized… having separate services isn&rsquo;t as bad as it looks, and having a cohesive app isn&rsquo;t all harmony!
          </figcaption>

</figure>
</p>

<h2 id="render-shipping-friction">Render: Shipping Friction</h2>

<p>At the very outset, getting your first Render service configured and deployed will likely be pretty easy! The git-connected, just-pull-from-Github/Gitlab/Bitbucket flow feels very familiar. Render has prefab buildpacks, configs environment variables from the web UI, and takes you right to a metrics dashboard. Sounds <em>very</em> familiar 😆.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-green-50 dark:bg-green-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-green-900 dark:text-green-500">
    ✅ Tip
  </h4>
  <div class="mt-2.5 text-green-800 prose-a:text-green-900 dark:prose-a:text-white prose-code:text-green-900 dark:text-gray-300 dark:text-green-100/80 dark:prose-code:text-gray-300">
    <p></p>

<p>Even though it’s not documented <em>anywhere</em> that we could find, Render’s Ruby buildpack <em>does</em> luckily include Jemalloc. Just use the super intuitive environment variable <code>LD_PRELOAD</code> = <code>/usr/lib/x86_64-linux-gnu/libjemalloc.so.2</code>. Duh! 🤦‍♂️🤦‍♂️</p>

  </div>
</div>

<p>But your application is more than a single service! Building out additional services via the Render web UI is, again, straightforward, but you might shift toward making a <a href="https://render.com/docs/blueprint-spec" target="_blank" rel="noopener">Blueprint</a>: a YAML file that declares the shapes and basic configs of all services within your repository (*cough* app). While it can feel nice to have many basics configured in code rather than freely changeable web UI, it introduced some confusion for us. Mostly anything we can configure in a Blueprint, we can also configure in the web UI… even though we made a Blueprint. For example, our Blueprint for our <code>judoscale-prod-clock</code> process contains a <code>buildCommand</code> of <code>bundle install</code>. And when I open that service in the web UI, I see the corresponding build command:</p>

<p><figure>
  <img alt="" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/41ed4368-f0d1-497f-ef2a-f7fbeeca3700/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/41ed4368-f0d1-497f-ef2a-f7fbeeca3700/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/41ed4368-f0d1-497f-ef2a-f7fbeeca3700/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>And you might say, “Well yeah, it’s just telling you what’s in your Blueprint!” But you’d be mistaken (as we were)! And you’ll also note there’s an “Edit” button. Yes, you can open that field and edit the build command from the web UI! So then which one <em>actually runs</em>? The Blueprint command or the web UI command? That’s a great question! 🙃</p>

<p>Nonetheless, the zero-to-running-in-prod workflow on Render is pretty seamless. We’re confident any Heroku-user could get up and running on Render smoothly. Shipping friction doesn’t only occur on day one, though. The day-to-day development and deployment cycles of teams matters much <em>more</em> than first-time workflows! And we have less than ideal impressions on that front, too.</p>

<p>Given Render’s “processes are the first class object” model, <strong>each ‘service’ manages its own deployment lifecycle independent of others</strong>.</p>

<p>This is easy to understand when we think back to how Heroku handles deploys: there’s a single build that sets up the final “slug” for that deploy, a “release” step which executes, and a final rollout of the slug to all processes in the Procfile if the release step completes. Render simply doesn’t have this multi-process coordination out of the box. When running Render’s simple “deploy from Git” style strategy, each service receives its own notice that there’s a new commit to deploy. Each service then executes its own full build. Then each service attempts to run the pre-deploy command, then finally roll out its built code.</p>

<p><figure>
  <img alt="Illustration of four independent deployment pipelines. A single cloud at the top sends updates to four separate services, each shown with its own laptop, build conveyor, and rocket launch. The repeated parallel workflows emphasize that every service builds and deploys independently rather than sharing one coordinated application release." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/78edb532-c494-400e-17e6-e48d0cc4ae00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/78edb532-c494-400e-17e6-e48d0cc4ae00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/78edb532-c494-400e-17e6-e48d0cc4ae00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>That has two main hiccups we care about: first (though Adam and I disagree on how big of a deal it is 🤭) it’s not ideal that each service has its own build artifacts generated. Should they result in the same actual files? Yes, probably. But can things happen? For sure. Second, and more importantly, if the pre-deploy step fails for Service A, it doesn’t halt anything for Service B. Service B may still deploy its updated code while Service A does not. <strong>There’s no unified “app”-level halt for release-step failures</strong>. Which makes me think it’s only a matter of time before my services end up running different versions of the codebase at the same time 😱.</p>

<p>Thus, Render makes it easy to deploy services, but it did not make it easy to deploy <em>Judoscale</em>. We ended up constructing some Github Actions-based hackery to coordinate our builds and deploys across multiple services… none of it was particularly elegant. Coming from Heroku, where we didn’t have to do any of that, we didn’t love it 😕</p>

<p>Our grade for Render on Shipping Friction: <strong>C</strong>. There is friction and you will feel it when coming from Heroku.</p>

<h2 id="render-debugging-friction">Render: Debugging Friction</h2>

<p>Pivoting over to a brighter outlook, Render’s facilities and tooling for debugging are actually pretty great! Where Heroku’s metrics charts are quite limited (and dated), its log tooling is essentially “view it in real time or plug in a log drain”, and its dashboard leaves something to be desired in terms of “how are things right now?”, Render solves many of these.</p>

<p>Render natively stores logs on your behalf for varying degrees of time (depending on your machine tier):</p>

<p><figure>
  <img alt="" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f6bab0a3-5e52-4325-b073-6191b2df6f00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f6bab0a3-5e52-4325-b073-6191b2df6f00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f6bab0a3-5e52-4325-b073-6191b2df6f00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>That’s pretty nice! We’d still recommend that serious production apps plug in a more comprehensive logging and dashboard/data tool, but it’s fantastic that Render itself can serve as a logs backup or even a simple debugging interface for smaller and/or less needy apps.</p>

<p>Render’s metrics charts aren’t especially comprehensive, but they win marks for having many different spans of time with which you can view them. From last-30-days to last-30-minutes, and just about everything in between. Going further, Render also supports OTel, so you can ship your metrics data off wherever you prefer with relative ease. </p>

<p><figure>
  <img alt="Screenshot of the Render application dashboard UI with the timespan selector for the metrics data open. Many options are shown, ranging from “Last 30 min” to “Last 30 days” and “Custom”" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/257febc2-e784-4e86-35ef-6ace330a0100/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/257febc2-e784-4e86-35ef-6ace330a0100/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/257febc2-e784-4e86-35ef-6ace330a0100/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Now, it’s not all roses and daisies — you end up coping with the disparate services issue even in some of the observability tooling (e.g. “give me the average CPU load across <code>web</code> AND <code>worker-fast</code> AND <code>worker-slow</code>” etc. in every query…), but that per-service breakdown can occasionally be useful too. </p>

<p>Render just feels like a more modern Heroku in this area. Where Heroku has log drains, Render has metrics streams, OTel, their own logs, and more flexible log delivery mechanisms. We wish we had some of that on Heroku!</p>

<p>But if we’re talking about debugging friction, we need to talk about Render‘s support workflow as well. The “submit a ticket” kind…</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-purple-50 dark:bg-purple-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-purple-900 dark:text-purple-400">
    ❗ Important
  </h4>
  <div class="mt-2.5 text-purple-800 prose-a:text-purple-900 dark:prose-a:text-white prose-code:text-purple-400 dark:text-purple-200 dark:prose-code:text-gray-300">
    <p></p>

<p>To be sure, I’m fully aware that the comparison is to Heroku/Salesforce support, which is possibly the worst of all hosting companies. The bar is <em>very</em> low here.</p>

  </div>
</div>

<p>Still, we found Render’s support workflow to be just ‘okay’. Email responses were on the order of (multiple) days and we got better support latency from their web chat widget, which feels distinctly less ‘professional’. Again, compared to Heroku it’s great, but still. It’s not an <strong>A+</strong> experience.</p>

<p>Finally, the last note to make about Render’s debugging workflow is with production console access. There is no Render-parallel for Heroku’s interactive “one-off” dynos (don’t be confused by Render’s “<a href="https://render.com/docs/one-off-jobs" target="_blank" rel="noopener">one-off jobs</a>”, those are fundamentally different!). Instead, you’re expected to hop into a production instance via SSH into one of your active service machines. As an autoscaling company, our first thought is, “Well what if you’re on a prod box and your autoscaler scales down that box&hellip;?” but TBD there. More critically, this workflow requires much more care and thought around system resources when taking production actions. Ever tried to execute something on a Heroku one-off dyno only for the connection to just die, then later realize that’s because you were spiking the memory <em>way</em> beyond what the default one-off dyno has available? Now think about doing that on your <em>active</em> web server. Whoops, that’s downtime!</p>

<p>It’s not necessarily a problem or a bad thing, it’s just something to be aware of and cautious about with production access on Render. You’re working on a live, hot machine. Work with caution!</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p></p>

<p><em>Update:</em> Render now supports <a href="https://render.com/docs/ssh#starting-an-ephemeral-shell-instance" target="_blank" rel="noopener">ephemeral shell instances</a> via <code>render ssh --ephemeral</code>. These spin up a temporary, isolated instance of your service’s latest build — it doesn’t receive traffic or run your start command, and Render tears it down when you disconnect. Much closer to Heroku’s one-off dyno workflow than SSHing into a live production box.</p>

  </div>
</div>

<p>All in all, we’re going to give Render a grade of <strong>A-</strong> for debugging friction. Render gives you much better windows into production than Heroku does, but also less-safe doors. 😜</p>

<h2 id="render-infrastructure-friction">Render: Infrastructure Friction</h2>

<p>We’ve already talked a bit above about the “service” vs. “app” primitive creating some friction when it comes to shipping in particular. But as we look at infrastructure as a whole, we find that this primitive difference ultimately means there are just more things to model, more things to keep track of, and more choices to make.</p>

<p><figure>
  <img alt="Illustration of a cloud storing application environment variables. A locked configuration panel sits inside the cloud, with dotted lines connecting it to several different application components below, representing centralized storage and distribution of shared configuration and secrets." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5583b835-dbef-4b0c-6837-a971be878200/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5583b835-dbef-4b0c-6837-a971be878200/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5583b835-dbef-4b0c-6837-a971be878200/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Let’s start with environment variables. Heroku keeps environment variables at the “app” level — one place for all ENV vars for the app, and all processes can read them. Since there is no “app” in Render, environment variables are configured at the service level. Ordinarily that’d mean duplicating all of your ENV vars across <em>all</em> of your app’s processes, but Render does offer “Environment Groups” that can be shared across many services, but it’s extra layering to keep in mind.</p>

<p>While we’ve got “environment” on the mind, we must also note that Render made a <em>terrible</em> naming choice in calling their environment <em>variable</em> sharing system “environment groups” while also creating a wholly different feature set generally called an “environment” that’s <em>totally different</em> (grouping multiple services together to call “staging” or “production”). A Render ‘environment’ is a thin facsimile of Heroku’s “app” abstraction while confusing the word “environment” in the Render ecosystem to begin with.</p>

<p>On the plus side, domain management and SSL provisioning were largely painless, <a href="/render">autoscaling</a> worked excellently (one should hope!), and actual system performance was quite good. While <a href="https://render.com/docs/compute-plans#available-instance-types" target="_blank" rel="noopener">Render’s Standard tier</a> is not a perfect analog for <a href="https://devcenter.heroku.com/articles/dyno-sizes" target="_blank" rel="noopener">Heroku’s Std-2x</a>, their general performance has <em>increased</em> compared to the last time we took a hard look at Render. At this point, we found their CPU provisioning to be fairly true to their marketing, which is awesome.</p>

<p>Here’s a rough resource mapping we landed on after several trials:</p>

<p><figure>
  <img alt="“A comparison chart contrasts Heroku and Render hosting plans side by side. On the left, Heroku tiers are shown as Std-1x (1 core, 0.5 GB RAM, $25/month), Std-2x (2 cores, 1 GB RAM, $50/month), Perf-M (2 cores, 2.5 GB RAM, $250/month), and Perf-L (8 cores, 14 GB RAM, $500/month). On the right, Render tiers are Standard (1 core, 2 GB RAM, $25/month), Pro (2 cores, 4 GB RAM, $85/month), Pro Plus (4 cores, 8 GB RAM, $175/month), and Pro Ultra (8 cores, 32 GB RAM, $450/month). Arrows connect roughly equivalent tiers, with handwritten notes highlighting that Render generally offers substantially more RAM at similar or lower prices, more granular scaling options, and a dedicated CPU core at the entry level. The overall message is that Render provides more hardware resources per dollar than comparable Heroku plans.”" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/8aabd8a8-84c5-4650-5d3a-5cd804a0bf00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/8aabd8a8-84c5-4650-5d3a-5cd804a0bf00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/8aabd8a8-84c5-4650-5d3a-5cd804a0bf00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p></p>

<p>The chart above is our experience, and opinion, based mapping for how teams might migrate to Render from Heroku’s resource classes. Your mileage may vary!</p>

<p><strong>*</strong>Heroku doesn’t reveal how many ‘actual’ cores you get on their hardware tiers, even for their dedicated (“Performance”) dynos. These core counts reflect our experience over the years and <a href="/blog/how-many-dynos">general recommendations</a>, especially when considering Heroku’s <a href="/blog/shared-hardware-how-bad-can-it-get">noisy neighbors</a> problems…</p>

  </div>
</div>

<p>Given <a href="https://render.com/docs/compute-plans#available-instance-types" target="_blank" rel="noopener">Render’s</a> and <a href="https://devcenter.heroku.com/articles/dyno-sizes" target="_blank" rel="noopener">Heroku’s</a> different resource-size tiers, this isn’t a story of “Render is faster” or “Render is cheaper” — it’s not that simple and it will depend on the specific app’s needs. But what we can say is that Render offers more granular resource choices for all apps and, <em>on average</em>, we believe that most teams will save between 30-40% on their <em>compute</em> costs* when moving to Render while keeping the same net total horsepower. <em>Particularly</em> for those apps currently running on Heroku’s Perf-M’s. Render’s Pro tier is a serious savings for similar horsepower there.</p>

<p><em>*We’ll get to overall costs in the next section; we’re just talking about the service machine costs here!</em></p>

<p>All that said, we’re going to give Render a solid <strong>B</strong> here. Where Heroku hides more infrastructure shape and details, Render exposes those details but <em>does</em> give you the tools to manage them well.</p>

<h2 id="render-organizational-friction">Render: Organizational Friction</h2>

<p>So, as noted above, we do believe that most teams will save 30-40% on their compute costs on Render. We’re saying that again because that <em>is</em> a massive deal. But now we need to talk about the <em>other</em> costs, because those can also be massive…</p>

<p>The big elephant in the room for us is egress costs. Render bills for <a href="https://render.com/docs/outbound-bandwidth" target="_blank" rel="noopener">egress bandwidth</a>. How much egress bandwidth does your app currently use? How much should you expect Render to charge you for it? Great question! We don’t know. Not a single team we’ve asked or know can answer “Hey how much outbound bandwidth does your app use each month?”. Because it’s a question nobody’s asking and nobody’s measuring!</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p></p>

<p>It’s worth noting that, as far as we know, Heroku is actually the only platform that <em>doesn’t</em> charge for bandwidth in some manner or form. But that doesn’t mean every other platform’s measurements or prices are the same!</p>

  </div>
</div>

<p>That said, our general take is this: if you’re going to use Render’s own services for database and/or cache (e.g. Postgres and Redis), you’ll probably be fine with Render’s built-in (free) egress allocations:</p>

<p><figure>
  <img alt="" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/4b1f24a5-f253-4e96-e77b-8960bf1dc000/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/4b1f24a5-f253-4e96-e77b-8960bf1dc000/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/4b1f24a5-f253-4e96-e77b-8960bf1dc000/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>However, <strong>if you’re using third-party services for database, cache, or otherwise, we wouldn’t recommend using Render for your compute hosting</strong>. Unless you can get Render’s <a href="https://render.com/docs/private-link" target="_blank" rel="noopener">VPC/Private Link architecture</a> going for your services (we could not) you will almost definitely hit the free egress allocation for your account. And you might blow <em>WAY</em> past it.</p>

<p>Just being transparent, the month before we moved Judoscale to Render, our Heroku bill was ~$700. The month that we spent on Render, our bill was ~$4,500. A full $4,000 of that was <em>just</em> egress fees. Ouch, ouch, ouch. We use <a href="https://www.tigerdata.com/cloud" target="_blank" rel="noopener">Timescale DB</a> (now “Tiger Data”) and can’t set up a private link for that service with Render.</p>

<p><figure>
  <img alt="Illustrated bar chart comparing hosting costs. A single bar labeled “Price on Heroku” reaches $700. Next to it, a much taller stacked bar labeled “Price on Render” totals $4,500, with a small $500 compute section at the bottom and a much larger $4,000 egress section above it, illustrating that bandwidth charges make up the vast majority of the total cost." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/312d9f89-9250-44d3-47e9-07107c8bce00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/312d9f89-9250-44d3-47e9-07107c8bce00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/312d9f89-9250-44d3-47e9-07107c8bce00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>So Render may well cut your compute bill down a fair amount, but if you run external services, you may rack up another bill you didn’t expect.</p>

<p>Lastly, Render’s pricing model itself is a little funky. It’s changing on August 1st, but before then, there’s a per-seat cost in addition to your actual compute (and egress) costs. We’re not fans of a per-seat model. After August 1st, 2026 the per-seat model is largely disappearing, but you end up either paying $25/mo flat-rate for the whole account while having only 25GB of egress bandwidth for the month, OR $499/mo flat-rate for the whole account while having 1TB of egress bandwidth. $500/mo <em>before</em> compute costs is <em>wild</em> to us, but egress becomes an even <em>more</em> important discussion point if you opt for their $25/mo “Pro” plan. Clear as mud!</p>

<p>For all of these reasons, and understanding that our egress may be an outlier in the community, we give Render a <strong>C</strong> for organizational friction. Egress costs are just not intuitive or easy to reason about, and their upcoming account billing structure is frankly frustrating.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p></p>

<p>We built a <a href="https://judoscale.com/tools/paas-pricing-calculator" target="_blank" rel="noopener">PaaS Pricing Calculator</a> last year to help you understand what equivalent costs are across platforms, including comparing Heroku to Render. It can help you estimate high-level egress values, too! </p>

<p><figure>
  <img alt="Screenshot of the PaaS Pricing Comparison Calculator" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/72b356d7-c055-4828-81a7-d8531c9d0f00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/72b356d7-c055-4828-81a7-d8531c9d0f00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/72b356d7-c055-4828-81a7-d8531c9d0f00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

  </div>
</div>

<h2 id="so-render">So&hellip;. Render?</h2>

<p><strong>Render</strong>:</p>

<ul>
<li>Shipping Friction: <strong>C</strong></li>
<li>Debugging Friction: <strong>A-</strong></li>
<li>Infrastructure Friction: <strong>B</strong></li>
<li>Organizational Friction: <strong>C</strong></li>
</ul>

<p>So where does this all leave us? Render is absolutely a viable Heroku alternative, but it is not simply “Heroku, only cheaper”. Render’s primitive is the service, not the app, and that mental model shows up everywhere: deploys, migrations, environment variables, dashboards, domains, scaling, and billing. Render gives you more knobs, better built-in observability, useful infra-as-code tooling, and potentially (much) cheaper compute. But in exchange, you own more of the connective tissue that Heroku just handled for you.</p>

<p>For many teams, that tradeoff will be worth it. If your app is fairly standard — web, workers, Render-managed Postgres/Redis, modest outbound traffic — Render may save you a meaningful amount of money while giving you a more modern dashboard and better native debugging tools. But if your app uses third-party data services, pushes lots of outbound traffic, or needs tightly coordinated multi-service deploys, the savings may be eaten by workflow complexity or egress fees.</p>

<p>That’s probably our shortest verdict: Render had the pieces, but Heroku had the workflow. Render is more flexible, more observable, and often cheaper. It is also more service-shaped, more fragmented, and more likely to make you design the application-level lifecycle Heroku gave you for free. Fewer infrastructure dollars, more infrastructure ownership.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-green-50 dark:bg-green-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-green-900 dark:text-green-500">
    ✅ Tip
  </h4>
  <div class="mt-2.5 text-green-800 prose-a:text-green-900 dark:prose-a:text-white prose-code:text-green-900 dark:text-gray-300 dark:text-green-100/80 dark:prose-code:text-gray-300">
    <p></p>

<p><strong>Reminder</strong>: This article is the second post in our “Judoscale going on tour” series, where we put our money where our mouth is and migrate Judoscale to various platforms. No holding back, no keeping background jobs somewhere else, no splitting traffic.</p>

<p>Judoscale is a 24/7 real-time reactive production application. We receive well over 3,000 RPS every moment of every day. Our downtime is <em>exceedingly</em> rare (generally only when Cloudflare or Heroku themselves have issues), but then, it darn well should be! We’re an autoscaler! We <em>need</em> to be online, regardless of traffic load, so that we can reactively scale our clients’ applications correctly and appropriately any time of day.</p>

<p>Sign up for our newsletter to join us on this tour as we discover the nooks and crannies of 2026’s available PaaS’s. If you’ve been thinking about moving, let us feel the pain first — we’ll tell you all about it 😆.</p>

  </div>
</div>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Blue Ridge Ruby: A Couple of Reflections</title>
      <description>Reflections from Blue Ridge Ruby: the irreplaceable value of in-person community and how AI supercharges experienced Rubyists to build faster.</description>
      <pubDate>Mon, 4 May 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/blue-ridge-reflections</link>
      <guid>https://judoscale.com/blog/blue-ridge-reflections</guid>
      <author>Jon Sully</author>
      <content:encoded>
        <![CDATA[<p>Oh Asheville! How wonderful, weird, and full of art you are! Aside from returning with sore shins (central Ohio isn’t known for its hills…), Adam and I head home from North Carolina with two primary reflections we feel worth sharing here.</p>

<p><figure>
  <img alt="Photo of the Blue Ridge Mountains in the distance with a town landscape in the foreground" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/1589723c-52f8-4436-4c5a-d6398d7a4300/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/1589723c-52f8-4436-4c5a-d6398d7a4300/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/1589723c-52f8-4436-4c5a-d6398d7a4300/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            Ye old Blue Ridge Mountains
          </figcaption>

</figure>
</p>

<p><strong>1.</strong> “<strong>Humans are the X-factor</strong>”. There’s something irreplaceable about a gathering of humans in-person; something unmistakably creative and promising. Something you feel more than you can describe; a moment where the whole is indeed greater than its constituent components. Small Ruby conferences ooze this feeling! My point here is simply that <strong>there is no replacement for in-person gatherings</strong>, and <strong>you will leave having gained something</strong>. Friendships, ideas, insights, and a sense of identity in community <em>way</em> beyond a Slack workspace or forum. <em>There is so much value there</em>! We all already know this — there’s a reason remote-only meetups can feel chore-ish and paradoxically disconnecting. Humans are the X-factor. They have to show up, but the rewards never fail to arrive when they do.</p>

<p>To that end, we can’t help but feel great joy at the resurgence of the small, single-track, regional Ruby conference. There’s a place for RubyConf and Rails World and the “big show”, for sure. But that human X-factor doesn’t scale linearly. Smaller regional conferences don’t need much production, keep everyone in the same room, and clearly relay the genuine love the conference planners have for their community. Yet they always have insights just as deep and compelling as the bigger conferences… and you can grab lunch with the speakers right after their talk! If you haven’t been to <a href="https://blueridgeruby.com" target="_blank" rel="noopener">Blue Ridge</a>, <a href="https://rockymtnruby.dev" target="_blank" rel="noopener">Rocky Mountain</a>, <a href="https://www.blastoffrails.com" target="_blank" rel="noopener">Blastoff</a>, or <a href="https://west.railscamp.us" target="_blank" rel="noopener">RailsCamp</a>, seriously consider making the trip!</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p></p>

<p>The quote, “Humans are the X-factor” was from <a href="https://joshuawenning.com" target="_blank" rel="noopener">Joshua Wenning</a>’s lightning talk at BRR. Josh only began writing Ruby in January! And gave a lightning talk at a Ruby conference in April! How cool is that?!</p>

  </div>
</div>

<p><strong>2. There has never been a better time to build</strong>. There are a <em>lot</em> of takes floating around about AI and software development right now. BRR hosted a two-hour round-table discussion on exactly that! Adam and I have had more than our own share of discussions around it and what the future looks like. We don’t know. Nobody does! But one <a href="https://speakerdeck.com/etagwerker/teaching-claude-code-to-upgrade-rails-at-blue-ridge-ruby-26?slide=21" target="_blank" rel="noopener">slide</a> from <a href="https://www.linkedin.com/in/etagwerker/" target="_blank" rel="noopener">Ernesto Tagwerker</a> at the conference contained the simple equation:</p>

<p><figure>
  <img alt="Simple words on a white background reading “experience plus LLMs equals speed”" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/453a53c0-64cf-4ddb-76f1-525ee639b800/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/453a53c0-64cf-4ddb-76f1-525ee639b800/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/453a53c0-64cf-4ddb-76f1-525ee639b800/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>And I think that sums up my current outlook on AI for Ruby/Rails right now. If you have experience in the stack, come in with opinions on what you want and exactly how you want it shaped, and opt to use Claude or Codex to execute/implement, you’re going to find that <strong>you can build at unprecedented speed</strong>. But the quality of the output, at this point, is going to governed more by how detailed and particular your input is, not by the quality of the model. The models are shockingly good. They can follow your opinions, build your ideas, and refactor entire codebases in minutes.</p>

<p>Let me rewind and repeat that: if you can leverage your experience and knowledge of the stack to write clear and concise specifications about what you’d like built, it can be built in minutes <em>for you</em>. At stunning accuracy. Truly, <em>there has never been a better time to build things for those who want to build</em>.</p>

<p>This was inadvertently on display at Blue Ridge. <a href="https://github.com/Kitkatnik" target="_blank" rel="noopener">Katya Sarmiento</a> (a wonderful human!) scaffolded up a fully custom app  just for Blue Ridge, “[what] started as an app for BRR Ruby Embassy&hellip; turned into a whole companion app called ‘My Blue Ridge’” — complete with user-specific scheduling, group meal coordination, and even a fun <a href="https://app.blueridgeruby.com/report" target="_blank" rel="noopener">data-report</a> that was added minutes before her lightning talk about that very thing! This sort of thing was absolutely possible before software development AI came on the scene but it would’ve had a <em>much</em> higher human-time cost (and likely wouldn’t have been possible to do for a volunteer venture like Blue Ridge). If your desire is to build things, the landscape is <em>wide open</em>. </p>

<p>Anyway, to the Blue Ridge team of <a href="https://twitter.com/jeremysmithco" target="_blank" rel="noopener">Jeremy</a>, <a href="https://twitter.com/marklocklear" target="_blank" rel="noopener">Mark</a>, and <a href="https://peckyeah.com/" target="_blank" rel="noopener">Joe</a>, thanks for another wonderful conference!</p>

<p><figure>
  <img alt="An AI-generated image of a pencil-sketch style scene with a large building that reads “Blue Ridge Ruby” on the front with many mountains sketched into the background, a simple scene and representation of the conference within the Asheville NC mountains" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/1a3fcaae-4748-4527-682c-e5b7e5292000/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/1a3fcaae-4748-4527-682c-e5b7e5292000/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/1a3fcaae-4748-4527-682c-e5b7e5292000/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Dear Heroku: Uhh... What’s Going On?</title>
      <description>An open letter urging Heroku to clarify its roadmap, define “sustaining engineering,” and communicate honest business intent to developers.</description>
      <pubDate>Mon, 6 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/heroku-whats-going-on</link>
      <guid>https://judoscale.com/blog/heroku-whats-going-on</guid>
      <author>Jon Sully</author>
      <content:encoded>
        <![CDATA[<p>Dear Heroku,</p>

<p>As friends, long time marketplace-partners, supporters, and customers, we here at Judoscale — along with <em>so many</em> others from the developer community — are a bit confused about what’s going on over there in San Francisco. Frustrations aside, we’re having a hard time figuring out what to think about this whole ‘Heroku thing’. </p>

<p><strong>First</strong> came the <a href="https://www.heroku.com/blog/an-update-on-heroku/" target="_blank" rel="noopener">blog post</a> on February 6th that, sort of out of nowhere, announced that:</p>
<blockquote><p>Heroku is transitioning to a sustaining engineering model focused on stability, security, reliability, and support… with an emphasis on maintaining quality and operational excellence rather than introducing new features…</p>

<p>Enterprise Account contracts will no longer be offered to new customers…</p>
</blockquote>
<p>Which, full disclosure, sounds an <em>awful lot</em> like a product going into “Maintenance Mode”, even if that specific phrase isn’t said. Speaking of specific phrases, what exactly <em>is</em> a “sustaining engineering” model? Respectfully, that feels like a phrase cooked up in a corporate PR meeting.</p>

<p><strong>But then</strong> came new features? 🤔 In mid March we saw <a href="https://www.heroku.com/blog/bigger-slugs-and-greater-build-timeout-flexibility/" target="_blank" rel="noopener">slug sizes increase</a> for the first time I can remember (which is genuinely helpful, thank you!), updates to the automatic SSL cert provisioning pipeline so it <a href="https://www.heroku.com/blog/preparing-for-shorter-ssl-tls-certificate-lifetimes/" target="_blank" rel="noopener">runs more frequently</a> (cool!), and… the entire CLI was <a href="https://www.heroku.com/blog/modernizing-the-command-line-heroku-cli-v11/" target="_blank" rel="noopener">rebuilt</a> (!?). Um.. these feel like new features and platform progress, not maintenance mode… 😵‍💫</p>

<p><strong>Add to that</strong> a <a href="https://www.heroku.com/blog/march-2026-update/" target="_blank" rel="noopener">round-up post</a> from the Head of Product and Engineering that doubled down on “Sustaining Engineering” (capitalized this time!):</p>
<blockquote><p>We will continue releasing features and functionality that align with our Sustaining Engineering goals…</p>
</blockquote>
<p>…but I thought “sustaining engineering” was</p>
<blockquote><p>rather than introducing new features</p>
</blockquote>
<p>I’m not trying to be pedantic here, I’m just confused. <strong>What are we developers/customers/teams supposed to expect from Heroku at this point</strong>?</p>

<p>Heroku and/or Salesforce leadership/PR/etc. team(s), can I humbly offer you a few thoughts? </p>

<h3 id="just-tell-us-straight">Just Tell Us Straight</h3>

<p>Developers notoriously hate corporate verbiage, PR plays, and implications. “Sustaining engineering” feels like the embodiment of all three of those. If Heroku is going into maintenance mode, cool — the platform is pretty great and if it never improved again, there’s still <em>many</em> years of life in it. Just call it that, then. What do the actual devs at Heroku think of all of the recent news, terms, and reactions? I’d be curious for their insights.</p>

<h3 id="open-the-roadmap">Open The Roadmap</h3>

<p>And, as noted above, if that roadmap is “do nothing, just keep it running”, that’s <em>fine</em>. But if there <em>are</em> things on the roadmap, it’d be really great if that was made public. Wasn’t Fir going to ship to the common runtime? Aren’t there Postgres updates to be made? There must be some cost optimizations to be made on several fronts… developers would be thrilled to actually see what Heroku’s priorities are, even if they can’t interact with them or influence them.</p>

<h3 id="clarify-business-intent">Clarify Business Intent</h3>

<p>Look, we get it. Developers are pragmatic people. We understand there are seasons to products and platforms and businesses and companies. Not everything lasts forever — we’ve all had Node packages that we regret installing. From where I’m standing, I’m guessing Heroku as a product is in one of two places:</p>

<ol>
<li>We just want to focus on direct-billing customers and maintain a smaller budget and team to do that. We’ll continue to move forward, but direct-billing is our outlook now; enterprise isn’t our goal anymore</li>
<li>We’re looking to exit major investing in the platform altogether, but obviously it will keep running and we’ll keep fixing issues and bugs for many years to come</li>
</ol>

<p>It’d be really helpful if someone could inform us which of those two roads Heroku leadership is choosing.</p>

<p>Again, this is coming from a (small) team of people who have loved and hosted on Heroku nearly as long as Heroku has existed. We care deeply about the platform and its future! We, and all those that we’ve talked to thus far, would just like to know what that future is.</p>

<p>Sincerely,</p>

<p>The Judoscale Team</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-purple-50 dark:bg-purple-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-purple-900 dark:text-purple-400">
    ❗ Important
  </h4>
  <div class="mt-2.5 text-purple-800 prose-a:text-purple-900 dark:prose-a:text-white prose-code:text-purple-400 dark:text-purple-200 dark:prose-code:text-gray-300">
    <p></p>

<p>Adam adding a little P.S. here (👋)</p>

<p>I just got back from RBQ (a lovely <a href="https://rbqconf.com" target="_blank" rel="noopener">Ruby conference in Austin</a>) a couple weeks ago. I spoke with several teams hosting on Heroku, and <em>Every single one of them</em> are making plans to migrate due to these recent communications. I echo Jon’s thoughts here. <em>Please</em> update us with some clarity!</p>

  </div>
</div>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Judoscale on Tour: An Ode To Heroku</title>
      <description>Heroku is shifting to a sustaining engineering model. Here’s what that means, whether you should migrate, and how the top alternatives compare.</description>
      <pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/judoscale-on-tour-ode-to-heroku</link>
      <guid>https://judoscale.com/blog/judoscale-on-tour-ode-to-heroku</guid>
      <author>Jon Sully</author>
      <content:encoded>
        <![CDATA[<p><strong>Judoscale &lsquo;On Tour&rsquo; Series</strong></p>

<ol>
<li>&ldquo;The Friction Model&rdquo; &amp; <strong>Heroku</strong> <em>(This page!)</em></li>
<li><a href="/blog/judoscale-on-tour-render"><strong>Render</strong></a></li>
<li><strong>Railway</strong> (Coming soon&hellip;)</li>
<li><strong>Fly</strong> (Coming soon&hellip;)</li>
<li><strong>Northflank</strong> (Coming soon&hellip;)</li>
<li><strong>Digital Ocean</strong> (Coming soon&hellip;)</li>
<li><strong>Amazon ECS Fargate</strong> (Coming soon&hellip;)</li>
</ol>

<hr>

<p><figure>
  <img alt="Black-and-white, hand-drawn pencil illustration of a single server rack on the right with a slightly crooked hanging sign that reads “Still running fine.” On the left, large bold hand-lettered text reads “Judoscale on Tour: Heroku.” The scene is minimal, with soft cross-hatching and a lightly sketched server room background." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/105c4b73-220b-4444-d0d2-03c8ba13b500/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/105c4b73-220b-4444-d0d2-03c8ba13b500/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/105c4b73-220b-4444-d0d2-03c8ba13b500/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Thus we begin our tour! As we mentioned in our <a href="/blog/heroku-whats-next">last post</a>, we’re going to take our production app on a hosting tour to fully experience what each option in today’s hosting marketplace looks like, feels like, and runs like. But before we do any of that, we need a baseline and a strategy.</p>

<p>Judoscale has been on Heroku since its origins ten years ago. Adam, Judoscale’s founder, has been using Heroku since it was a first-days startup! All that to say, we’ve been around the Heroku block many, many times. We’re <em>too</em> close. We need to intentionally zoom out and take a look at Heroku like a brand new user would. We need to put words to the things we take for granted <em>before</em> we jump ship so we can know what to look for somewhere else.</p>

<p>And we’re not alone! Many folks are starting to put out feelers for alternative hosting platforms as Heroku’s moved into <del>maintenance mode</del> a <a href="https://www.heroku.com/blog/an-update-on-heroku/" target="_blank" rel="noopener">“Sustaining Engineering model”</a> and they too need a pragmatic view of the features and toolkits that make a hosting platform fantastic. So we asked for <a href="https://www.linkedin.com/posts/adamlogic_idea-the-judoscale-platform-tour-we-activity-7428137395641827328-4ZwZ" target="_blank" rel="noopener">their input</a> as well:</p>

<p><figure>
  <img alt="Screenshot of Adam’s LinkedIn post asking folks for input on a ‘Judoscale tour’ concept and what specifics they’d want to see" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5678fdf7-6b9a-41e8-9721-6cda33273900/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5678fdf7-6b9a-41e8-9721-6cda33273900/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5678fdf7-6b9a-41e8-9721-6cda33273900/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Our goal here is to build a community rubric of sorts. A set of baseline standards for performance, developer experience, and complexities-vs-niceties on hosting platforms. </p>

<p>With all of that said, this article is our attempt at outlining many of the features that have made Heroku so great (and occasionally difficult!) to build on over the last fifteen years. We’ve come up with an assessment strategy that we think will work for all platforms and we’ll apply it here to Heroku first.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p>It’s worth calling out: not everyone uses, sets up, or runs their Heroku-based applications in the same ways. Even just asking if folks <a href="https://www.linkedin.com/posts/adamlogic_how-common-is-it-these-days-for-teams-to-activity-7432055190750412801-H5ql/" target="_blank" rel="noopener">use a staging-server setup</a> brought <em>many</em> different opinions into the mix. <strong>That’s okay</strong>! There may be things noted here in this write-up that simply don’t apply to you or you don’t care about. Just keep an open mind: there’s no single way to do simple app hosting, and you might even find new ideas here!</p>

  </div>
</div>

<h2 id="the-friction-model">The Friction Model</h2>

<p><figure>
  <img alt="Black-and-white, hand-drawn cartoon of a smiling airplane flying to the right, slowed by four sideways parachutes trailing behind it. Each parachute is labeled “Shipping,” “Debugging,” “Infrastructure,” and “Organizational,” representing different sources of friction. The parachutes are stretched taut with motion lines, clearly pulling against the plane’s forward movement. The scene includes light clouds, subtle cross-hatching, and a soft paper texture, giving it a clean, minimal, sketch-style infographic feel." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/067b8759-54e0-4376-7a57-e9b77be40a00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/067b8759-54e0-4376-7a57-e9b77be40a00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/067b8759-54e0-4376-7a57-e9b77be40a00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            How fast can you fly?
          </figcaption>

</figure>
</p>

<p>After spending quite a bit of time brainstorming, then asking other experienced devs for input, we’ve got a <em>long</em> list of features that we know make Heroku great. From this simple stuff (<code>git push heroku</code>) to the more complicated (“What are all the buildpacks I need for <code>vips</code>, again?”), there are plenty of things. But a plain list of things is only helpful for making our eyes glaze over. We need to see these features in some kind of logical groupings that help us understand intent and perspective.</p>

<p>Given Heroku’s history and philosophy over the years, we believe this is best captured as a <em>friction</em> model. From the beginning, Heroku’s value proposition was always about friction: removing it. Anti-friction = anti-pain. Anti-pain = peaceful shipping. And, of course, peaceful shipping nets developers that are excited to build (“productivity”). It’s always been about friction.</p>

<p>As it’s said: you don’t know what you’ve got until it’s gone… (which <a href="/blog/heroku-whats-next">it’s not</a>, but we can try to simulate the feeling) so let’s try to peel back the anti-friction layers and discover what Heroku’s been silently handling for so many years.</p>

<p>We’re considering four feature groups here:</p>

<p><strong>Shipping friction</strong>. As in, “how many steps are there between my local code and production?” This vector covers things like deploys and releases, migrations, pipelines, review apps, setup, CI/CD (is that phrase still popular?), how <em>long</em> it takes to deploy, and zero-downtime deploys ✨.</p>

<p><strong>Debugging friction</strong>. As in, “WTF IS GOING ON WITH PRODUCTION RIGHT NOW?!” This vector covers a lot of visibility and speed-of-access: logs, metrics, dashboards, production consoles/terminals, scaling, and some cron/scheduled jobs concepts. Also, reaching actual customer-service help when necessary!</p>

<p><strong>Infrastructure friction</strong>. As in, “how much <em>platform’y</em> stuff do we have to own and maintain? How often do I have to (re-learn how to) fix this stuff?” Things like environment variables and secrets, SSL configurations, domains and DNS, multi-region / replication, compliance (scary, I know), and networking/routing. Oh, also, are the servers <em>actually</em> fast / performant?</p>

<p>And finally, <strong>Organizational friction</strong>. As in, “the stuff my manager probably cares more about than me, but therefore still impacts me indirectly”. How much does the platform <em>actually cost</em>? How many nines? Do I need to hire an Ops team? How much is my CTO going to hear the word “Heroku”?</p>

<p>We believe the friction model helps to paint a clear and personal picture of what hosting on <em>any</em> platform will feel like. It’s not just a feature-list table with checks and x’s; our goal is to capture the subjective experience of using platforms at various moments in a commercial developer’s workflow.</p>

<p>Okay, enough setup! Let’s dive in and see how Heroku fits into this model.</p>

<h2 id="heroku-shipping-friction">Heroku: Shipping Friction</h2>

<p><figure>
  <img alt="Black-and-white, hand-drawn pencil-style illustration of a wide canyon viewed from the near cliff. The ground on the viewer’s side is labeled “LOCALHOST” near the edge. A long bridge stretches across the canyon into the distance, connecting to a far cliff where a sign reads “PRODUCTION.” The canyon walls are shaded with cross-hatching, and the scene includes light clouds and a subtle paper texture, giving it a clean, minimal, infographic-like appearance." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/74549ce1-cb88-40a8-6ad4-55fdd9dff400/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/74549ce1-cb88-40a8-6ad4-55fdd9dff400/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/74549ce1-cb88-40a8-6ad4-55fdd9dff400/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Heroku essentially taught an entire generation of developers that shipping could be as simple as <code>git push heroku</code>, so it’s fair to say that they’ve optimized for low shipping friction from the beginning. In fact, Heroku’s pioneering of the PaaS <em>concept</em> was mostly rooted around low shipping friction. While most applications these days probably use automatic deployments off <code>main</code> via GitHub connection rather than pushing from local, Heroku’s done a great job of keeping that setup just about as simple to configure as good old <code>git push heroku</code>. A few clicks and you’re off to the races.</p>

<p>Heroku was also built with “release” processes in mind. We might take them for granted now, but a dedicated short-lived process that runs a command once only when deploying a new commit is both <em>very</em> helpful and somewhat complicated! Unless your host has this specific workflow supported and pre-setup in their platform, trying to do it yourself can be a real pain in the rear. Heroku simply built and gave us a perfect home for <code>db:migrate</code>.</p>

<p>Skimming through some of the other features here, the story is broadly the same: we only think about these features and/or know about them because Heroku brought them to the masses. Automatic PR-review apps, pipeline setups to go from review app to staging to production, direct-deploy <code>main</code> to production (after CI passes!) — I’d wager these concepts are familiar to most developers because Heroku pioneered them.</p>

<p>If we boil down the category into a single question, “how hard is it to take an app I have running locally and get it running in the cloud?” Then we’d give Heroku an <strong>A</strong>. To this day, Heroku sets the bar for low-friction shipping. </p>

<h2 id="heroku-debugging-friction">Heroku: Debugging Friction</h2>

<p><figure>
  <img alt="Black-and-white, hand-drawn pencil-style illustration of a server rack standing on a tiled floor. Several cartoon-style bugs with rounded bodies and large, friendly eyes are peeking out from the sides and edges of the rack, suggesting hidden software “bugs.” Some small bugs hover nearby with dotted motion lines. The scene uses soft cross-hatching and a subtle paper texture, with a clean, minimal, and slightly whimsical infographic style." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/13b18556-8c84-430b-3444-ff4233d68000/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/13b18556-8c84-430b-3444-ff4233d68000/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/13b18556-8c84-430b-3444-ff4233d68000/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            Bugs will be bugs&hellip;
          </figcaption>

</figure>
</p>

<p>Figuring out why your prod app is on fire isn’t ever <em>easy</em> per se, but there are things a platform can do to (hopefully) make it <em>easier</em>. We’d normally split this concept into two camps:</p>

<ol>
<li>The platform’s <em>native</em> tooling for viewing and searching logs, seeing metrics, and assessing what’s failing</li>
<li>How easy the platform makes it to add third-party software (APMs, scalers, etc.) which can provide even more visibility</li>
</ol>

<p>But Heroku sort of has a third — or maybe a <code>2a</code>. Add-ons. Entire third-party software <em>suites</em> that can bolt onto your application with (typically) <em>zero</em> extra configuration required, with a single click. That’s neat!</p>

<p>But let’s start with #1. How good is Heroku’s <em>native</em> tooling for figuring out why production is on fire? Eh 🤷‍♂️. There’s good and bad.</p>

<p>Being able to fire up <code>heroku run</code> ad-hoc at any point <em>is</em> handy, but it can take a little while to spin up and runs on its own separate VM. Heroku does allow SSH’ing into running dynos, which is handy, but there’s an ephemerality that you need to keep in mind with dynos. A dyno that’s currently ‘on fire’ may well restart and shut you out at any time if the platform control plane decides it’s on fire <em>enough</em>. Essentially, there are times when the control plane feels more authoritative than the actual resources! That’s helpful sometimes, but harmful others.</p>

<p>Heroku’s approach for giving you helpful log parsing and tooling is essentially just to not do that. The CLI allows you to tail your real-time logs (as does the web UI) but you’ll have to pipe that into other tools if you want to do anything more than just read logs whizzing by. It’s accessible quickly enough that it can be useful, just hit <code>heroku logs -t</code>, but depending on your app’s RPS it may be <em>way</em> too much info to be useful to human eyes on a terminal.</p>

<p>On the metrics front, Heroku’s dashboard metrics display is… fine. Heroku isn’t an APM and doesn’t install a package into your code, so it really doesn’t have access to the sort of application-level stats we might be interested in these days. But is <em>is</em> a reasonable readout of throughput, memory, errors, and <em>dyno load</em>, though they lose points for the last one. “Dyno load” is an opaque and unhelpful metric derived from opaque resource-sharing algorithms for their <code>Standard</code> dynos. How much dyno load <em>should</em> you use? 🤷‍♂️</p>

<p>Lastly in the native-tooling group, Heroku <em>does</em> provide a cron-ish scheduling system that’s first-party (even though it’s installed as an add-on) but it’s just not great. We actually used it for years before deciding to <a href="/blog/heroku-scheduled-jobs">move away</a>. It’s <em>fine</em> for very small apps and/or non-critical jobs, but it’s not something that should scale with any application. Though, to be fair, we wouldn’t consider having a “heavy duty scheduler” a responsibility of a hosting platform. That’s something you <em>should</em> implement inside your application layer one way or another. </p>

<p>On to #2: how easy Heroku makes it to add third-party software. The short answer is that Heroku makes it <em>very</em> easy. Heroku decided early-on to invest in infrastructure for an “add-on” system that made installing third-party libraries as easy as installing a new app on your phone. In fact, Judoscale was born on the <a href="https://elements.heroku.com/addons/judoscale" target="_blank" rel="noopener">Heroku Marketplace</a> before we branched out to supporting many platforms! Maybe you want Scout for your APM, a MySQL DB for your data, and ElasticSearch for a search index across that data. All of those can be setup with just a click or two from the <a href="https://elements.heroku.com/addons" target="_blank" rel="noopener">Heroku Marketplace</a>. Handy!</p>

<p>Aside from the Marketplace, it’s also easily to install third party libraries or software the old-fashioned way: <a href="/heroku-pricing#why-are-these-prices-different-from-the-heroku-elements-page">signing up directly</a>. Heroku doesn’t restrict any dynos’ <em>outbound</em> internet access by default so getting third party libraries configured which need to verify license keys, send data somewhere, or otherwise talk to some server work fine. Given that environment variable control on Heroku is quite simple too, the “DIY” third party software path is nearly as simple as the Marketplace path. We’ve only ever experienced the occasional friction of dynos not having static IP’s… but there are add-ons that <a href="https://elements.heroku.com/addons#network" target="_blank" rel="noopener">do just that</a>!</p>

<p>Beyond #1 and #2, we also need to consider Heroku’s actual customer support system for when we experience actual platform issues. How long does it take to get helpful, actual customer support when something happens? Well, it looks and feels just about like:</p>

<p><figure>
  <img alt="Black-and-white pencil-style cartoon of an empty “Heroku Support” kiosk with cobwebs, while a simple figure seen from behind shrugs in confusion at the unattended desk." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/e4b64ddb-2700-431b-0e02-c3bc386aae00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/e4b64ddb-2700-431b-0e02-c3bc386aae00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/e4b64ddb-2700-431b-0e02-c3bc386aae00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            Uhh&hellip;. Help?
          </figcaption>

</figure>
</p>

<p>Unless you pay <em>hefty</em> fees for an elevated support tier on an enterprise contract, Heroku’s support response times can be <em>rough</em>. You can be paying thousands per month for all the dynos you’d like but still have to wait actual <em>days</em> before you get a response to a critical issue you submitted a ticket for. If you’ve ever tried, you know. That stinks.</p>

<p>If we boil down the concept of ‘debugging friction’ into a single question, “how hard is it to figure out what’s on fire?” Which, being fair, is a crazy large question that your hosting provider holds just a slice of responsibility for, we’d give Heroku a <strong>B</strong>. The tooling is mature and reliable, as both points #1 and #2 above cover, but we can’t deny the awful experience of their customer service ticketing. Nonetheless, the likelihood of needing to open a ticket remains low, so we have to balance the weight there.</p>

<h2 id="heroku-infrastructure-friction">Heroku: Infrastructure Friction</h2>

<p><figure>
  <img alt="Black-and-white, hand-drawn pencil-style illustration of a server rack labeled “HEROKU” positioned in a mechanic’s shop bay. The rack stands on a tiled floor, surrounded by tools such as wrenches, a toolbox on wheels, and a mechanic’s creeper, suggesting it is about to be worked on. A workbench with additional tools and containers sits in the background, along with a hanging shop light and a closed garage door. The scene uses soft cross-hatching and a subtle paper texture, with a clean, minimal, infographic-like style that conveys infrastructure as something requiring hands-on maintenance." loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f793469c-9200-44c5-47ee-dc833857a700/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f793469c-9200-44c5-47ee-dc833857a700/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f793469c-9200-44c5-47ee-dc833857a700/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            Get your tools ready.
          </figcaption>

</figure>
</p>

<p>Our application is shipped, prod is running smoothly, and we’re starting to think about week-over-week maintenance and upkeep. At this point we need to consider how much of our platform footprint requires our own active involvement across months and seasons — how much friction our infrastructure causes in our day-to-day for an existing app: “How often do I have to (re-learn how to) fix this stuff?” Let’s enumerate the basics.</p>

<p>When it comes to environment variables, Heroku model is almost aggressively simple. There’s no separate secret manager, no multiple layers of injection depending on build vs runtime, and no ambiguity about where a value is coming from. It’s a single, flat interface per application; you set it once, and your app has it. Done. It’s not the most flexible system in the world, but it’s extremely predictable, and predictability reduces friction. </p>

<p>The same story shows up in domains and SSL. Adding a custom domain and getting HTTPS working is deliberately designed to be a few clicks, then something you just don’t think about again. They even do some <a href="/blog/heroku-ssl-revisited">special magic</a> we’ve written about — some neat tricks for correctly configuring SSL when you’ve opted to use Cloudflare in front of Heroku… it all just works! No provisioning your own certs, no renewal tasks every few months (<a href="https://www.heroku.com/blog/preparing-for-shorter-ssl-tls-certificate-lifetimes/" target="_blank" rel="noopener">or more</a>&hellip;), just a 🔒 in your browser address bar that you will (lovingly) ignore for the rest of your app’s lifetime.</p>

<p>Networking and routing is a <em>similar</em> story but has its own tradeoffs. When you run an app on Heroku there’s no ports to configure, no connections to setup; you don’t own the routing or load balancing layers at all. <em>But</em>, Heroku’s “load balancer” actually isn’t. As we’ve mentioned in <a href="/blog/request-queue-time">“Understanding Queue Time: The Metric that Matters”</a>, Heroku’s router uses a <em>random</em> routing algorithm. There’s no load balancing! So, while Heroku does grant the wonderful simplicity of, ‘your app listens for requests on a port, Heroku handles the rest’, the one caveat is that you should take just a few minutes and read a primer on how random routing might impact your app. The article I just linked is exactly that 😜.</p>

<p>There are, of course, a couple of rough edges. Buildpacks can get tricky when you need system-level dependencies. Performance characteristics of dynos are frustratingly opaque, especially when you start caring about CPU vs memory (and please don’t get me started on <a href="/blog/shared-hardware-how-bad-can-it-get">noisy neighbors</a>). And while Heroku’s abstractions are usually quite helpful, they can be limited once you venture into multi-region replication, strict compliance requirements, and truly private networking. Heroku has a lot of features and capabilities in those spaces, but some of the “it just works” shine might fade.</p>

<p>But the point here isn’t to judge whether or not the platform can do <em>everything</em> — most can if you’re willing to fiddle enough. The point is about assessing how <em>often</em> the platform makes us think about these configurations and setups in the first place. Infrastructure friction is about the recurring cost of the platform in terms of our own time.</p>

<p>Heroku’s abstractions let us think about our infrastructure configuration and maintenance, year over year, less than just about any other host. For that reason, we give it an <strong>A-</strong> on infrastructure friction. Points lost for “dyno” resource opaqueness! </p>

<h2 id="heroku-organizational-friction">Heroku: Organizational Friction</h2>

<p>Finally, let’s talk about the stuff managers and owners usually care about more than boots-on-the-ground developers. This is less about the mechanics of actually building on the platform and more about the ripple effects of that platform up the chain-of-command. Remember, <em>someone</em>’s got to actually pay the bill!</p>

<p>And we might as well start with the bill. Heroku is notoriously the “worst” deal in PaaS’s. Just about any way you slice the performance-per-dollar, Heroku is more expensive than everyone else. We built a <a href="https://judoscale.com/tools/paas-pricing-calculator" target="_blank" rel="noopener">PaaS Price Calculator</a> that makes that much clear:</p>

<p><figure>
  <img alt="Screenshot of the PaaS Pricing Calculator with default values set showing Heroku at about $1850 per month while Render is at $850 and Fly is closer to $600" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ab709813-e4dc-42ea-4067-84e26032e600/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ab709813-e4dc-42ea-4067-84e26032e600/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ab709813-e4dc-42ea-4067-84e26032e600/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            Based on default app values in our calculator
          </figcaption>

</figure>
</p>

<p>And that is perhaps the greatest knock against Heroku in this entire rundown. While maybe not the first thing that jumps to mind when thinking about a “friction model”, there is <em>absolutely</em> friction here: cost friction! Friction incurred when having to hand over all those dollars every month instead of, in some cases, <em>half</em>! We’ll save the deeper discussion for another day, but just understand that the balance here is a higher cost vs. all of the low-friction abstractions we described in all of the paragraphs above. Heroku’s <em>schtick</em> is paying for simplicity. It always has been, it likely always will be. You’re buying back time that you might otherwise have to spend on infrastructure and hosting tasks.</p>

<p>Along with that idea, your platform choice has implications on team structure. To be frank, you shouldn’t need dedicated operations engineers if you’re running on Heroku. So maybe that potential savings accounts for some of the cost, but we’ll leave that to your own discretion. The truth is that <em>many</em> large applications and businesses, Judoscale included, began with <em>one</em> developer building and deploying an app on Heroku. Heroku allows the “one dev shop” to scale enormously in ways that more complex platforms would not — Adam talked about this quite a bit in <a href="/blog/black-box-hosting-vs-glass-box-hosting-an-interview-with-adam">our interview</a> — most teams can go a <em>long</em> time before needing more complexity and control than Heroku gives. We’ve seen companies doing <em>billions</em> in annual revenue humming along perfectly fine on a cluster of Perf-L’s!</p>

<p>When it comes to uptime concerns (depending on how you count them and consider magnitude), Heroku has had 2-4 major outages in the last two years, each lasting at least a few hours, impacting most of their customers. That’s still somewhere in the “three nines” region, and frankly, I don’t know of any hosting platform shooting much higher. Heroku outages just tend to be more prominent in developer news given <em>how many</em> applications run on Heroku. Answering the earlier question of, “How often is my CTO going to hear the word ‘Heroku’?”, which is an implication of outages, the answer is probably once or twice a year. Therefore, we’d consider their uptime to be solidly “good”. </p>

<p>If we boil the concept of organizational friction down to simply, “how does using this platform impact my business beyond my developers?” Then there are a few plain answers: it’s going to cost a lot, it’s going to save you from hiring quite a bit, and it’s going to be boring and unmentioned <em>almost</em> all the time. Generally speaking, most organizations are into that tradeoff, which is why Heroku has been so successful. Nonetheless, given that Heroku is no longer the only fish in the sea, <em>and</em> that its competitors have kept up with modern compute hardware and pricing adjustments so much more, we give Heroku a <strong>C</strong>  in for organizational friction. It’s a very good service, but it just shouldn’t be as expensive as it is in 2026.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p></p>

<p>There’s one last bit of “organizational friction” that’s more amorphous and would be difficult to give a grade because it’s entirely subjective: trust in the platform and company itself. Do you trust that the platform is moving in the right direction? Building according to your needs and interests? Has the best intentions? Looks out for the customer’s needs?</p>

<p>We’re not going to factor it into our grade here (a <strong>C</strong> already is what it is), but this is perhaps this single biggest pain point for us with Heroku right now. Heroku has absolutely <em>burned</em> a whole lot of developer trust in the last two months. Unclear messaging, vague direction, <del>a “Sustaining Engineering” model</del> lots of corporate hand-wavy verbiage… trust in Heroku is at a many-year low.</p>

<p>Tangibly, we’d love to see platforms provide public development roadmaps, transparent communication when things go wrong (or right!), and open spaces for developers to provide feedback that’s taken seriously. Heroku currently fails on all three fronts.</p>

  </div>
</div>

<h2 id="let-s-wrap-it-up">Let’s Wrap It Up</h2>

<p><strong>Heroku</strong>:</p>

<ul>
<li>Shipping Friction: <strong>A</strong></li>
<li>Debugging Friction: <strong>B</strong></li>
<li>Infrastructure Friction: <strong>A-</strong></li>
<li>Organizational Friction: <strong>C</strong> (for ‘<strong>C</strong>ost’ 😆)</li>
</ul>

<p>Perhaps our most primary opinion given in our “<a href="/blog/heroku-whats-next">Heroku: What’s Next</a>” article was that of, “Heroku’s still fine, we’ve got years”. That opinion feels worth reiterating here because it’s hard to look at these grades and <em>not</em> choose Heroku. It’s not a perfect platform by any means, but it still sets quite a high bar in 2026, cost aside.</p>

<p>Our “friction model” grading mechanism and rubric, if anything, might be helping to remind us why we chose Heroku in the first place. It wasn’t flashy features or landmark architectures… Heroku just removes a <em>lot</em> of friction in a lot of different places. You can ship quickly and easily, debug reasonably, focus on building your product, and largely not worry about Heroku at all. That’s nice!</p>

<p>But, of course, this sets the stage for what’s coming: it’s time to find out if this experience and ease of use holds up elsewhere. It’s time to go on tour and move our production application to each of the competitors; time to see how much friction exists on other platforms. We want to find out what’s harder, what’s easier, what’s faster, and what’s rough. That’s the real test, but now we have our baseline!</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-green-50 dark:bg-green-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-green-900 dark:text-green-500">
    ✅ Tip
  </h4>
  <div class="mt-2.5 text-green-800 prose-a:text-green-900 dark:prose-a:text-white prose-code:text-green-900 dark:text-gray-300 dark:text-green-100/80 dark:prose-code:text-gray-300">
    <p></p>

<p><strong>Reminder</strong>: This article is the first post in our “Judoscale going on tour” series, where we put our money where our mouth is and migrate Judoscale to various platforms. No holding back, no keeping background jobs somewhere else, no splitting traffic.</p>

<p>Judoscale is a 24/7 real-time reactive production application. We receive well over 3,000 RPS every moment of every day. Our downtime is <em>exceedingly</em> rare (generally only when Cloudflare or Heroku themselves have issues), but then, it darn well should be! We’re an autoscaler! We <em>need</em> to be online, regardless of traffic load, so that we can reactively scale our clients’ applications correctly and appropriately any time of day.</p>

<p>Sign up for our newsletter to join us on this tour as we discover the nooks and crannies of 2026’s available PaaS’s. If you’ve been thinking about moving, let us feel the pain first — we’ll tell you all about it 😆.</p>

  </div>
</div>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Heroku: What’s Next</title>
      <description>Heroku is shifting to a sustaining engineering model. Here’s what that means, whether you should migrate, and how the top alternatives compare.</description>
      <pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/heroku-whats-next</link>
      <guid>https://judoscale.com/blog/heroku-whats-next</guid>
      <author>Jon Sully</author>
      <content:encoded>
        <![CDATA[<p>In a move that surprised many of us — and one which I still can’t determine the business sense in making at all — Salesforce <a href="https://www.heroku.com/blog/an-update-on-heroku/" target="_blank" rel="noopener">officially announced</a> last week that Heroku will be moving into a “<em>sustaining engineering model</em>”. That’s essentially giant-software-corporation-speak for, “we’re putting this into maintenance mode”. The platform that taught a generation of developers to “push to deploy” has reached its investment limit from its owners 😕.</p>

<p><figure>
  <img alt="An AI-generated image of a pencil-sketch style scene, with a single server rack in a large space, and a sign hanging on that server rack which reads “Heroku Servers”, while several wrenches and tools are on the ground next to the rack, likely to be left there and not used again" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/e62be00b-a705-4806-f10b-d9bde603fd00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/e62be00b-a705-4806-f10b-d9bde603fd00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/e62be00b-a705-4806-f10b-d9bde603fd00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            "Our work here is done"
          </figcaption>

</figure>
</p>

<p>Now, before you jump straight to “abandon ship!!”, there are real questions we should think about when looking ahead. Heroku is still an excellent platform, runs very stably, and, to this day, has the smoothest DX for getting an application into production. For those of us with production apps currently running on Heroku, we need to be pragmatic about what this announcement means for our present, our future, and our time! </p>

<p>Salesforce’s announcement should ultimately drive a calm, collected conversation around both timing and execution. Heroku isn’t a sinking ship, it’s just done shipping new features.</p>

<h2 id="let-s-be-honest-about-urgency">Let’s Be Honest About Urgency</h2>

<p>Urgency itself is a function of two inputs: having a thing to do and believing that you must do that thing <em>soon</em>. The sooner you believe you must do it, the more urgent it will feel. So allow me to reiterate the point I made above and mix in some urgency:</p>

<p><strong>Heroku is not dying today, tomorrow, next month, or next year.</strong></p>

<p><strong>It is <em>not</em> urgent that you migrate away from Heroku</strong>.</p>

<p><figure>
  <img alt="An AI-generated image of a pencil-sketch drawing depicting a person taking a deep breath, with arrows that indicate ‘inhale’ and ‘exhale’, while they have a smile on their face as air leaves them" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/2ac05764-25ef-438a-c3a2-d127f4901a00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/2ac05764-25ef-438a-c3a2-d127f4901a00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/2ac05764-25ef-438a-c3a2-d127f4901a00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
            <figcaption class="text-center text-sm">
            "Breathe"
          </figcaption>

</figure>
</p>

<p>The Salesforce announcement might serve to give you the first component of urgency: we’ll all have a ‘thing to do’ at some point: migrate to another platform. But it certainly does <em>not</em> give the second component (‘do that thing soon’). Heroku isn’t going anywhere. And, if you recall the late two-thousand-teens, this isn’t even the first time that Heroku will spend some years running without major feature improvements! We sincerely believe it’ll be a few <em>years</em> before there’s any real pressing need to migrate off Heroku if you’re already successfully running your production app there.</p>

<p>I don’t want to come off like a Heroku shill here, so let me clarify why I’m pushing back against the hype and panic. It has nothing to do with Heroku’s bottom line or expensive servers. It has to do with your team’s time spent shipping useful features that will grow the value of your app and/or business.</p>

<p>Even in the best of circumstances and setups, migrating platforms takes time. It requires testing, planning, mapping, and careful execution to ensure that you’re not dropping traffic or upsetting customers along the way. It’s <em>work</em>. All of this work has opportunity cost: you <em>won’t</em> be building and shipping the features and enhancements that your customers want. You <em>won’t</em> be improving your application or business. At the end of the day, your customers don’t care how or where you host your app. They just want it to work and provide them value!</p>
<blockquote><p>Okay fine but give me an actual recommendation here?</p>
</blockquote>
<p>Sure. Deep breath. Let the panic subside: most applications currently running on Heroku <em>shouldn’t worry about migrating until next year</em> (2027) at the earliest. If you have an enterprise contract, you should renew it in 2026.</p>

<p><figure>
  <img alt="An AI-generated image depicting a simple block-lettered message as a pencil sketch on paper, reading: “don’t worry about migrating yet.”" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/44757617-96bc-4e18-0168-f5a2ae4c8700/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/44757617-96bc-4e18-0168-f5a2ae4c8700/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/44757617-96bc-4e18-0168-f5a2ae4c8700/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>You already chose Heroku, you’re already setup on Heroku, your app is already running <em>fine</em> on Heroku. You should try to capitalize on <em>those</em> gains as long as possible (especially if you have enterprise/discount pricing!). “Heroku isn’t going to get any new major features” doesn’t actually prevent you from realizing the value of your initial investment into “I want managed hosting I don’t have to worry about”. Moving to another PaaS would still satisfy the “I want managed hosting&hellip;” but the migration itself is an additional investment and cost that you simply don’t need to make yet. Take a deep breath and go build your app / business! That <em>does</em> reap value <em>today</em>.</p>

<p>😮‍💨</p>

<h2 id="looking-at-the-alternatives">Looking at the Alternatives</h2>

<p>Nonetheless, I know many readers are still going to queue up migrations in the coming months. Maybe that’s discomfort, simply having time available to migrate, or a bad taste in the mouth. I get it! Even as I wrote the paragraphs above I felt some of those same tensions. Honoring those thoughts (and knowing that the future will come eventually) it feels worthwhile to talk through some of the migration paths an existing Heroku app has ahead.</p>

<p>We’re going to evaluate each option in three primary lenses:</p>

<ul>
<li><strong>Migration effort</strong>: how painful it would be to migrate a full production Heroku app to this new setup</li>
<li><strong>Ongoing operational load</strong>: how it <em>feels</em> (subjectively) to use over time — things like CLI, “hop into prod console”, control and tweakability, etc.</li>
<li><strong>Cost structure</strong>: how expensive is this new setup compared to Heroku, and how is it billed differently?</li>
</ul>

<p>Then we’ll give our general take on each path outside of those three parameters. Today’s challengers:</p>

<ul>
<li>Render</li>
<li>Fly.io</li>
<li>Railway</li>
<li>Run-it-Yourself Systems</li>
</ul>

<p>But today’s look isn’t our one time “here’s the truth” post, it’s just a preview. We’ll give you our opinions here today based on our work integrating with most of these platforms and running various apps on them over the last three years, but we’re planning on going deeper in the coming months: Judoscale is going <a href="#judoscale-on-tour">on tour</a>. More on that below, but we’ll be moving our 3,000+RPS production app to each of these platforms to <em>really</em> feel out what it looks like for a production app that can’t go down!</p>

<h2 id="render-the-obvious-choice">Render: The Obvious Choice</h2>

<p><figure>
  <img alt="An AI-generated image of a simple sketch, the Heroku logo on the left, and an arrow in the middle pointing toward the Render logo on the right" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/6cabdc59-2557-4533-b412-54752a5ba900/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/6cabdc59-2557-4533-b412-54752a5ba900/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/6cabdc59-2557-4533-b412-54752a5ba900/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>If you asked me for a simple, single-sentence recommendation for most teams, it’s going to be Render. <em>Many</em> folks have described Render as, essentially, “the natural progression of Heroku” — perhaps what Heroku could’ve become had it never been acquired by Salesforce. I think this is mostly due to Render sharing many of the same philosophies as Heroku (fully managed PaaS, auto build detection, etc.) but just having been built fresh many years <em>after</em> Heroku: the Render devs had the chance to reimagine the Heroku UX from the ground up with plenty of Heroku experience to draw from.</p>

<p><strong>Migration effort</strong>. Any migration is going to take effort, but things are pretty smooth here. Heroku to Render is a <em>well</em>-trod path at this point and Render’s own team offers <a href="https://render.com/docs/migrate-from-heroku" target="_blank" rel="noopener">migration assistance</a> for those coming from Heroku. The mental model is broadly the same and you’ll feel at home within a few minutes of logging into the Render dashboard. The only gotcha to keep in mind is around buildpacks and system dependencies. Render does supply some base-level buildpacks that should cover most apps, but if your app requires specific system dependencies beyond their <a href="https://render.com/docs/native-runtimes#tools-and-utilities" target="_blank" rel="noopener">included set</a>, you may need to build out a Dockerfile. Where on Heroku buildpacks themselves can be composable, Render’s approach is simply, “stay on the rails or bring your own <code>Dockerfile</code>” (more <a href="https://render.com/docs/docker#docker-or-native-runtime" target="_blank" rel="noopener">here</a>). </p>

<p><strong>Ongoing operational load</strong>. Again here, this one’s going to feel just like Heroku. They handle the infrastructure, you just merge to <code>main</code>. Metrics and web dashboard UI are all friendly and available, logs can be pushed wherever you need, manual rollbacks are simple and accessible, there’s a broad CLI for control if you prefer that style, you can take your favorite <a href="/render">autoscaler</a> with you… the list goes on. Essentially everything you love about Heroku exists in Render in parallel or enhanced form.</p>

<p><strong>Cost structure</strong>. Of all that platforms and paths we’ll look at today, Render’s cost structure and setup matches Heroku’s the most. Like Heroku, their pricing revolves around pre-set, <a href="https://render.com/pricing#services" target="_blank" rel="noopener">per-month pricing</a> depending on which instance types (e.g. “dyno type”) you need. <em>Unlike</em> Heroku, they’re actually clear about how many vCPU cores you’re paying to hold (🎉). In terms of real cost, our rough estimate is that, depending on the composition of your app and resources you need, you’ll likely save 20-30% off your current Heroku bill for similar resources on Render.</p>

<p>Our general takeaway on Render is that it’s the right choice for the grand majority of currently-on-Heroku apps. It’s a near-seamless transition, the billing operates the same, the operational overhead for engineers learning the new platform is very low, and most apps will be able to get up-and-running within a day.</p>

<h2 id="fly-io-a-little-more-complicated-a-little-more-interesting">Fly.io: A Little More Complicated, A Little More Interesting</h2>

<p><figure>
  <img alt="An AI-generated image of a simple sketch, the Heroku logo on the left, and an arrow in the middle pointing toward the Fly.io logo on the right" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5df41937-3257-41df-e17c-19462c7fc300/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5df41937-3257-41df-e17c-19462c7fc300/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/5df41937-3257-41df-e17c-19462c7fc300/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Still mostly on the high-level-PaaS layer, Fly.io was built to accomplish a different goal. Fly’s whole <em>thing</em> is distributing your app geographically so that your users will always hit an application server close-by, and doing so with “Fly machines” — micro VM’s with much smaller footprints than full-on Docker containers. Fly is also heavily optimized for its powerful CLI and config tooling. Fly is <em>tremendously</em> flexible and configurable, but comes with the cost of complexity: a steep learning curve!</p>

<p><strong>Migration effort</strong>. Like Render, Fly has written <a href="https://fly.io/docs/getting-started/migrate-from-heroku/" target="_blank" rel="noopener">guides</a> specifically for those migrating from Heroku, including framework specific guides in many cases (<a href="https://fly.io/docs/rails/getting-started/existing/" target="_blank" rel="noopener">Rails</a>, <a href="https://fly.io/docs/django/getting-started/existing/" target="_blank" rel="noopener">Django</a>, <a href="https://fly.io/docs/python/frameworks/fastapi/" target="_blank" rel="noopener">FastAPI</a>, <a href="https://fly.io/docs/python/frameworks/flask/" target="_blank" rel="noopener">Flask</a>, etc.) to help explain nuances. And these guides are certainly helpful, but there’s no getting around the paradigm shift: Fly is a fundamentally different platform from Heroku and doesn’t operate quite the same. There <em>is</em> going to be a learning lift as you get familiar with its UI tooling and <code>flyctl</code> CLI tool — the latter of which you <em>absolutely will</em> want to become highly familiar with.</p>

<p><strong>Ongoing operational load</strong>. Like other PaaS’s, Fly can absolutely be configured to do the simple deploy-on-<code>main</code> thing and includes built in metrics dashboards, logging basics, and standard machine health checks, but you’ll find a lot of utility in <code>flyctl</code>. Restarting instances, changing environment variables, spinning up secondary production instances&hellip; all simple <code>flyctl</code> commands once you learn them! If you’re not already a heavy terminal user, dive on in. Fly exposes more primitives and control around lower-level constructs than most PaaS’s (think: direct VM controls, volumes, storage, regions, etc) and most of that is controlled via <code>flyctl</code>. So there’s more flexibility, but again, a steeper learning curve. Oh, also, you can still take your favorite <a href="/fly">autoscaler</a> with you!</p>

<p><strong>Cost structure</strong>. Fly walks a sort of middle-ground between resource tier-based pricing and metered usage, which makes it easy to jump around to difference scale sizes, tweak your RAM levels, and scale vertically as needed. Prices are <a href="https://fly.io/docs/about/pricing/#started-fly-machines" target="_blank" rel="noopener">per second</a> of machine runtime, extra RAM can be added wherever you want (very cool), and Fly offers everyone a (massive) <a href="https://fly.io/docs/about/pricing/#machine-reservation-blocks" target="_blank" rel="noopener">40% discount</a> when you opt to pre-reserve compute time — no enterprise contract required. If that sounds like a lot of levers to pull and tweak, that’s because it is. Again, Fly’s schtick here is configurability.</p>

<p>My take: if you’re the kind of person that was driving an automatic Honda Civic and already felt for years like you just wanted more of a car-person’s kind of car, then it’s probably true that Heroku’s recent announcement didn’t change anything for you — your Civic is still a Civic. But it’s understandable that Salesforce has, in some way or another, shaken you into realizing your dream. If you’re after that ‘69 Big Block Mustang with a four-barrel carb that you can tune <em>juuuuust</em> right&hellip; then Fly might be for you. This metaphor may have gone too far. Fly is complex. There are neat value-adds with that complexity, but it comes at the cost of complexity — there’s more to learn, more to understand, and more to manage.</p>

<h2 id="railway-not-exactly-our-way">Railway: Not Exactly Our Way</h2>

<p><figure>
  <img alt="An AI-generated image of a simple sketch, the Heroku logo on the left, and an arrow in the middle pointing toward the Railway logo on the right" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f08c1223-608c-43d6-ddab-c45f65abc100/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f08c1223-608c-43d6-ddab-c45f65abc100/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/f08c1223-608c-43d6-ddab-c45f65abc100/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>We’re not out to bash any hosting providers, especially ones that <a href="/railway">we support</a> autoscaling on, but we also need to be honest: our experience with Railway has been pretty lackluster. All other bells and whistles aside, we had the worst actual system performance on Railway. Not because of dependent services or database latencies or anything like that, we just found our real, pure compute performance to be worse on Railway than any other platform. <strong>It was just plain slower</strong>.</p>

<p>We can’t tell you why that’s the case, and at the same time, we love that Railway’s schtick is running their own metal in datacenters rather than reselling metal they rent from the big three. That’s awesome! But we suspect that economies of scale are a relevant factor here.</p>

<p><strong>Overall</strong>, we would not recommend Railway at this time. We love the mission and the goal, but we had a less-than-great-time. For the sake of being positive-outlook community members, we’ll simply leave it at that!</p>

<p>Oh, and we <em>do</em> still plan on taking another full crack at Railway when we go <a href="#judoscale-on-tour">on tour</a> — see more below.</p>

<h2 id="the-more-hiy-stuff">The More HIY Stuff!</h2>

<p><figure>
  <img alt="An AI-generated image of a simple sketch, the Heroku logo on the left, and an arrow in the middle pointing toward a small rack of servers with the simple label “Your Servers” above them" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/fa4d1951-78f4-4102-1364-4f3e80321700/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/fa4d1951-78f4-4102-1364-4f3e80321700/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/fa4d1951-78f4-4102-1364-4f3e80321700/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>We live in a wonderful time of <em>options</em>! There are so many great options in the <strong>H</strong>ost-<strong>i</strong>t-<strong>Y</strong>ourself world, in many flavors, and at many levels of even hosting-it-yourself. The bring-your-own VPS tooling like <a href="https://dokku.com" target="_blank" rel="noopener">Dokku</a>, <a href="https://hatchbox.io" target="_blank" rel="noopener">HatchBox</a>, <a href="https://coolify.io/" target="_blank" rel="noopener">Coolify</a>, and <a href="https://caprover.com" target="_blank" rel="noopener">CapRover</a> offer lightweight PaaS-like experiences with great flexibility, each with their own distinct tradeoffs and workflows. Going even more complex, container orchestrators (e.g. they coordinate the Kubernetes for you) like <a href="https://northflank.com" target="_blank" rel="noopener">Northflank</a>, <a href="https://www.porter.run" target="_blank" rel="noopener">Porter</a>, and <a href="https://www.qovery.com" target="_blank" rel="noopener">Qovery</a> can allow you to “bring your own cloud” (be it your own metal, rented Hetzner boxes, or AWS API keys, etc.) while still handling most of the complexities of Kubernetes cluster orchestration for you. And, of course, the big world of AWS itself — “Hop onto ECS Fargate!” or “Elastic Beanstalk, baby!” among other choices. There’s truly never been so many ways to run the “Heroku experience” yourself!</p>

<p>Honestly, there are a <em>dizzying</em> number of ways to make the technologies at this level of hosting control work. For the sake of this article not turning into a book, we’re going to mostly leave them unmentioned here. The reality is that <strong>if you’ve been a happy Heroku customer, you shouldn’t go looking down this path</strong>. I know that’s a strong statement that might make a few of the “come to the DIY-side!” folks upset, but it’s a pragmatic truth. These are two wholly different worlds with different levels of time and skill involved. Going ‘down’ a single layer in the hosting stack (as we perceive it) and getting into <em>Fly’s</em> ecosystem is already going to add overhead to your workflow as you need to learn to understand and handle their config complexity. Going all the way down to the HIY tooling is only going to add more ops time (or people!) to your app’s needs. If you’re happy with your PaaS-level at Heroku, stay up there!</p>

<h2 id="the-real-answer">The Real Answer</h2>

<p>Let’s zoom out and take a deep breath. I still <em>fully</em> stand by my original sentiment above: Heroku isn’t going anywhere and will remain stable for years to come. There’s no urgency to move, and doing so will only detract from the hours you could be spending on your product itself at this point. Moving takes work. We can’t ignore that reality amidst the hype here.</p>

<p>Then, of course, conceding to those who are <em>for sure</em> going to move soon out of principle, spite, or otherwise disdain for Salesforce (which… I get), we covered some options. Render is the clearest, clean-cut, easy choice. Fly is more complex but more complicated. Railway isn’t recommended at the moment. Host-it-yourself and bring-your-own-cloud solutions are way more effort than a Heroku team should look at.</p>

<p>So&hellip;. move to Render and call it a day? <strong>Not exactly</strong>.</p>

<p>As your resident auto-scaling experts for the last decade, who have integrated deeply with and provide autoscaling services for nearly all of the platforms previously mentioned, we have some opinions.</p>

<p><figure>
  <img alt="A gif of a man in a bomber jacket nodding slowly with a confident, knowing expression" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ecea9dcf-5da2-4231-6bdc-3fb95341fd00/width=96,quality=20,blur=10,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ecea9dcf-5da2-4231-6bdc-3fb95341fd00/width=96,quality=20,blur=10,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ecea9dcf-5da2-4231-6bdc-3fb95341fd00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>But our opinions are from last year (or prior). And they’re based on integration work with Judoscale. And, who knows, they might just be wrong. So we’re going to do something that we haven’t seen done before: <strong>we’re going on tour</strong>.</p>

<h2 id="judoscale-on-tour">Judoscale On Tour</h2>

<p>As much as I wish that meant a music tour around the US with <a href="https://www.linkedin.com/posts/adamlogic_railsconf2025-activity-7349094411043037185-vWMQ" target="_blank" rel="noopener">our kazoos</a>, we actually hatched up a better idea. Judoscale is a 24/7 real-time reactive production application. We receive well over 3,000 RPS every moment of every day. Our downtime is <em>exceedingly</em> rare (generally only when Cloudflare or Heroku themselves have issues), but then, it darn well should be! We’re an auto-scaler! We <em>need</em> to be online, regardless of traffic load, so that we can reactively scale our clients’ applications correctly and appropriately any time of day.</p>

<p>Sounds like the perfect app to move to each of these platforms / services to test some things out.</p>

<p>To be clear: our “going on tour” means that we’re going to migrate the Judoscale production application, including all traffic, DNS, configs, background workers, etc, to each of Heroku’s competitors, one at a time, and document every step along the way for you all.</p>

<p><figure>
  <img alt="A gif of a nervous animatronic monkey puppet anxiously scratching its head with wide, worried eyes" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ab4999c4-d589-4840-82a2-9c5287bbf300/width=96,quality=20,blur=10,format=jpeg" x-data="{&quot;src&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ab4999c4-d589-4840-82a2-9c5287bbf300/width=96,quality=20,blur=10,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://judoscale.com/cdn-cgi/imagedelivery/g5ziwLsypgTqGag7aYHX0w/ab4999c4-d589-4840-82a2-9c5287bbf300/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>So, again, our real recommendation here is simply to hang tight on Heroku. We’re going to take the plunge for you (many times over) and move our real-time, high traffic application ourselves. We’re going to find the rough edges. We’re going to feel the performance bottlenecks. We’re going to foot the literal bill and feel the DX each of these new platforms provides compared to ol’ purple.</p>

<p>If that sounds exciting to you, make sure you subscribe to our newsletter below. We’ll start with a full breakdown of all the things we love and use on Heroku, which will set forth our rubric for how to evaluate other platforms.</p>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Latency-based Celery Queues in Python</title>
      <description>If you plan your Celery task queues around latency, you'll have more predictable (and scalable) results. Learn how to plan your Python queues around latency!</description>
      <pubDate>Tue, 17 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/latency-based-celery-queues-in-python</link>
      <guid>https://judoscale.com/blog/latency-based-celery-queues-in-python</guid>
      <author>Jeff Morhous</author>
      <content:encoded>
        <![CDATA[<p>If you’ve worked with Celery in production with real traffic, you’ve probably hit one of its many sharp edges. Maybe you’ve watched a simple background job silently pile up in an unmonitored queue.</p>

<p>Or maybe you’ve built out a tidy set of queues only to find your high-priority jobs are getting stuck behind slow (and unimportant) ones. Celery gives you powerful tools, but few guardrails.</p>

<p>These pain points usually stem from <strong>queue planning problems</strong>. Most teams slap labels like <code>high_priority</code> or <code>emails</code> on queues without defining what those mean.</p>

<p>If you plan your <a href="/blog/choose-python-task-queue">Python task queues</a> around latency, you&rsquo;ll have more predictable (and scalable) results. Ready to get started?</p>

<h2 id="the-basics-of-celery-queues">The basics of Celery Queues</h2>

<p>Before we get into queue planning, let’s clarify some Celery terminology. If you already have a great understanding of how Celery works, feel free to skip to the next section.</p>

<p><figure>
  <img alt="Celery queue diagram, showing a Celery queue, full of tasks, with worker processes" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/fc5eacfb-840c-4636-e0a6-e7a5b018cb00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/fc5eacfb-840c-4636-e0a6-e7a5b018cb00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/fc5eacfb-840c-4636-e0a6-e7a5b018cb00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<h3 id="celery-tasks">Celery tasks</h3>

<p>In Celery, a <strong>task</strong> is a single unit of work. For example, <code>send_email_task</code> might send a welcome email.</p>

<h3 id="celery-queues">Celery queues</h3>

<p>A <strong>queue</strong> in Celery refers to a named channel on the broker (like a Redis list or RabbitMQ queue) where tasks wait to be processed. By default, Celery uses a queue named <code>&quot;celery&quot;</code> (if you don’t specify one).</p>

<h3 id="celery-workers">Celery workers</h3>

<p>A <strong>worker</strong> is a Celery process that runs tasks. A worker can run multiple tasks concurrently, depending on its concurrency setting.</p>

<h3 id="celery-concurrency">Celery concurrency</h3>

<p><strong>Concurrency</strong> refers to the number of tasks a worker can process at the same time. In prefork mode, this is the number of child processes (often defaults to the number of OS-reported CPUs).</p>

<h3 id="decisions-you-have-to-make-when-using-celery">Decisions you have to make when using Celery</h3>

<p>In a typical deployment, you must decide <strong>how many queues</strong> to use and what they are called, <strong>which tasks go to each queue</strong>, and <strong>how many worker processes</strong> will consume each queue.</p>

<p>You also choose how many threads/processes each worker has (concurrency) and how many total containers to run (horizontal scaling). That’s a lot of decisions!</p>

<p>So let&rsquo;s dig into how you can make these decisions with scaling in mind.</p>

<h2 id="why-celery-queues-run-into-problems-at-scale">Why Celery queues run into problems at scale</h2>

<p>Out of the box, Celery will use a single queue (usually named <code>&quot;celery&quot;</code> by default). If a task doesn’t specify a queue, it goes to the default queue. If you start a worker without specifying <code>-Q</code>, it will consume the default queue. </p>

<p>Could you build an app with just one queue? <strong>Sure.</strong>  But please don&rsquo;t.</p>

<h3 id="not-every-task-is-created-equal">Not every task is created equal</h3>

<p>For a brand-new project, one queue might work fine for a short while. But very soon, you’ll encounter scenarios that push you to create additional queues:</p>

<ul>
<li>You have a task that needs to run <strong>quickly</strong> (a high-priority job), so you want it processed before other tasks.</li>
<li>You have a task that takes a long time to run (perhaps several seconds or minutes), and you want it to have <strong>lower priority</strong> or even separate handling so it doesn’t block faster tasks.</li>
</ul>

<p>In response, teams might eventually create ad-hoc queues like <code>&quot;urgent&quot;</code> for high priority and <code>&quot;low&quot;</code> for slow tasks.</p>

<h3 id="ambiguous-queue-names">Ambiguous queue names</h3>

<p>However, there’s a big problem. <strong>Those queue names are ambiguous</strong>.</p>

<p>How urgent is “urgent”? What does “low” mean, exactly? As your application grows, you’ll find there are varying degrees of priority. One developer might add <code>very_urgent</code> or <code>critical</code> queues; another might introduce a queue for a specific feature like <code>reports</code> or <code>emails</code>.</p>

<p>Before you know it, you have a <strong>sprawl of Celery queues</strong> without a clear hierarchy or expectations.</p>

<h2 id="latency-based-queues">Latency-based queues</h2>

<p>Take a step back and consider what metrics define the “health” of a task queue. Three key metrics are commonly used:</p>

<ul>
<li>Worker CPU: How taxed is the CPU for worker processes?</li>
<li>Queue depth: How many tasks are waiting in the queue (queue length).</li>
<li>Queue latency: How long a task waits in the queue before a worker starts processing it (sometimes called queue time).</li>
</ul>

<p>CPU can be used, but it doesn&rsquo;t actually tell everything about <em>the queue</em>. It simply gives an indication (and often a trailing indication) of the worker process during an individual task. And task queues often back up without spiking CPU at all, giving a false sense of worker health.</p>

<p>Queue depth is easy to visualize (a simple count of jobs), so many people focus on it. Queue depth can be very misleading. The number of tasks doesn’t tell you how <em>long</em> they’ll take to clear.</p>

<p>For example, imagine two queues, each handled by one worker process:</p>

<ul>
<li>Queue A has 10 jobs enqueued, and each job takes ~1 second to run.</li>
<li>Queue B has 10,000 jobs enqueued, but each job takes ~1 <em>millisecond</em> to run.</li>
</ul>

<p>Queue B might look “backed up” at a glancem, but in reality, both queues will finish their work in about 10 seconds. <strong>The <em>latency</em> (wait time) for jobs in both queues is the same ~10 seconds</strong>, which is the metric that truly matters.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-green-50 dark:bg-green-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-green-900 dark:text-green-500">
    ✅ Tip
  </h4>
  <div class="mt-2.5 text-green-800 prose-a:text-green-900 dark:prose-a:text-white prose-code:text-green-900 dark:text-gray-300 dark:text-green-100/80 dark:prose-code:text-gray-300">
    <p><strong>Queue latency</strong> tells the real story about how well a queue is doing.</p>

  </div>
</div>

<p>So, is a 10-second wait time good or bad? <strong>It depends.</strong></p>

<p><figure>
  <img alt="It depends meme, showing Celery queue latency health is a complicated decision" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/b71aa293-84a1-4982-edee-567358874700/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/b71aa293-84a1-4982-edee-567358874700/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/b71aa293-84a1-4982-edee-567358874700/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>The acceptable latency for a queue is a business decision. It depends on what the tasks are doing and how quickly that work needs to begin. This brings us back to the notion of “urgency”, but now we can quantify it. Instead of calling a queue &ldquo;urgent&rdquo; in a vague sense, we decide what latency is acceptable for that queue’s tasks.</p>

<h2 id="latency-sla-queue-names">Latency SLA queue names</h2>

<p>If you&rsquo;re convinced <strong>queue latency</strong> is the right metric to measure performance, you should fix the ambiguity in your queue names. Naming your queues after their latency targets (SLAs) is a great way to set yourself up for success.</p>

<p>For example:</p>

<ul>
<li>“urgent” becomes <code>within_5_seconds</code> (tasks should start within 5 seconds)</li>
<li>“default” becomes <code>within_5_minutes</code> (tasks should start within 5 minutes)</li>
<li>“low” becomes <code>within_5_hours</code> (tasks should start within 5 hours)</li>
</ul>

<p>If I push a task to the <code>within_5_seconds</code> queue, I’m explicitly saying I expect that job to begin processing within five seconds. The name of the queue communicates the expectation.</p>

<p>You can choose whatever latency thresholds make sense for your app, the specifics aren’t as important as the explicitness of the naming.</p>

<p>By communicating latency expectations in the queue names, we get a few important things.</p>

<p>First, <strong>you&rsquo;ll end up with fewer queues</strong>. You’re far less likely to create a new queue per feature or whim. Almost every new task will fit into an existing latency category. This should remove the temptation of one-off queues that don&rsquo;t serve a strategic purpose.</p>

<p>Second, each queue now has a <strong>performance target</strong> (its name). This gives clarity for monitoring. If the <code>within_5_minutes</code> queue starts seeing 10-minute latencies, you have an unambiguous problem.</p>

<p>Of course, naming queues “within_X” doesn’t magically make tasks start within X time – <strong>you have to ensure enough worker capacity to meet those targets</strong>. That’s where scaling comes in.</p>

<p>Fortunately, this strategy makes it crazy easy to decide when to spin up more (or fewer) workers to scale, but we&rsquo;ll talk more about that later.</p>

<p><figure>
  <img alt="Diagram showing latency-based celery queues with different tasks in each queue" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/d67d1259-b77a-4f82-9f4a-bff4267fa800/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/d67d1259-b77a-4f82-9f4a-bff4267fa800/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/d67d1259-b77a-4f82-9f4a-bff4267fa800/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<h2 id="simple-ways-to-scale-celery-queues">Simple ways to scale Celery queues</h2>

<p>Typically, scaling a Celery worker pool is with the goal of avoiding a queue backlog.</p>

<p>Now that our queue names encode latency expectations, we can define a clear scaling goal for each queue:</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-green-50 dark:bg-green-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-green-900 dark:text-green-500">
    ✅ Tip
  </h4>
  <div class="mt-2.5 text-green-800 prose-a:text-green-900 dark:prose-a:text-white prose-code:text-green-900 dark:text-gray-300 dark:text-green-100/80 dark:prose-code:text-gray-300">
    <p>Each queue’s latency should stay within its target (as named), without having overprovisioned resources.</p>

  </div>
</div>

<p>For most people, traffic and job volumes fluctuate too much to maintain this manually. You’ll want to <strong>autoscale</strong> your workers based on queue latency. With autoscaling in place, meeting those latency targets becomes trivial.</p>

<p>When jobs start waiting too long, spin up more workers; when the queues are empty, spin them down.</p>

<p>For example, if the <code>within_5_seconds</code> queue’s jobs are waiting &gt;5 seconds, your autoscaler should add another worker process (or increase concurrency) for that queue. If the queue’s latency stays under 5 seconds, you can maybe scale down. We’ll talk about how to assign workers to queues next, which affects how you set up autoscaling triggers.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p>Built-in autoscalers default to CPU usage for scaling. <a href="https://judoscale.com/python" target="_blank" rel="noopener">Judoscale</a> is a great autoscaler add-on that can scale your queues based on queue latency!</p>

  </div>
</div>

<p>Speaking of queue assignment, how should we split up queues across Celery workers? I have a few opinions!</p>

<h2 id="your-options-for-matching-workers-to-queues">Your options for matching workers to queues</h2>

<p>When it comes to queue-to-worker assignment, you have a couple of options. On one hand, you have <em>one set of workers pulling from all queues</em>. On the other hand, you have <em>dedicated workers for each queue</em>.</p>

<p>In between these two extremes, you might run some workers that each handle a subset of queues.</p>

<h3 id="running-a-single-worker-pool-for-all-queues">Running a single worker pool for all queues</h3>

<p>Running a single worker pool for all queues is the simplest setup. It’s resource-efficient since any free worker can work on any task, and you don’t need to worry about balancing workers between queues.</p>

<p><figure>
  <img alt="Diagram showing a single Celery worker pool consuming from multiple queues" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/34499f20-7103-4537-e2f0-7c7e38a83a00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/34499f20-7103-4537-e2f0-7c7e38a83a00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/34499f20-7103-4537-e2f0-7c7e38a83a00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>However, the downsides are significant. You risk <strong>long-running tasks blocking high-priority tasks</strong>, plus it’s harder to autoscale effectively for all latency goals at once.</p>

<p>For example, suppose one Celery worker (with concurrency 4) is consuming <code>within_5_seconds</code>, <code>within_5_minutes</code>, and <code>within_5_hours</code> queues. If it picks up several very slow <code>within_5_hours</code> tasks (say tasks that each take minutes to execute) on all its worker processes, and then a bunch of new <code>within_5_seconds</code> tasks arrive, those fast tasks <strong>can’t start until a process is free</strong>.</p>

<p>All processes are busy churning on slow jobs, so even though the <code>within_5_seconds</code> queue is the highest priority, it’s effectively blocked. This defeats the purpose of having a fast queue!</p>

<h3 id="dedicated-workers-per-queue">Dedicated workers per queue</h3>

<p>In this setup, each queue gets its own Celery worker process (or pool).</p>

<p>For example, you might start one set of workers with <code>-Q within_5_seconds</code>, another with <code>-Q within_5_minutes</code>, and so on. This <em>completely isolates</em> each latency tier.</p>

<p>The slow jobs in the 5-hour queue can never block the 5-second jobs, because they’re handled by different workers on possibly different machines.</p>

<p>Autoscaling becomes much cleaner because you can <strong>scale each worker deployment based on <em>that queue’s</em> latency threshold.</strong> The <code>within_5_minutes</code> workers only care about keeping that queue under 5 minutes latency, and if they’re idle, you can scale them down without affecting the queue time of unrelated queues.</p>

<p>The mental model is simpler, and each queue’s performance can be managed separately. The primary downside is the <strong>cost</strong> of running more separate processes.</p>

<p>The cost difference between one big worker vs. multiple smaller dedicated workers is often minor, and it’s far outweighed by the performance improvements. With dedicated per-queue workers, you also avoid starving out fast tasks with long-running ones.</p>

<h3 id="a-bit-of-both">A bit of both</h3>

<p>One strategy is to try to group certain queues together on workers and isolate others. For example, maybe combine the <code>within_5_seconds</code> and <code>within_5_minutes</code> queues on one worker type, but keep the <code>within_5_hours</code> queue separate.</p>

<p>While this can work, any time you put multiple latency tiers on one worker, you reintroduce the possibility of interference. It also complicates autoscaling (which latency do you scale on for that combined worker?).</p>

<h3 id="my-recommendation">My recommendation</h3>

<p>In summary, I <strong>recommend dedicated Celery workers per latency-based queue</strong>. It makes it straightforward to maintain each queue’s SLA.</p>

<p><figure>
  <img alt="Diagram showing Celery workers dedicated to their own queues" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/636416f4-eda8-403a-7a52-82e2c5e2fd00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/636416f4-eda8-403a-7a52-82e2c5e2fd00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/636416f4-eda8-403a-7a52-82e2c5e2fd00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>If you’re on an autoscaling platform, set each worker deployment to scale up whenever its queue latency exceeds the target. To mitigate the <em>potentially</em> higher resource usage of this setup, I also recommend autoscaling your lower-priority workers (5 minutes, 5 hours, etc.) down to zero when the queues are idle. (Of course Judoscale makes this super easy 😁.)</p>

<p>If you’re doing this manually, you still benefit from clarity: you can monitor each queue’s wait time and add resources accordingly without guessing which queue is starved.</p>

<p>You should also look into other ways to <a href="/blog/scaling-python-task-queues">effectively scale Python task queues</a>, like fanning out large jobs.</p>

<h2 id="one-thing-to-keep-in-mind-for-celery-queues">One thing to keep in mind for Celery queues</h2>

<p>One Celery-specific consideration that doesn&rsquo;t apply to every queuing system is task acknowledgment timing. By default, Celery acknowledges a task as &ldquo;received&rdquo; when a worker picks it up. If the worker crashes mid-task, that task is dropped.</p>

<p>Setting <code>acks_late=True</code> (either globally or per-task) delays acknowledgment until the task <em>completes</em>. This means crashed tasks get redelivered, but it also means <strong>your tasks need to be idempotent</strong>, since they might run more than once.</p>

<p>If you&rsquo;re using <code>acks_late</code> with Redis as your broker, pay attention to the <code>visibility_timeout</code> setting. This controls how long Redis waits before assuming a task was lost and redelivering it. The default is one hour. If you have tasks that need to run longer than your visibility timeout, they&rsquo;ll get redelivered while still running.</p>

<p>For latency-based queue planning, the practical advice is that tasks in your fast queues (like <code>within_5_seconds</code>, <code>within_5_minutes</code>) should be short enough that the visibility timeout is irrelevant. For your slow queue, make sure your longest-running tasks finish well under the visibility timeout, or increase the timeout accordingly.</p>

<h2 id="shipping-performant-celery-queues">Shipping performant Celery queues</h2>

<p>This opinionated guide for setting up your Celery queues is very much inspired by the <a href="/blog/planning-sidekiq-queues">strategies we know work well in the Sidekiq world</a>. I hope this gives you some fresh ideas and a solid game plan for taming your Celery queues.</p>

<p>Remember, planning your queues boils down to:</p>

<ul>
<li>Name queues by expected latency.</li>
<li>Isolate latency tiers on separate workers to avoid cross-interference.</li>
<li>Monitor and autoscale by latency.</li>
</ul>

<p>Follow these steps, and you’ll avoid most of the common background job headaches that plague teams as they scale up.</p>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Node.js Hosting Options</title>
      <description>Choosing where to host a Node.js is a high stakes decision. This guide will show you how to pick the best hosting option for your app AND your team.</description>
      <pubDate>Wed, 4 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/node-js-hosting-options</link>
      <guid>https://judoscale.com/blog/node-js-hosting-options</guid>
      <author>Jeff Morhous</author>
      <content:encoded>
        <![CDATA[<p>Choosing the right hosting environment for a Node.js application will define much of both your development workflow and application performance. The hosting option you choose directly affects the developer experience (how easy deployments and updates are), the cost model of running your app, its scalability under load, and how much control (and responsibility) you have over your infrastructure.</p>

<p>For example, a fully managed platform can eliminate server maintenance at the cost of less flexibility and more money, whereas running your own server gives maximum control but demands more operational work.</p>

<p>Your goal in deciding on where to host a node app is to align your hosting choice with your app’s <strong>technical requirements</strong> and your <strong>team’s capacity</strong> to manage the underlying infrastructure.</p>

<h2 id="different-types-of-node-apps-have-different-needs">Different types of Node apps have different needs</h2>

<p>APIs built with Node are stateless request/response services and are a good fit for most hosting models. A Node.js API can run on anything from a cheap VPS to serverless functions, since each request is independent and typically short-lived.</p>

<p>Real-time apps (like those with WebSockets), on the other hand, need persistent connections. Things like chat apps or live dashboards require hosting that supports long-lived network sockets. Traditional servers or container-based platforms are often necessary here as pure serverless platforms often don’t allow WebSockets or constant connections. For example, Vercel’s serverless functions cannot hold always-on WebSocket connections, but they do support WebSockets through their Edge Runtime.</p>

<p>Server-rendered apps (think Next.js) are certainly a special case. Frameworks like Next.js generate (most) pages server-side and often do well with serverless deployment. <strong>Next.js is tightly integrated with Vercel</strong>, which offers zero-configuration deployment, serverless functions for API routes, and edge caching for static assets. Many teams choose serverless platforms for these SSR apps to leverage features like automatic CDN distribution and on-demand scaling without managing servers. However, this serverless approach comes with tradeoffs in execution time limits and statefulness, which we’ll discuss later.</p>

<p>First, let&rsquo;s talk about the option that demands the most of you.</p>

<h2 id="hosting-node-apps-on-a-vps-or-similar-cloud-service">Hosting Node apps on a VPS (or similar cloud service)</h2>

<p>Running a Node.js app on a VPS (Virtual Private Server), Amazon EC2, or cloud virtual machine gives you <strong>maximum control</strong> over the environment. But with that comes maximum responsibility.</p>

<p>On a VPS, you get root access to install any OS packages, configure the stack exactly as you want, and run any background processes you need. This flexibility is powerful for custom setups, but the maintenance burden on you or your team is high. You are in charge of everything under the hood.</p>

<p>Applying OS security patches, monitoring disk and CPU usage, setting up firewalls, managing backups, and handling scaling manually are all things you should be prepared to manage if you go this route.</p>

<p>Using infrastructure-as-code and containers can ease some pain, but won’t eliminate ops work. Tools like <a href="/blog/kamal-vs-paas">Kamal can simplify deploying a containerized app</a> to a VPS. However, <strong>Kamal doesn’t handle the surrounding infrastructure needs</strong>. You still need to set up things like load balancers, databases with backups, log aggregation, and system monitoring yourself.</p>

<p>Containers help by packaging your Node.js app with its dependencies, making it portable and consistent across environments. But the VPS still needs to have everything the container needs. You’ll still be responsible for orchestrating containers, scaling them, and managing the host VM’s health.</p>

<p><figure>
  <img alt="Hosting a Node.js app on a VPS" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/ebf2bc38-68a6-455e-a28a-5d9eeac9a300/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/ebf2bc38-68a6-455e-a28a-5d9eeac9a300/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/ebf2bc38-68a6-455e-a28a-5d9eeac9a300/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Hosting on a VPS or cloud VM is fine if you need fine-grained control or have specialized requirements that platforms don’t support. But it&rsquo;s not an option I can recommend unless you have a dedicated ops team (or you just really love that sort of thing). I&rsquo;ve hosted small projects on a VPS, and it&rsquo;s always been more headache than the cost savings I faced.</p>

<h2 id="hosting-your-node-app-on-a-paas">Hosting your node app on a PaaS</h2>

<p>Platform-as-a-Service (PaaS) offerings strike a middle ground by handling most infrastructure concerns while still letting you run a “server-like” app. Platforms like Heroku, Render, Amazon ECS Fargate, and Fly.io are PaaS leaders.</p>

<p>They allow you to push your Node.js code (via Git or container image) and then they build, run, and serve your application in a managed environment. Platforms abstract away the server (or VPS) management.</p>

<p><figure>
  <img alt="Hosting Node.js apps on a platform" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/aa8a522b-9748-44f4-fe05-6d4436b80a00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/aa8a522b-9748-44f4-fe05-6d4436b80a00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/aa8a522b-9748-44f4-fe05-6d4436b80a00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Most platforms give you the option between using containers or not, so the above image could be even simpler, with you only managing the app itself.</p>

<p>With platforms, there&rsquo;s very little manual configuration and management. You get a deployment platform that automates scaling, security updates, and (some) monitoring, usually through a web dashboard or CLI. Developers can focus on code and let the platform handle the “ops” heavy lifting.</p>

<p>Using a PaaS still provides you with the flexibility to run long-running processes and <a href="/blog/node-task-queues">async job queues like BullMQ or Bee-Queue</a>, which are things that pure serverless platforms don’t support.</p>

<p>The general-purpose nature of PaaS means it doesn’t matter whether you’re deploying a frontend, a Node API, or a background worker. This makes platforms the best option for <em>most</em> Node apps.</p>

<p>You get persistent Node.js processes that can maintain state in memory, hold database connection pools, handle WebSocket connections, and even schedule cron jobs without worrying about hitting an execution timeout or some vendor constraint. Essentially, it offers the convenience of managed hosting <em>without the severe limitations on process lifespan</em> that come with serverless function environments. </p>

<p>You get a managed environment that dramatically reduces your operations overhead, but you <strong>keep quite a bit of control.</strong></p>

<p>But serverless <em>is</em> right for some apps! Let&rsquo;s look into that next.</p>

<h2 id="hosting-serverless-node-apps-on-vercel-or-netlify">Hosting serverless Node apps on Vercel or Netlify</h2>

<p>Serverless platforms like <strong>Vercel and Netlify</strong> have gained popularity, especially for frontend-oriented and Jamstack applications. Vercel hired much of the React core team away from Meta and has stewarded the development of both React and Next.js, which positions them well to support Next apps in particular.</p>

<p>In a serverless model, you don’t maintain a running server process. Instead, your Node.js code is deployed as functions that execute on demand in response to requests (or events) and then terminate. This model brings <strong>automatic scaling per request</strong> – every incoming request can spin up a new isolated function instance if needed, so capacity can increase seemingly without bound, and you never pay for idle time.</p>

<p>Vercel and Netlify both provide an experience where you connect a Git repo, and they build and deploy your site with serverless functions backing any dynamic endpoints or API routes. This gives a fantastic developer experience for certain use cases. Frontend-heavy apps get static hosting plus dynamic capabilities without ever thinking about servers, and things like CI/CD, CDN distribution, and SSL are handled for you out of the box.</p>

<p>I host my personal site and a few simple projects on Vercel and am quite happy with how hands-off it&rsquo;s made hosting. For my simple Next.js app, Vercel is a very good fit and also free.</p>

<p><figure>
  <img alt="Hosting a Node app on vercel" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/bfc0a270-5518-4a41-59e3-58061a143700/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/bfc0a270-5518-4a41-59e3-58061a143700/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/bfc0a270-5518-4a41-59e3-58061a143700/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>That being said, if I want to expand this application to include more functionality, I&rsquo;d probably run into some limitations.</p>

<p>The first major limitation is that <strong>serverless functions on these platforms have hard time limits.</strong> This means you cannot do long processing jobs directly. If your Node app needs to generate a large report or process a big file, you’ll likely exceed these limits and the platform will kill the function.</p>

<p>Long-running tasks have to be offloaded to external services or broken into much smaller jobs. But Vercel and Netlify do <strong>not allow running arbitrary background worker processes</strong>. You can’t have a worker listening to a queue or a scheduler that continuously runs in the background. “Background Functions” on Netlify simply allow a single function invocation to run longer (<a href="https://docs.netlify.com/build/functions/overview/#default-deployment-options" target="_blank" rel="noopener">up to 15 minutes</a>) asynchronously, but they are not equivalent to a always-on worker process.</p>

<p>Vercel recently introduced scheduled functions, which are cron-like triggers, but these are just periodic invocations of serverless functions, not persistent jobs. Any asynchronous or delayed work in a serverless architecture has to be handed off to another system (using an external job queue service, or triggering an AWS Lambda via event).</p>

<p>This is a fundamental design difference. Traditional platforms (like Heroku, Render, etc) let you run a worker indefinitely, whereas on Netlify/Vercel, you might schedule a function to run every few minutes, but it will start fresh and then terminate each time.</p>

<p>Both Vercel and Netlify abstract away containers and don’t let you deploy a custom Docker image to their platform. You are limited to the runtimes and languages they support and the build process they provide. While the support is often sufficient, the platform’s provided environment is the only environment. Vercel and Netlify focus on source-based deployment and static assets, not running arbitrary containers.</p>

<p>They are great at what they do (fast frontend deployments), but aren’t general-purpose hosting for any kind of app.</p>

<h2 id="autoscaling-a-node-app">Autoscaling a Node app</h2>

<p>Scalability is a big question for web developers, and different platforms scale Node apps in different ways. Understanding your autoscaling options and their implications for performance and cost matters a bit for choosing a host.</p>

<p>On traditional setups like VPS or self-managed servers, scaling is usually manual unless you build your own scripts or use cloud vendor tools to spin up new VMs. By contrast, PaaS platforms typically offer some form of horizontal autoscaling for Node apps, but the responsiveness to load can vary.</p>

<p>Heroku, for example, has a built-in autoscaler (available on certain tiers) that can add or remove dynos based on response time thresholds. The caveat with this metric is that they might react sluggishly or scale at the wrong times.</p>

<p>This is why third-party solutions like <a href="https://judoscale.com/node" target="_blank" rel="noopener">Judoscale</a> have emerged. Judoscale focuses on <strong>request queue time</strong> as the metric to decide scaling, which directly measures if requests are backing up due to a lack of capacity. </p>

<p><figure>
  <img alt="Scaling Node.js apps" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/08389d89-bccb-4f95-e8aa-987a06e35e00/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/08389d89-bccb-4f95-e8aa-987a06e35e00/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/08389d89-bccb-4f95-e8aa-987a06e35e00/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>Judoscale will add more web processes as capacity demands it, and we also watch your job queues to autoscale worker processes. If you want reliable autoscaling on a PaaS, you want Judoscale.</p>

<h3 id="scaling-on-serverless-is-weird">Scaling on serverless is weird</h3>

<p>Serverless platforms scale very differently.</p>

<p>Essentially, they scale <em>per request by default</em>. There’s no “instance” for you to add.</p>

<p>Every incoming event will find capacity by the provider launching more copies of your function as necessary. This leads to effectively unlimited concurrency out of the box, which is great for absorbing traffic spikes without any configuration. The flip side is limited control over this scaling.</p>

<p>Normally, every request that comes in will result in a new Node.js runtime starting if the existing ones are all busy. This is an awesome way to ensure reliability in a scenario where you traffic increases quickly.</p>

<p>However, there are two big tradeoffs: cold starts and cost unpredictability.</p>

<p>When serverless scales, many of those new function invocations might incur a cold start delay (a few hundred milliseconds or more to initialize a Node environment). In a high-traffic scenario, you could have lots of functions cold-starting, which might cause latency for some requests. More importantly, from a cost perspective, serverless billing is usually metered by time and memory per execution, plus any external service calls (like database or bandwidth).</p>

<p>If you get 1000 concurrent requests frequently, you pay for 1000 function runs in parallel, which can add up quickly. I see <a href="https://x.com/mattwelter/status/1949850488654143932" target="_blank" rel="noopener">developers on X</a> and Reddit all the time complaining that their Vercel bills ballooned under heavy load.</p>

<p><figure>
  <img alt="A post on X complaining about a big increase in their Vercel bill" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/d18bd154-fcd2-4736-2ca7-1ec9ce343600/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/d18bd154-fcd2-4736-2ca7-1ec9ce343600/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/d18bd154-fcd2-4736-2ca7-1ec9ce343600/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>This isn’t to say serverless can’t be cost-effective. For super volatile but low-average traffic, it can be the cheapest option.</p>

<p>If you require tight control and predictability, a PaaS with the right autoscaling tool might be preferable. If you need to handle unpredictable surges and are okay with the stateless function model, serverless will do it out of the box. Just keep an eye on those usage metrics!</p>

<h2 id="picking-your-hosting-option-based-on-developer-experience">Picking your hosting option based on developer experience</h2>

<p>I&rsquo;ve thrown a bunch of information at you, but I don&rsquo;t want to make my opinion unclear.</p>

<p>I think you should prioritize developer experience. Whether you&rsquo;re trying to decide where to host a solo project or influence a decision for an enterprise, put real weight behind the developer cost that comes with the &ldquo;cheaper&rdquo; options.</p>

<p>Beyond that, the decision comes down to your application’s type and its traffic profile.</p>

<p>Ask yourself a few questions about your Node.js app:</p>

<p>Does your app require persistent connections or background processes? If it does, then a serverless platform (Vercel/Netlify) likely <em>won’t</em> serve you well. You’d <strong>lean towards a PaaS</strong> or even your own VPS if you&rsquo;re okay being pretty hands-on.</p>

<p>How much ops work are you (or your team) willing to take on? If you have a strong DevOps skillset or an ops team, hosting on VPS or some pure cloud solution might be a good fit. You’ll get full flexibility to tailor the environment and potentially save on high-volume costs by squeezing more out of each server. But if you’d rather <em>not</em> deal with server management, then PaaS or serverless is attractive.</p>

<p>What are your scaling and traffic patterns? For relatively steady, predictable traffic, it can be more cost-effective and simpler to run a fixed number of servers (or dynos) on a PaaS or VPS. You won’t get surprises in the bill, and you can ensure they’re always warm and performant. For spiky or highly variable traffic, serverless is an option.</p>

<p><strong>Choose the platform that fits the shape of your app and your team.</strong> For a typical web API or monolithic Node app that has a mix of web requests and background jobs, a PaaS will provide the least friction. If you’re building a highly interactive frontend-heavy app (especially with Next.js), deploying the frontend on Vercel or Netlify can be great for the static+serverless benefits, possibly complemented by a separate backend for any heavy lifting. </p>
]]>
      </content:encoded>
    </item>
    <item>
      <title>Choosing the Right Node.js Job Queue</title>
      <description>So you've got a Node.js app, and you know what needs to be passed off to a job queue. But do you know what job queuing system to use? Learn how to choose the right one for your needs..</description>
      <pubDate>Mon, 5 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://judoscale.com/blog/node-task-queues</link>
      <guid>https://judoscale.com/blog/node-task-queues</guid>
      <author>Jeff Morhous</author>
      <content:encoded>
        <![CDATA[<p>Modern Node.js apps often need to perform background jobs. Offloading to a job queue is a great way to preserve web performance when faced with sections of code that are too slow or resource-intensive to handle during an HTTP request. If your app needs to send emails, generate PDFs, process images, or aggregate data, you probably need background jobs.</p>

<p>Offloading these jobs (sometimes called <em>tasks</em>) to a <strong>job queue</strong> ensures your web process remains responsive and keeps latency down. A typical setup is to have your web processes enqueue jobs to an external system, and one or more <strong>worker</strong> processes consume and execute those jobs asynchronously.</p>

<p><figure>
  <img alt="Node job queues" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/c6f68ade-abd2-48de-cfbf-fcc2b0f1b600/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/c6f68ade-abd2-48de-cfbf-fcc2b0f1b600/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/c6f68ade-abd2-48de-cfbf-fcc2b0f1b600/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>This works well for keeping your web processes free and performant.</p>

<p>So you&rsquo;ve got a Node.js app, and you know what needs to be passed off to a job queue. But do you know what job queueing system to use?</p>

<p>If you&rsquo;re looking for a quick answer, I won&rsquo;t make you wait. BullMQ is right most of the time. But let&rsquo;s take a look at our options!</p>

<h2 id="bull-and-bullmq-for-job-queues">Bull and BullMQ for job queues</h2>

<p><a href="https://bullmq.io/" target="_blank" rel="noopener">BullMQ</a> is definitely the <strong>most popular Node.js job queue</strong> (especially if you also consider Bull).</p>

<p>It is a powerful queue library backed by <em>Redis</em>, known for its high performance and rich feature set. Bull can process a large volume of jobs quickly by leveraging Redis and an efficient implementation under the hood.</p>
<div class="my-8 rounded-3xl px-6 py-1 bg-sky-50 dark:bg-sky-900/30 dark:ring-1 dark:ring-gray-300/10" >
  <h4 class="font-medium text-sky-900 dark:text-sky-400">
    👀 Note
  </h4>
  <div class="mt-2.5 text-sky-800 prose-a:text-sky-900 dark:prose-a:text-white prose-code:text-sky-400 dark:text-sky-200 dark:prose-code:text-gray-300">
    <p><strong>Understanding Bull vs BullMQ:</strong> One really important thing to note is that <strong>Bull’s original library is now in maintenance mode</strong>. The authors have moved efforts to <strong>BullMQ</strong>, a modern TypeScript rewrite that will receive new features going forward.</p>

  </div>
</div>

<p>Jobs are persisted in Redis, so they won’t be lost if a worker crashes. Bull provides job persistence, automatic retries, error handling, and priority queues. Together, this gives you an unbeatable expectation of reliability.</p>

<p>BullMQ also supports multiple workers consuming the same queue, and you can configure concurrency (the number of jobs a single worker can process in parallel). This horizontal scaling ability means BullMQ can handle a lot of load and is also perfect for autoscaling, which we&rsquo;ll get into later.</p>

<p><figure>
  <img alt="Scaling BullMQ" loading="lazy" x-init="$el.removeAttribute(&#39;loading&#39;)" src="https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/e1b189e9-baab-4161-bb9b-e964f1757300/width=96,quality=30,blur=20,format=jpeg" x-data="{&quot;src&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/e1b189e9-baab-4161-bb9b-e964f1757300/width=96,quality=30,blur=20,format=jpeg&quot;,&quot;fullResSrc&quot;:&quot;https://imagedelivery.net/g5ziwLsypgTqGag7aYHX0w/e1b189e9-baab-4161-bb9b-e964f1757300/public&quot;}" :src="src" x-intersect="src = fullResSrc">
  
</figure>
</p>

<p>BullMQ is essentially a new (major) version of <a href="https://github.com/OptimalBits/bull" target="_blank" rel="noopener">Bull</a>, with mostly the same API and using Redis, but with improved internals. If you&rsquo;re already using Bull, that&rsquo;s fine. But if you&rsquo;re starting fresh, consider BullMQ so you get long-term support and benefit from the improvements.</p>

<p>Since they&rsquo;re Redis-based, Bull and BullMQ are naturally suited for modern web apps that may run across multiple processes. It&rsquo;s no surprise <a href="https://judoscale.com/blog/ultimate-guide-scaling-sidekiq" target="_blank" rel="noopener">Ruby&rsquo;s Sidekiq uses Redis too</a>.
All workers connect to the same Redis instance, so adding more worker processes (whether permanently or by autoscaling) increases the throughput of job processing. Jobs will be pulled by any available worker.</p>

<p>BullMQ includes mechanisms to detect stalled jobs, such as requeueing failed jobs. For most web applications, a single Redis-backed queue can coordinate dozens of workers reliably. If your app already uses Redis, BullMQ fits in nicely. If not, you&rsquo;ll need to introduce Redis just for the queue, which is probably a worthwhile tradeoff for the reliability it provides in most cases.</p>

<h2 id="bee-queue-for-job-queues">Bee-Queue for job queues</h2>

<p><a href="https://github.com/bee-queue/bee-queue" target="_blank" rel="noopener">Bee-Queue</a> is another popular Redis-backed job queue for Node. It&rsquo;s designed with a focus on simplicity and speed, inspired by the shortcomings of older libraries. Like BullMQ, Bee-Queue requires a Redis instance to operate, a common theme we&rsquo;ll continue to see.</p>

<p>Bee-Queue intentionally has a smaller feature set than BullMQ, trading breadth of features for low complexity and high performance. It gives us all of the core job queueing capabilities, but leaves out some of the advanced features of BullMQ.</p>

<p>This tradeoff is right for some people, as it&rsquo;s notably easier to get started.</p>

<p>The library’s API is relatively straightforward. You create a queue, define a job processor function, and enqueue jobs. My time reading Bee-Queue’s examples and documentation has been stress-free as they&rsquo;re very easy to understand. This can translate to faster initial setup and less overhead in learning the tool, something that&rsquo;s really underrated in medium-sized software projects.</p>

<p>Despite being lightweight, Bee-Queue does include essentials for production. You get persistence in Redis, job completion callbacks, and even rate limiting and retry logic. It supports job timeouts, retry attempts, and will handle <em>“stalled job”</em> detection.</p>

<p>What it lacks is some features of Bull and BullMQ, like built-in priority levels or repeatable (scheduled) jobs.</p>

<p>Multiple Bee-Queue worker processes can consume from the same queue even if they&rsquo;re on different machines, making scaling as simple as running more workers. This makes it a great fit for autoscaling scenarios.</p>

<p>In practice, you’d run one or more worker processes with Bee-Queue. If you need more throughput, just increase the number of workers, and jobs will be distributed across them. If you’re okay with using Redis (and most Node apps can add Redis via a managed service fairly easily), Bee-Queue provides a nice balance of <strong>simplicity and performance</strong>.</p>

<p>Still, it&rsquo;s been 2 years since the last release of Bee-Queue, and the lack of recent maintenance/development may put off a lot of developers.</p>

<h2 id="agenda-for-job-queues">Agenda for job queues</h2>

<p><a href="https://github.com/agenda/agenda" target="_blank" rel="noopener">Agenda</a> is a different breed of job queue for Node when compared to BullMQ and BeeQueue. It is primarily a job scheduler built on <a href="https://www.mongodb.com/" target="_blank" rel="noopener">MongoDB</a>, <em>not Redis!</em> It focuses on scheduling jobs (think cron jobs and delayed jobs), but it also supports immediate job queuing with concurrency control.</p>

<p>Agenda is a popular choice, especially for teams already using MongoDB, since it uses your MongoDB database to store job information. If I were in a project not already using MongoDB, this wouldn&rsquo;t be my first choice.</p>

<p>Agenda’s features overlap with BullMQ and Bee-Queue in some areas, but it has its own philosophy. Agenda stores jobs in a MongoDB collection, so if your application already uses MongoDB, you don’t need an extra infrastructure component for the queue. Jobs are persisted to the database, which ensures durability.</p>

<p>Agenda can also work with other databases (it supports a few Mongo-like interfaces), giving <em>some</em> flexibility in persistence. Still, it shines in scheduling future or recurring jobs. It offers a human-readable syntax (but still supports cron syntax) and the ability to schedule jobs at specific dates or intervals.</p>

<p>For example, you can schedule a job to run every day at 8 am, or run once a week, all using cron patterns or (close to) plain English. This makes Agenda ideal for background jobs that need to run on a schedule.</p>

<p>Agenda runs as a single process scheduler. It pulls jobs from Mongo and processes them in the same process. It does support concurrency (multiple jobs at once in one process) and can be scaled to multiple processes using MongoDB’s locking mechanism (to ensure two processes don’t run the same job).</p>

<p>However, scaling horizontally with Agenda is not as straightforward as with Redis queues. Agenda is generally single-master, meaning one instance should be scheduling to avoid duplicate scheduling of recurring jobs, though multiple workers can cooperate on different jobs. It&rsquo;s not impossible to scale horizontally, of course, but the path isn&rsquo;t as straightforward.</p>

<p>Agenda is probably best suited for applications that need cron-like scheduling and already use MongoDB. If you have a Node app in production that&rsquo;s already using Mongo, you can use Agenda to schedule jobs without introducing Redis. It’s great for things like daily reports, periodic cleanup jobs, or any job that must run X times a day/week without needing to support another infrastructure piece.</p>

<h2 id="using-a-message-broker-like-rabbitmq">Using a message broker like RabbitMQ</h2>

<p>Instead of using a Node-specific library, you can opt for a <strong>message broker service</strong> such as <a href="https://www.rabbitmq.com/" target="_blank" rel="noopener">RabbitMQ</a>, <a href="https://aws.amazon.com/sqs/" target="_blank" rel="noopener">Amazon SQS</a>, or <a href="https://docs.cloud.google.com/tasks/docs" target="_blank" rel="noopener">Google Cloud Tasks</a>. These are not Node.js libraries. They&rsquo;re external systems that Node can interface with through their APIs or client libraries.</p>

<p>For example, RabbitMQ is a robust open-source message queue that many large systems use. In a Node app, you might use a package to publish and consume messages from RabbitMQ.</p>

<p>The advantage of brokers like RabbitMQ is primarily reliability and advanced messaging patterns like acknowledgments and dead-letter queues.</p>

<p>Similarly, cloud services like AWS SQS or even Google Cloud Tasks are fully managed queues. They remove the need to run Redis or RabbitMQ yourself, which is attractive to a lot of people. These can scale virtually indefinitely and handle autoscaling scenarios by design.</p>

<p>The trade-off with using external cloud queues is that you’ll have to implement some features in your application code, like deciding how to schedule jobs or doing retries. Also, there’s a bit more latency as calls go over the network. Developer experience might not be as seamless as using a Node library, but if you prefer not to manage any infrastructure, they are a very reasonable option.</p>

<h2 id="autoscaling-your-workers">Autoscaling your workers</h2>

<p>Scaling Node job queues is a necessary part of running them in production. Offloading intensive jobs to queues doesn&rsquo;t do much for the performance of the queue processing itself, which isn&rsquo;t that performant.</p>

<p>There are two big levers you can pull to scale your Node job queues. <strong>Vertical scaling</strong> means using more powerful workers with more threads/processes. Meanwhile, <strong>horizontal scaling</strong> increases the number of worker processes or machines. Comprehensive solutions require attention to both.</p>

<p>As we talked about above, the major Node job queues support horizontal scaling without too much hassle, so it&rsquo;s worth putting some effort into. You can do this manually, but it&rsquo;s best practice to set up an autoscaler.</p>

<p>This lets you keep your hands off, adding worker processes when your existing processes can&rsquo;t keep up with demand, and removing them when demand allows, which saves you costs. Still, most autoscalers leave much to be desired. Heroku&rsquo;s autoscaler doesn&rsquo;t work for workers, and other major platforms that have support use CPU as the autoscaling metric, which is not an optimal way to measure demand on asynchronous worker processes.</p>

<p>Judoscale is a powerful autoscaler that you can add to most any hosting setup. The autoscaling algorithm <strong>scales based on queue latency</strong>, which is a much better indicator of queue well-being than CPU usage. If you&rsquo;re running a Node app in production, try <a href="https://judoscale.com/node" target="_blank" rel="noopener">Judoscale&rsquo;s free plan</a> to see if it&rsquo;s right for you.</p>

<h2 id="comparing-node-job-queue-options-and-making-a-decision">Comparing Node job queue options and making a decision</h2>

<p>My opinion here is somewhat controversial in that I think you should value developer experience <em>a lot</em> in your decision-making. That means <strong>using BullMQ</strong> unless you <em>really need</em> a ton of extra features, in which case use a message broker like RabbitMQ.</p>

<p>If your app environment already includes a certain datastore, leaning into that can simplify setup. For instance, if you use Redis, Bull or BullMQ will be straightforward to add. If you use MongoDB, Agenda might integrate more naturally. A solution that fits your existing stack usually means less friction for you, which I think you should place a premium on.</p>
]]>
      </content:encoded>
    </item>
  </channel>
</rss>
