Skip to content

Instantly share code, notes, and snippets.

@Ravenstine
Last active February 27, 2024 00:26
Show Gist options
  • Save Ravenstine/d9210663c36b7949740c to your computer and use it in GitHub Desktop.
Save Ravenstine/d9210663c36b7949740c to your computer and use it in GitHub Desktop.
How to Host a Rails App on a Home Server

Host to Host a Rails App on a Home Server

Hosting services like Heroku and Amazon EC2 are nice. That is, until they cost money. Some things are worth running on your own hardware, especially when the cost and Terms of Service requirements outweigh the expense of rolling your own hosting.

I am writing this because I recently had to figure all this out in order to host a personal blog off a Raspberry Pi, and I thought I'd share what I learned. This guide assumes that you already know how to install Ruby and you know how to use Rails. If you don't, look those up first before coming back to this guide.

Prerequisites

  • Ruby >=2.0
  • Rails >=4.0
  • Nginx
  • Debian(or Raspbian)
  • A domain name hosted through Amazon Route53

Your Gemfile

Add these lines:

gem 'whenever', require: false
gem 'aws-sdk', '~> 1', require: false

I chose version 1 of the AWS SDK because there were some existing implementations of my use case for it and it seemed a tad simpler than how it would work with version 2.

A Record Update Rake Task

If you don't know what an A record is, basically, your home server probably has a dynamic IP address so it changes every once in a while; you need to tell Route53 what your new IP adddress is so that your domain points to your server. Here's is the task that I placed in the file lib/tasks/update_a_record.rake:

require 'aws-sdk'

task :update_a_record => [:environment] do

  r53 = AWS::Route53.new(
    :access_key_id => Rails.application.secrets.aws['access_key_id'],
    :secret_access_key => Rails.application.secrets.aws['secret_access_key'])

  change = {
    :action => 'UPSERT',
    :resource_record_set => {
      :name => Rails.application.secrets.aws['domain'],
      :type => "A",
      :ttl => 600,
      :resource_records => [{:value => `wget http://ipinfo.io/ip -qO -`}]
    }
  }

  r53.client.change_resource_record_sets({
    :hosted_zone_id => "#{Rails.application.secrets.aws['hosted_zone_id']}",
    :change_batch => {
      :changes => [change]
    }
  })

end

The different authentication values and my domain name are stored in config/secrets.yml. If you don't know how to use secrets.yml, the Rails official guides explain it.

My task is pretty simplistic and does a fairly "logicless" update every day at 12am. I've found this to be sufficient because my IP address rarely changes and, since few people read my blog, I really don't care if the IP address is wrong for a while. If you want to go the extra mile, you could change it to check if there's a difference in IP addresses since the last time it ran, and schedule it to run more often.

Cron

We are going to use the Whenever gem to write a custom Crontab file that will do two things for us:

  1. Automatically start our Rails server on reboot
  2. Periodically update the A record on our domain.

Write something like the following in a new file config/schedule.rb in your Rails project:

every '0 0 * * *' do
  rake "update_a_record", environment: :production
end

every '@reboot' do
  command "source /home/pi/.rvm/scripts/rvm && cd /home/pi/blog && bundle exec rails s -b 0.0.0.0 -p 3000 -e production -d &"
end

You will need to edit the line in the @reboot block to reflect the path where your Rails app is located on your server. source /home/pi/.rvm/scripts/rvm is required if you are using RVM. The reason we are using Cron to autostart our server is because I've found it a pain in the ass to get it to work in root and all that. This is just way easier this way and it gets stored directly in the Rails project as opposed to some separate file in the Linux directory structure.

Once you have this written, execute whenever -w in your terminal and you're done.

Nginx

Inside your /etc/nginx/nginx.conf file(thought it may be located elsewhere), make sure this line is in the http section:

include /etc/nginx/sites-enabled/*;

Then create the file /etc/nginx/sites-enabled/default. Fill in the path to your Rails project.

upstream rails_server {
  server 0.0.0.0:3000;
}

server {
  listen 80;

  location / {
    root [PATH TO YOU RAILS PROJECT]/public;
    try_files $uri @missing;
  }

  location @missing {
    proxy_pass http://rails_server;
    proxy_set_header Host $http_host;
    proxy_redirect off;
  }
}

The reason we are using Nginx is so that we can run the Rails server on port 3000 and it gets routed to port 80 without the need to run our project as root. Nginx also has good logging, and it opens up the possibility of running multiple servers running under the same IP address but different domains.

.bashrc

Let's set Rails to run in Production by default. Add the following line to the top of your ~/.bashrc file:

export RAILS_ENV=production

Last Step

Lastly, run rake update_a_record to update your domain to point to your server's IP address.

Then restart your server, and your app should be up and running on your chosen domain name.

@cj-wallace
Copy link

Hi, thank you for the detailed guide! Do you know if it is possible to get https security while hosting on a home server? Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment