Migrate a Rails Appfrom Heroku to Render

By Chris Castle

Render is a unified, full-stack development platform where you define what’s possible. Whether you’re a solo developer, growing startup, or established enterprise, Render provides just what you need to bring your ideas to life.

Render makes it quick to deploy a Ruby on Rails app, but if you’re moving an existing Rails app over to Render, it might not be obvious where to start. We’ve created a language and framework-agnostic migration guide, but I thought it would help to provide a Rails-centric example.

This post will walk through migrating a non-trivial Ruby on Rails app to Render. To make it even more realistic, we’ll use an open source, production Rails application running on Heroku: https://git-scm.org. This is the main informational site about the git project. From it, you can download the latest version of git, search the documentation, and learn everything and more about git. It’s a Rails app that uses PostgreSQL for data persistence, Redis for caching, Elasticsearch for fast site searches, and scheduled jobs to pull-in documentation from new git releases. The code for the site is open source, and we can see that it’s currently using Rails v6.11.

But first, why might you want to migrate your Rails app from Heroku to Render? Check out our comparison page. It explains all the benefits you’ll get – like free private networking, HTTP/3, and free DDoS protection, among many other things.

Concept Mapping

Before going through the migration, let’s map some Heroku concepts to Render concepts.

HerokuRender
Web Process (within a Heroku app)Web Service
Worker Process (within a Heroku app)Background Worker
DynoAn instance of your service on Render
Heroku PostgresRender PostgreSQL
Heroku RedisRender Redis
Heroku SchedulerCron Job
Config VarsEnvironment Variables2

On Heroku, an app is the parent for other resources – e.g., a Postgres database, a Redis instance, or multiple processes. Render doesn’t have this hierarchical relationship (yet). You can deploy an independent managed Redis instance or a standalone PostgreSQL database if that’s all you need. Instead, Render provides Blueprints (its implementation of Infrastructure-as-Code) to allow you to orchestrate multiple services. For example, here’s how you might define and integrate a PostgreSQL database and a Web Service on Render with a Blueprint:

services:
  - name: my-rails-app
    type: web
    env: ruby
    repo: https://github.com/render-examples/rails-6
    buildCommand: bundle install
    startCommand: bundle exec puma -C config/puma.rb
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: my-rails-db
          property: connectionString

databases:
  - name: my-rails-db

Deploying this Blueprint to Render will create a Web Service and a PostgreSQL database and ensure that the Web Service has the unique database connection string for my-rails-db. No copying and pasting necessary!

With that conceptual framing out of the way, let’s walk through the process of deploying the git-scm Rails app to Render.

Deploy git-scm Code on Render

We will deploy the code using Render’s native Ruby environment. Let’s start building out a render.yaml file that we’ll place at the root of the repository. This file is the Blueprint that defines and integrates all the components of the working production application. If you want to follow along interactively with the rest of this blog post, fork the git-scm repository.

One important thing to note is that we could use the Render Dashboard to create and configure each component of this app. However, codifying the app’s architecture in a render.yaml reduces the chance of human error, reduces repetitive point-and-click configuration, and gives us an overview of the architecture in a single place.

Let’s jump into creating our render.yaml. Here’s what we’ll do.

  1. Create a Web Service
  2. Add a Database
  3. Add Redis
  4. Update Build Steps
  5. Add Bonsai Elasticsearch
  6. Add Cron Job
  7. DRY It Up

Create a Web Service

We’ll begin by defining a Web Service for the Rails app in our render.yaml.

services:
  - name: git-scm-example-site
    type: web
    env: ruby
    buildCommand: bundle install
    startCommand: bundle exec puma -C config/puma.rb
    envVars:
      - key: SECRET_KEY_BASE
        generateValue: true

Let’s make sure each line is clear.

  • name is a name for our service to make it easy to find on the Render Dashboard. It’s also used to generate an .onrender.com URL.
  • type tells Render that we’d like to create a Web Service.
  • env specifies that we’d like to use Render’s native Ruby environment. This environment includes OS packages that common Ruby libraries need in addition to Ruby and Rails specific environment variables.
  • buildCommand tells Render the command to run to pull in all the dependent libraries specified in the Gemfile. The default value is bundle install, but you can modify it.
  • startCommand tells Render the command to run to start the Rails app.
  • SECRET_KEY_BASE is an environment variable that Rails requires, and generateValue: true tells Render to generate a base64-encoded 256-bit secret for its value on the first deploy.

Add a Database

Now we need a PostgreSQL database for the app to write to or read from. Let’s add that to our render.yaml.

# …snip…
databases:
  - name: git-scm-db
    ipAllowList: [] # only allow connections from services in this Render account

That was simple, but how do we connect this database to the Web Service?

services:
  - name: git-scm-example-site
    type: web
    env: ruby
    buildCommand: bundle install
    startCommand: bundle exec puma -C config/puma.rb
    envVars:
      - key: SECRET_KEY_BASE
        generateValue: true
      - key: DATABASE_URL        fromDatabase:          name: git-scm-db          property: connectionString
databases:
  - name: git-scm-db
    ipAllowList: [] # only allow connections from services in this Render account

The highlighted lines above create an environment variable whose value is the connection string for the database.

Add Redis

This Rails app uses Redis for caching, so let’s add it.

services:
  # …snip…
  - name: git-scm-redis
    type: redis
    ipAllowList: [] # only allow connections from services in this Render account

And similar to the database, we now need to tell the Rails Web Service how to access the Redis instance.

services:
  - name: git-scm-example-site
    type: web
    env: ruby
    buildCommand: bundle install
    startCommand: bundle exec puma -C config/puma.rb
    envVars:
      - key: SECRET_KEY_BASE
        generateValue: true
      - key: DATABASE_URL
        fromDatabase:
          name: git-scm-db
          property: connectionString
      - key: REDIS_URL        fromService:          name: git-scm-redis          type: redis          property: connectionString
  - name: git-scm-redis
    type: redis
    ipAllowList: [] # only allow connections from services in this Render account

databases:
  - name: git-scm-db
    ipAllowList: [] # only allow connections from services in this Render account

The highlighted lines create an environment variable whose value is the connection string for the Redis instance.

Update Build Steps

When Rails apps are deployed, a few extra commands are commonly run to precompile static assets and run a database migration. Heroku’s Ruby buildpack handles these behind the scenes for you, but Render encourages being more transparent and gives you more control. Let’s create a bin/render-build.sh file containing the following

#!/usr/bin/env bash
# exit on error
set -o errexit

bundle install
bundle exec rake assets:precompile
bundle exec rake assets:clean
bundle exec rake db:migrate

And then we’ll change the buildCommand in the render.yaml from bundle install to bin/render-build.sh.

Add Bonsai Elasticsearch

The git-scm.org site uses an Elasticsearch cluster managed by Bonsai to make the extensive git documentation quickly searchable. We can sign-up for a free sandbox Elasticsearch cluster and configure our Rails app to use that. The git-scm code expects to find a URL to access the Elasticsearch cluster in the BONSAI_URL environment variable.

# …snip…
# within the `envVars` property of the `git-scm-example-site` Web Service
      - key: BONSAI_URL
        sync: false
# …snip…

You might have expected to see the URL for our Elasticsearch cluster as the value of the BONSAI_URL environment variable. However, we don’t want to paste the URL there because it contains secrets that we don’t want to save in a file in our repository, similar to a database connection string. Instead, sync: false tells Render to ask us for the value of this environment variable during the first deploy of the Rails app.

Add Cron Job

This Rails app runs a few commands nightly to keep the site content up-to-date:

  • bundle exec rake preindex updates the site with the documentation from new git releases.
  • bundle exec rake downloads updates the links on the site to download the most recent version of git.
  • bundle exec rake remote_genbook2 pulls the Pro Git book into the site.
  • bundle exec rake search_index indexes the man page content in Elasticsearch.
  • bundle exec rake search_index_book indexes the Pro Git book content in Elasticsearch.

Let’s create a Render Cron Job to run these commands every day at 3 am UTC.

  - name: git-scm-nightly-job
    type: cron
    env: ruby
    schedule: 0 3 * * *
    buildCommand: bundle install
    startCommand: >
      bundle exec rake preindex
      && bundle exec rake downloads
      && bundle exec rake remote_genbook2
      && bundle exec rake search_index
      && bundle exec rake search_index_book
    envVars:
      - key: SECRET_KEY_BASE
        generateValue: true
      - key: REDIS_URL
        fromService:
          type: redis
          name: git-scm-redis
          property: connectionString
      - key: DATABASE_URL
        fromDatabase:
          name: git-scm-db
          property: connectionString
      - key: BONSAI_URL
        sync: false
      - key: GITHUB_API_TOKEN
        sync: false

You might have noticed that the definition of the Cron Job looks very similar to that of the Web Service. A Render Cron Job is not tied to a parent app or Web Service like Heroku’s Scheduler add-on. It is defined on its own. A Cron Job builds and runs code from any repository on a specified schedule, whereas the Heroku Scheduler can only execute a command using an existing deployed app.

Two other things to note about this Cron Job definition are the schedule property and the GITHUB_API_TOKEN environment variable. schedule is a cron expression that defines when to run the job – in this case, we’re running it every day at 3 am UTC. The GITHUB_API_TOKEN is needed by the rake tasks that the Cron Job runs because they download man pages and the Pro Git Book from a few different GitHub repositories.

DRY It Up

As I mentioned previously, there’s some duplication in the render.yaml between the Web Service and Cron Job. We can reduce some of this duplication using an Environment Group, which is a set of environment variables that can be maintained in one place and shared with multiple services. Let’s move the SECRET_KEY_BASE, GITHUB_API_TOKEN, and BONSAI_URL environment variables to an Environment Group.

envVarGroups:
  - name: git-scm-shared
    envVars:
      - key: SECRET_KEY_BASE
        generateValue: true
      - key: GITHUB_API_TOKEN
        sync: false
      - key: BONSAI_URL
        sync: false

Now we can remove the definition of those environment variables from the Web Service and Cron Job and add the following property to their envVars objects.

      - fromGroup: git-scm-shared

We’re now ready to deploy a fully working version of the git-scm.org site to Render! Here’s a fork of the git-scm.org repository to which I’ve added our render.yaml and build script. Initiating a Blueprint deploy on Render with this render.yaml will deploy all the site’s components – Web Service, Cron Job, PostgreSQL Database, and Redis – and connect them to each other. You’ll need to create a Bonsai Elasticsearch cluster and add a value for the BONSAI_URL environment variable for site search to work.

Background Worker

Another common component of Ruby on Rails apps is a background worker that executes outside of your app’s HTTP request/response cycle. It is often used for things like sending emails or SMS messages, or generating invoice PDFs. The git-scm.org site doesn’t use any background workers, but Render has native support for them. For example, here’s a guide that explains how to use the Sidekiq job scheduler library with a Rails app on Render.

Other Considerations

Here are some other tips for a successful Ruby on Rails deploy on Render:

  • Add a .ruby-version file to the root of your repository if you don’t have one already. Render will use this to determine which version of Ruby to install when deploying your app. It should contain a single line with a Ruby version number.
  • You might want to copy the value of the SECRET_KEY_BASE environment variable from Heroku to Render so your users aren’t logged out of your app after the migration. Doing this will allow any cookies your Heroku app set on user browsers to be readable by your app running on Render.
  • If you don’t know all the components of your Ruby on Rails app on Heroku, looking in these files can give you some clues: Procfile, app.json, heroku.yml. You may not have these files, but if you do, they will help you understand the architecture of your app.

Ready to Try Render?

If you’re thinking about migrating your Rails app from Heroku to Render, rest assured that there are many Rails apps running successfully on Render. Hopefully this post helps you understand the process. If you have questions not addressed here, please ask them on our user community or contact Render’s awesome support engineers.


  1. 6.0, 6.1, and 7.0 are being actively updated by the Rails core team as of today. Support for the previous version, Rails 5.2, will be discontinued in less than two weeks.
  2. Render also provide Environment Variable Groups, which allow you to share a set of environment variables with multiple services.
Interested in working with Chris at Render? Check out our open roles!

Chris Castle

Chris Castle formerly headed up Developer Relations for Render.

Subscribe to our newsletter for monthly product updates.

Discover More

  1. How BeerMenus Elevated its Craft with a Seamless Move to Render

    Issues with Heroku mounted, so BeerMenus founders migrated to Render. They reduced costs, gained better Support, and adopted game-changing features like Preview Environments....

    - Rosalind Benoit

  2. OpenSSL Patch

    Render services are not affected by the CVEs announced for OpenSSL 3.0 earlier today. We recommend that organizations patch OpenSSL 3.0.0 and above....

    - Ed Ropple

  3. Render Newsletter Vol. 4 - October 2022

    This periodical for the Render community keeps you up to date on new guides and content, product updates, and developer news....

    - Rosalind Benoit

  4. Shipping Monorepo Support

    Monorepo Support is now generally available for all Render customers. Your feedback has shaped what we've shipped and allowed us to design flexible features to help you define how...

    - Shantanu Joshi