How to Deploy Ruby on Rails to Render.com
April 13, 2021
Hey everyone! Today I want to show you how to deploy a Ruby on Rails application to render.com
So a few things off the top:
So a few things off the top:
- I'll be using an application I've already made, in fact it's from my second course, Learn Hotwire by Building a Forum. One Important Bit: It's already configured to use Postgres for the database in all environments. It also requires Redis to work properly (because it uses Turbo!).
- We'll need PostgreSQL and Redis as additional services.
-
But we won't be covering background jobs / email sending / or image uploads. Those are going to require more work depending on your application.
- I am very interested in trying out their persistent disk storage, but that comes with limitations depending on how you want to use it. (i.e no zero downtime deploys and you're limited to one instance for now).
Render Introduction
Render is a Platform as a Service(PasS) provider, similar to Heroku. Most Rails developers have heard of Heroku, as its super simple to get going with them. One advantage of Render is they bill themselves as a significantly cheaper Heroku. I mean, just look at their comparison page. That seems too good to be true... or does it?
I'm the type of person who wants to explore on my own without resorting to digging through documentation. In Heroku, I know I can just Heroku create my app, deploy it, then go add my resources for redis afterwards and it does all the ENV configuration for me. That's pretty slick and hard to beat, even though you do pay for it in cost.
Part of that setup ease is habit too, I've used Heroku off and on for the better part of like 8 years, so let's try not to let habit taint the views of this new offering.
With Render, you get only the Bells and none of the whistles Heroku has. There's only managed Postgres Databases for now, and with Render, it feels like you get more controls. It's super easy to spin into a shell, and it even seemed like when you opened a shell, you're on the actual instance running the app! (I ran 'ps aux' and it showed me my Puma process! 😱)
I'm the type of person who wants to explore on my own without resorting to digging through documentation. In Heroku, I know I can just Heroku create my app, deploy it, then go add my resources for redis afterwards and it does all the ENV configuration for me. That's pretty slick and hard to beat, even though you do pay for it in cost.
Part of that setup ease is habit too, I've used Heroku off and on for the better part of like 8 years, so let's try not to let habit taint the views of this new offering.
With Render, you get only the Bells and none of the whistles Heroku has. There's only managed Postgres Databases for now, and with Render, it feels like you get more controls. It's super easy to spin into a shell, and it even seemed like when you opened a shell, you're on the actual instance running the app! (I ran 'ps aux' and it showed me my Puma process! 😱)
Setting up our app
Okay, we'll need to make a few changes. Some of these follow Render's own deploying Rails guides. I ignore some of what they do, because I don't have a need for multiple workers for my Puma process.
Update database.yml for production
Heroku will overwrite the database.yml to support the environment they manage for you. You'll need to do this yourself. It's common for Rails apps to use a DATABASE_URL environment variable for storing the credentials for your database. Heroku adds this for you automatically when you provision your application (or a new database) but for Render you'll have to do this yourself.
Update database.yml for production
Heroku will overwrite the database.yml to support the environment they manage for you. You'll need to do this yourself. It's common for Rails apps to use a DATABASE_URL environment variable for storing the credentials for your database. Heroku adds this for you automatically when you provision your application (or a new database) but for Render you'll have to do this yourself.
production: <<: *default url: <%= ENV['DATABASE_URL'] %>
Okay cool, that's good to go.
Let's add a 'bin/render-build.sh' script (again, taken from their guide, but it will allow us to customize it down the road if we need to!)
Let's add a 'bin/render-build.sh' script (again, taken from their guide, but it will allow us to customize it down the road if we need to!)
#!/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
We'll need to ensure we give this script the ability to execute before we commit it!
chmod a+x bin/render-build.sh
Then, lastly, and this part may need to change, but I had to permit my Render subdomain and custom domain I was adding as an allowed origin for ActionCable. Inside of config/environments/production.rb, add:
config.action_cable.allowed_request_origins = [/https:\/\/*/]
In this case I'm doing a bad thing, where I permit all https origins to connect to my Cable server. You'd likely want to list your specific domains, but in some cases if you know what you're doing this is okay.
For me to realize I had to allow it, I saw "request origin not allowed: https://appname.onrender.com" in my logs after I deployed the first time while setting up my app. I'm putting this here, now, so you can maybe avoid the pain.
TBH: I have no idea why I didn't need to make this change on Heroku but did on Render. I imagine it's some kind of behind the scenes magic with Heroku's router or something, but nonetheless, I'll try not to let that bother me. I don't like asking questions I won't like the answer for.
For me to realize I had to allow it, I saw "request origin not allowed: https://appname.onrender.com" in my logs after I deployed the first time while setting up my app. I'm putting this here, now, so you can maybe avoid the pain.
TBH: I have no idea why I didn't need to make this change on Heroku but did on Render. I imagine it's some kind of behind the scenes magic with Heroku's router or something, but nonetheless, I'll try not to let that bother me. I don't like asking questions I won't like the answer for.
Okay, so that was all the changes I needed to make. I git committed those, and then pushed it up to GitHub.
Render setup
Render unfortunately doesn't let you easily group all of your services into an Application or Project. It would be nice if they did... but alas, let's create services we need for our application first, before we go to do our first deploy.
In my case, I need Redis and a PostgreSQL database.
Once you have a Render account and have added a Credit Card...
Let's setup Redis.
In my case, I need Redis and a PostgreSQL database.
Once you have a Render account and have added a Credit Card...
Let's setup Redis.
- Fork this redis repo. We need to fork it, so our account doing deployments can access it when we go to deploy a Private service for redis for it.
- Create a new Private Service
- Select your redis repo you just forked.
- For "name", enter in something you'd like to identify easily. In my case I went with appname-redis, since there's no grouping, we can at least prefix services with appname- for now.
- Ensure Docker is the selected Environment
- Pick a basic plan (I went with Starter for testing!)
- Click Advanced
- Add a new disk. Give it a name and set the mount path to /var/lib/redis. Pick a size for the disk in my case I did: 1 GB should be enough for small projects.
- Click Create Private Service
When it's done creating, you'll see on the page when viewing it: "Service Address" It may look something like "appname-redis:10000" You'll need this later, for example, this is what we'll use inside of a REDIS_URL env for our App web worker.
Let's setup PostgreSQL:
Let's setup PostgreSQL:
- Click New, then click Database in their dropdown.
- Give it a name, again I chose to use appname-database as my name for it. I left the database and username field blank. I also picked the same region I plan to put everything else in. (Oregon in my case)
- Click Create
Once it's done creating, you'll view it and see "Internal Connection String". Copy that value, we'll use that to set it as DATABASE_URL shortly.
Let's create our App service now
Let's create our App service now
- Click New -> Web service
- Select your source code repository, don't make a mistake here, you can't change it later :(
- Give it a name, in my case "appname-web". Ensure it's in the same region as your Database.
- Update build command to be "./bin/render-build.sh" (no quotes!)
- Update Start command to be "bundle exec puma -C config/puma.rb" (no quotes!)
- Click Advanced
- NOTE: You may want to use an Environment Group instead if you also need to create a Background worker (like Sidekiq!) That will let you share these variables between the Web + Background Services (handy!)
- Under environment variables:
- Add DATABASE_URL to the value of the Internal Connection String from your Database service
- Add REDIS_URL as... "redis://serviceaddress:10000", aka, take the service address from your private service, and prefix it with redis:// and you'll be good to go
- Add a RAILS_MASTER_KEY to the value from your config/master.key or config/environmentname/master.key file (if using environment specific keys)
- Or if not using RAILS_MASTER_KEY, add SECRET_KEY_BASE key and set the value to whatever "rails secret" outputs in your terminal.
- Click Create Web Service.
- Let the deploy finish, if it works successfully go try out the app, otherwise read the logs and try and do some troubleshooting. Hopefully it works though! 🎉
- Assuming it worked, go configure your Custom Domain in Settings on your Web service! Then your app can respond to both the onrender.com domain + your custom domains!
Overall thoughts
Render wasn't as quick for me to setup, but it still was faster than doing all of the setup myself. I do wish they had a managed Redis instance, as I have no idea how I'd do a Redis update for my service I just deployed, or if doing so would make me loose data.
Note: This is highly opinionated, and I realize Render is a small team, and it will take time for them to come up to the same level as Heroku (which I've used for years and years). Take this with a grain of salt, what is a Pro or Con for me, may actually be the opposite for you! Try it out!
Pro: Shell access from the browser, the terminal in the browser feels snappy. A+ job there. Felt like I really had an SSH session open, and wasn't limited with what I could do. I even 'vim'd a file, even though I don't think it did anything since there's not a restart option.
Pro: Pull Request Review feature looks neat, but I haven't tried it out.
Pro: it supports HTTP/2 out of the box, which is nice if you're using Turbo Frames.
Con: Access controls, for inviting teammates give full access (even for deleting) which feels like a non-starter for considering it for anything for Work. Maybe Teams let you have different controls.
Con: The community/support being on Discourse is fine for some, but for those of us who are worried about harassment, posting in a public forum is not always something we want to do. I wish there was different support channel (maybe there is, I just couldn't find one). This is especially relevant if say you used it for work and needed custom support. NOTE: The CEO replied to me on twitter and mentioned there's always support [at] render [.] com to email for support, so that helps solve that problem!
Note: This is highly opinionated, and I realize Render is a small team, and it will take time for them to come up to the same level as Heroku (which I've used for years and years). Take this with a grain of salt, what is a Pro or Con for me, may actually be the opposite for you! Try it out!
Pro: Shell access from the browser, the terminal in the browser feels snappy. A+ job there. Felt like I really had an SSH session open, and wasn't limited with what I could do. I even 'vim'd a file, even though I don't think it did anything since there's not a restart option.
Pro: Pull Request Review feature looks neat, but I haven't tried it out.
Pro: it supports HTTP/2 out of the box, which is nice if you're using Turbo Frames.
Con: Access controls, for inviting teammates give full access (even for deleting) which feels like a non-starter for considering it for anything for Work. Maybe Teams let you have different controls.
Con: The community/support being on Discourse is fine for some, but for those of us who are worried about harassment, posting in a public forum is not always something we want to do
Con: Add-ons are clearly new, there was only one "LogDNA" at the time of writing this (April 14th, 2021). Perhaps more will come in the future.
My Verdict: I'm going to consider using it in the future for projects I have upcoming. I may even move services over to cut costs, if I can figure out a smooth way of moving them off Heroku without a lot of pain.
Render: Please change these if you're listening
I have a few things right off the bat I wish were changed... Hopefully these can end up on a Roadmap somewhere and then make their way to Render's users.
- Application Groups for services (i.e let me put my Web + Database + Redis private service into one Group. If I had a background worker, it'd be worse. Let me manage my access controls for a group from that one place too. Thinking about how I'd handle teammate access and whatnot without going full team support, I didn't see a way to limit folks from deleting services either when inviting them...).
- Managed Redis please.
- Allow me to change Source Control repositories after creating a service. I've had to rename repositories for work before and after I setup a service to test with I had no way to switch out my repo used for deployments so... I would have had to go make a completely new service for a production app if we were using Render at the time. Scary and frustrating!
- CLI / + REST API - It'd be so nice to be able to get into a shell from a CLI (ala 'heroku run rails console') or view logs (for small apps that aren't a log streaming disaster). For the REST API... I want to be able to manage my ENV configs + Manage custom domains for my services.