A Quick Guide to Deploy Rails 5.2 Application on AWS EC2

Share This Post

We Love Rails and we use it widely at Gleecus to power the digital backbone of some of our customers’ products and solutions.

Ruby on Rails has come a long way since its early days and there are tons of articles/tutorials/guides on the internet explaining how to do something-everything in Rails (ask the tech geeks at Twitter, GitHub or Netflix). Nevertheless, during a recent interaction with one of our customers, we were talking at length on how the ecosystem evolved around Rails and how more and more people are embracing it as the de-facto framework for their web application development. We wanted to show them how swiftly Rails apps can be deployed but surprisingly couldn’t find an authentic tutorial from our quick Google search to walk them through end-to-end. Understandably, we decided to pull one ourselves. Let’s dive in.

We decided to use AWS for the sake of this tutorial. Why not? AWS offers an awesome free tier that gives limited access to most of its service suite. Let’s grab an account and login to the console. You should see N.Virginia on the top right corner of the page. Click on that and you will see a list of AWS regions. Select the region of your choice. Now use the search bar on the page under AWS services, type EC2 and click on the first result. It should take you to the Ec2 dashboard.

Look for a big blue button that says Launch instance. Clicking on that takes you to a page where you see multiple steps on the top. Select Ubuntu Server 16.04 in the 1st step and t2.micro in 2nd step. Follow through steps 3,4,5 without any changes. In the 6th step, add a new rule for HTTP and mark Source as anywhere for both rules. In the next step review our selection so far and click on Launch. Here you will be asked to create a key pair to log in to your instance. A key pair is a set of public-private keys. Create a new key pair, give it a name and download the private key file (file.pem) to your local machine. Click on Launch Instances and give a couple of mins for the new instance to spin up.

Now that we have our EC2 instance spinned up, let’s access it using SSH.

Locate the pem file that we’ve downloaded to our local machine in the previous steps. Navigate to that directory and run the following command to modify permissions of the pem file.

 

cd /path/to/your/pem/file.pem
chmod 400 file.pem

Now SSH into the EC2 instance using the pem file

ssh -i "file.pem" [email protected]

Once you are on the EC2 instance, run the following commands to set up the server.

sudo apt-get update

sudo apt-get install autoconf bison build-essential libssl-dev libyaml-dev
libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm3 libgdbm-dev
git-core curl libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev systemd
libcurl4-openssl-dev python-software-properties

 

Once the required dependencies are installed, let us move on to install Ruby and Rails. We will use rbenv to manage multiple ruby versions on our server.

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
type rbenv ## to see if rbenv is installed correctly
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
rbenv install 2.5.1
rbenv global 2.5.1
ruby -v ## must display 2.5.1 if installed correctly
which ruby ## must show the fully qualified path of the executable
echo "gem: --no-document" > ~/.gemrc ## to skip documentation while installing gem
gem install bundler
rbenv rehash ## latest version of rbenv apparently don't need this. Nevertheless, lets use it to avoid surprises.
gem env home
gem install rails -v 5.2.1
rbenv rehash
rails -v ## must display 5.2.1 if installed correctly

 

With Ruby and Rails installed, let us move on to install Javascript runtime Node JS.

curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v ## must display v8.11.4
npm -v ## must display v5.6.0

 

Rails 5.2 (remember our rails version?) requires yarn to manage the dependencies listed in the package.json file. Yarn installation tries to install Node along with it by default. Let us try to skip it as we already have Node installed.

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install --no-install-recommends yarn
yarn -v ## must display v1.9.4

Lets quickly move on to setting up the databases. Run the following commands to setup PostgreSQL and its users.

sudo apt-get install postgresql postgresql-contrib libpq-dev
sudo -i -u postgres
psql
create user dbuser with password ‘dbuser12345';
create database website_production owner dbuser;
create database dbuser owner dbuser;
alter user dbuser superuser createrole createdb replication;
\q
exit
psql dbuser -h 127.0.0.1 -d website_production
## key in the password when prompted to login.

and run these to get MySQL up and running.

sudo apt-get install mysql-server mysql-client
mysql_secure_installation ## answer to the prompts as required.
CREATE USER ‘dbuser’@‘localhost’ IDENTIFIED BY 'dbuser12345';
GRANT ALL PRIVILEGES ON * . * TO ‘dbuser’@‘localhost’;
FLUSH PRIVILEGES;
systemctl status mysql.service
sudo systemctl start mysql

With the runtime environment setup on the server, let’s move on to our local machine.

Create a Rails 5.2 application, stuff it with whatever you want, make sure that it is running absolutely fine locally and push the changes to a remote repository on Github/Gitlab (not the scope of this article).

Come back to our EC2 instance. Create an SSH key for ubuntu user on the server. Copy the public key and add it as a deployment key for the remote repo on Github/Gitlab.

ssh-keygen -t rsa -b 4096 # press ENTER whenever prompted
cat ~/.ssh/id_rsa.pub # copy this to clipboard

Clone the repository into ~/apps directory on the server.

mkdir ~/apps
cd ~/apps
git clone [email protected]:user-name/repo-name.git

Navigate to root directory of the project and make sure that you have database.yml and master.key present with appropriate data. Run the following commands from your project root.

cd repo-name
bundle install
yarn install
RAILS_ENV=production rake db:create
RAILS_ENV=production rake db:migrate
RAILS_ENV=production rake assets:precompile

With the project set up on the server, let us have Nginx configured as a reverse proxy for the Puma web server which serves our shiny new Rails application from EC2 instance. If you’re with me so far, you know that Nginx isn’t installed yet on our EC2 instance. Let’s get that now.

sudo apt-get install nginx
systemctl status nginx
nginx -v ## must display v1.10.3
sudo systemctl restart nginx
sudo systemctl enable nginx

Now that Nginx is installed and is running, let’s create a server block for Nginx.

cd /etc/nginx/sites-available
sudo touch our-website
sudo vim our-website

Add the below content to the server block that we just created.

upstream app {
# Path to Puma SOCK file, as defined previously
server unix:/home/ubuntu/apps/repo-name/shared/sockets/puma.sock fail_timeout=0;
}
server {
listen 80;
listen [::]:80;
root /home/ubuntu/apps/repo-name/public;
index index.html index.htm index.nginx-debian.html;
server_name domain.tld www.domain.tld;
try_files $uri/index.html $uri @app;
location @app {
proxy_pass http://app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
access_log /home/ubuntu/apps/repo-name/log/nginx.access.log;
error_log /home/ubuntu/apps/repo-name/log/nginx.error.log;
}
location ~* ^/assets/ {
# Per RFC2616 - 1 year maximum expiry
expires 1y;
add_header Cache-Control public;
# Some browsers still send conditional GET requests if there's a
# Last-Modified header or an ETag header even if they haven't
# reached the expiry date sent in the Expires header.
add_header Last-Modified "";
add_header ETag "";
break;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
}

Check for any possible issues with our server block and make sure that they are fixed. Restart Nginx at the end.

sudo nginx -t
sudo systemctl restart nginx

Observe that we’ve added an upstream block above the server block which is responsible to redirect the incoming traffic from Nginx to Puma using UNIX socket. Now let us get done with setting up Puma to handle these requests and serve our Rails application.

Open Puma configuration file in the project (~/config/puma.rb) and replace the existing content with this.

threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart
# Change to match your CPU core count
workers 1
# Min and Max threads per worker
threads 1, 6
app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/shared"
# Default to production
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env
# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"
# Logging
stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true
# Set master PID and state locations
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
activate_control_app
on_worker_boot do
require "active_record"
ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env])
end

As the last step, lets setup systemd service to manage Puma restarts. Create a file puma-website.service under /etc/systemd/system

cd /etc/systemd/system
sudo touch puma-website.service
sudo vim puma-website.service

and add the following content to the above file

After=network.target
[Service]
# Foreground process (do not use --daemon in ExecStart or config.rb)
Type=simple
# Preferably configure a non-privileged user
User=ubuntu
Group=ubuntu
# Specify the path to your puma application root
WorkingDirectory=/home/ubuntu/apps/repo-name
# Helpful for debugging socket activation, etc.
Environment=PUMA_DEBUG=1
#EnvironmentFile=/var/www/my-website.com/.env
# The command to start Puma
ExecStart=/home/ubuntu/.rbenv/shims/bundle exec puma -C /home/ubuntu/apps/repo-name/config/puma.rb
Restart=always
[Install]
WantedBy=multi-user.target

Now, you can start the Puma service as

sudo systemctl start puma-website.service

and make it start on system boot as

sudo systemctl enable puma-website.service

We are now good with the necessary setup on EC2 instance.

Let’s move on to the portal where your domain DNS is being managed and add an A record with your EC2 Public IP mentioned as host. Give it some time to propagate. Once done, type domain.tld in your browser and you should see your homepage served.

Yay !! that marks the end of this article. Share the love if you find it useful.

Looking for a Strategic Digital Transformation Partner?

Contact Us

More To Explore