Render takes your infrastructure problems away and gives you a battle-tested, powerful, and cost-effective cloud with an outstanding developer experience. Focus on building your apps, shipping fast, and delighting your customers, and leave your cloud infrastructure to us.
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.
Before going through the migration, let’s map some Heroku concepts to Render concepts.
|Web Process (within a Heroku app)||Web Service|
|Worker Process (within a Heroku app)||Background Worker|
|Dyno||An instance of your service on Render|
|Heroku Postgres||Render PostgreSQL|
|Heroku Redis||Render Redis|
|Heroku Scheduler||Cron Job|
|Config Vars||Environment 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.
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.
- Create a Web Service
- Add a Database
- Add Redis
- Update Build Steps
- Add Bonsai Elasticsearch
- Add Cron Job
- DRY It Up
We’ll begin by defining a Web Service for the Rails app in our
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.
nameis a name for our service to make it easy to find on the Render Dashboard. It’s also used to generate an
typetells Render that we’d like to create a Web Service.
envspecifies 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.
buildCommandtells 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.
startCommandtells Render the command to run to start the Rails app.
SECRET_KEY_BASEis an environment variable that Rails requires, and
generateValue: truetells Render to generate a base64-encoded 256-bit secret for its value on the first deploy.
Now we need a PostgreSQL database for the app to write to or read from. Let’s add that to our
# …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.
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.
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
bundle install to
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.
This Rails app runs a few commands nightly to keep the site content up-to-date:
bundle exec rake preindexupdates the site with the documentation from new
bundle exec rake downloadsupdates the links on the site to download the most recent version of
bundle exec rake remote_genbook2pulls the Pro Git book into the site.
bundle exec rake search_indexindexes the man page content in Elasticsearch.
bundle exec rake search_index_bookindexes 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.
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
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
- 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.
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.
Here are some other tips for a successful Ruby on Rails deploy on Render:
- Add a
.ruby-versionfile 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_BASEenvironment 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:
heroku.yml. You may not have these files, but if you do, they will help you understand the architecture of your app.
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.
- 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.↩
- Render also provide Environment Variable Groups, which allow you to share a set of environment variables with multiple services.↩