I recently moved two of my side projects off of Heroku. This was in reaction to two things: a longstanding sense that Heroku is stagnating (see their recent outage), and a reaction to their price increases that make Heroku a bad fit for my small projects. This post describes the process I used to evaluate alternatives and to migrate onto Render.

Why I Originally Chose Heroku

I have had projects deployed on Heroku since 2012 or 2013. I originally chose it because it was the easiest way to put a Rails project onto the web. It was a “batteries included” hosting solution at a time that AWS still required a great deal of configuration (and was frankly too complex for me as a novice software developer). Heroku remains popular in the Rails community, but since its acquisition by Salesforce it has remained largely stagnant. The main reason I have used Heroku for more recent projects was simply familiarity rather than an honest evaluation of the choices available.

The two apps I currently have hosted there are pretty basic: Rails apps using Postgres as their database, plus Sidekiq workers and Redis. My current Heroku bill is $28 per month. With their planned price increases, this would’ve gone up to $76 per month. I considered doing nothing and just absorbing the increase, but at nearly $600 per year in new costs it made sense to spend a couple of hours exploring the other offerings available.

Evaluating Alternatives

What are the best options if you’re looking to hire a new hosting solution in 2022? In my search I narrowed in on three options:

  • Fly.io
  • Render
  • AWS

The thing that attracted me to Fly.io was its tool for importing Heroku projects. This includes not just the app settings, but also the configuration of how many app and worker nodes you have deployed as well as the app’s environment variables. Importing the environment variables means that when you initially deploy your Fly.io app it will be communicating with your Heroku database. This means that the app works right away. However, if you decide to switch to Postgres hosted by Fly.io there is not an easy way to import the backup that you export from Heroku. This made Fly.io a non-starter for my migration case. Had I used Fly, I was looking at a monthly bill of $26-38.

Render was the other major hosted solution I evaluated. Its interface was simple and intuitive, and its documentation was very thorough: I found both step-by-step migration guides as well as detailed information on each step of the process. One difference between Render and Heroku is that there is no logical grouping between my app, worker, and storage systems–I just set them up and pointed them at one another using the internal URLs. This means that I can scale these independently, and if I wanted to have another app share the Redis or Postgres resources for some reason I could do so (that doesn’t make it a good idea, but still). To host my apps with comparable resources I’m looking at a monthly bill of $28 on Render once the 90-day Postgres free tier expires.

The third option I considered was moving to AWS directly. Since 2013 or 2014 I’ve used AWS for work on a regular basis, and am familiar with the most common services. Had I moved to AWS I would’ve switched from Postgres to Dynamo, and would’ve replaced my Sidekiq usage with Lambda. In terms of out-of-pocket costs this likely would’ve been the cheapest option ($10 or less per month thanks to the generous free tier), but it would’ve taken the most effort given the rearchitecting involved. I decided to postpone this option since I could always do this later.

After a bit of exploration, I decided to migrate to Render. The remainder of this post describes the migration process and my first impressions.

Migrating to Render

Once I chose Render, the migration process itself was straightforward (and greatly aided by their documentation).

  1. Deploy the app on Render, while still using Heroku Postgres and Redis resources
  2. Deploy a Postgres DB on Render, and point the app at this new DB
  3. Deploy Redis on Render, and point the app at this resource
  4. Deploy a Background Worker for Sidekiq, pointed at the Redis instance from Step 3

The key question for Step 1 was how much effort it took. This was really easy: just connect my GitHub account and select the right repo. The only hiccup was that I needed to do a manual deploy to clear the cache in order to get the build to succeed. Since the app is pointed at the same DB, I could navigate to the Render deployment and login to the app using my existing account to verify that everything worked. Step 2 broke that ability temporarily until I restored from a backup. You could do the backup restoration process multiple times until your Render deployment starts serving production traffic (Step 7).

At this point I had a working deployment of my app but it wasn’t serving production traffic. It was a “split brain” from my Heroku deployment, since database writes in either environment weren’t propagated to the other environment. Once I had tested this thoroughly, I was ready to make my Render app serve production traffic.

  1. Put the Heroku app in maintenance mode to block writes
  2. Take a final backup from Heroku and import it on Render
  3. Change DNS to point to Render instead of Heroku

Once you do this, Render is your primary deployment and users of your app will be interacting with the Render version instead of the Heroku version. Once you’ve served production traffic for long enough to have confidence, you can complete the final two steps (either consecutively or with some waiting time in between). During Step 7 I had about 10 minutes of downtime as DNS switched over. If downtime is a crucial consideration for you this may not be acceptable, but it was OK for my use case.

  1. Turn down Heroku resources to the cheapest tiers available (or turn them off completely)
  2. Destroy the Heroku app

Everything before step 8 is reversible, so you have plenty of time to decide whether to commit to the new solution. This is a low-risk approach and one that I felt very comfortable with. The next section describes my first impressions as a Render user.

First Impressions

The best thing about moving to Render was how easy it was. I also like that the app and its resources are managed separately, making it easy to scale them independently. The migration would not have been as smooth without Render’s great documentation.

The only con I’ve encountered so far is that, as I mentioned above, I had to do a “clear cache” manual deployment to get both of my apps started up correctly. There seems to be a bit of delay between changing environment variables and having that change take effect (beyond deploying and restarting the app). Neither of these has been a big drawback so far.

Lessons Learned

My biggest takeaway is that there are viable Heroku competitors out there, and there’s no need to feel locked in. Depending on your needs you could potentially save hundreds of dollars per year moving to a competitor. Beyond the cost savings, you can also learn a new deployment tool without too much complexity.

I wanted a simple option to get my app working right away, with minimal migration effort. With that in mind, I’m happy I chose Render. If I had wanted to rearchitect my app I might have gone with native AWS. As I learn more I’ll be sure to share it here.