DEPLOYING RAILS APP TO EC2 INSTANCE WITH CAPISTRANO USING NGINX AND PUMA





This is a guide/tutorial on how to setup and deploy your Rails application to AWS Ec2 instance (Ubuntu Server). In this tutorial, I worked from an Ubuntu 16.10 and  just recently I decided to try it on Linux Mint (which is also Debian based like Ubuntu) and it worked! In this tutorial I made use of the following tools:

  • RVM (on local machine as well as Ec2 instance)
  • PostgreSQL
  • Capistrano
  • GitHub

I also did a previous tutorial on how to deploy your rails app from a Windows machine to an AWS instance(Ubuntu Server) with just GitHub, so if you are interested, check it out

Let’s get started with deploying your rails application to an Ec2 instance with Capistrano using nginx and puma server:

1. CREATE RAILS APP

rails new commodre -d postgresql

Commodre is a simple blog site, and we’ll use scaffold for that. . .

cd commodre
rails g scaffold Blog title:string content:text

Update config/database.yml by adding your username, password, host: localhost. 

To your /config/routes.rb add:

root ‘blogs#index’

Back to your terminal. . . 

rake db:create
rake db:migrate
rails s

Go to localhost:3000 and you should see the blog home page. . . 

WORKS FINE! Let’s stop the server: Ctrl C

2. CAPISTRANO AND PUMA SETUP (ON LOCAL MACHINE)

To your Gemfile add:

gem ‘figaro’gem ‘puma’ #Should already be in your Gemfilegroup :development do  gem ‘capistrano’  gem ‘capistrano3-puma’  gem ‘capistrano-rails’ #Should already be in your Gemfile  gem ‘capistrano-bundler’  gem ‘capistrano-rvm’end

Back to your terminal. . .

bundle install

To generate Capistrano configuration run (on the terminal)

cap install STAGES=production

If that doesn’t work, then run bundle exec cap install STAGES=production 

This should generate some files like you see in the image above.

To your Capfile, add: 

require ‘capistrano/rvm’require ‘capistrano/bundler’require ‘capistrano/rails/migrations’require ‘capistrano/rails/assets’ require ‘capistrano/puma’

To your deploy.rb add:

set :application, ‘commodre’set :repo_url, ‘git@github.com:ohiodn8/commodre.git’ # should match git reposet :branch, :masterset :deploy_to, ‘/home/deploy/commodre’set :pty, trueset :linked_files, %w{config/database.yml config/secrets.yml}set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads}set :keep_releases, 5set :rvm_type, :userset :rvm_ruby_version, ‘ruby-2.4.0’ # Should match ruby version
set :puma_rackup, -> { File.join(current_path, ‘config.ru’) }set :puma_state, “#{shared_path}/tmp/pids/puma.state”set :puma_pid, “#{shared_path}/tmp/pids/puma.pid”set :puma_bind, “unix://#{shared_path}/tmp/sockets/puma.sock”    #accept array for multi-bindset :puma_conf, “#{shared_path}/puma.rb”set :puma_access_log, “#{shared_path}/log/puma_error.log”set :puma_error_log, “#{shared_path}/log/puma_access.log”set :puma_role, :appset :puma_env, fetch(:rack_env, fetch(:rails_env, ‘production’))set :puma_threads, [0, 8]set :puma_workers, 0set :puma_worker_timeout, nilset :puma_init_active_record, trueset :puma_preload_app, false

To your .gitignore, add:

/config/database.yml/config/secrets.yml

3. SETUP EC2

I’m guessing you know how to setup up an Instance already. I’ll be using an Ubuntu Instance (free tier).

Over to your aws.amazon.com account, find and click the link to Ec2. On the Ec2 dashboard click the launch Instance button

Select Ubuntu Server

Choose the instance you prefer… I’m using t2.micro (free tier eligible)

Next through the steps. . .

I’ll create a new security group for this project

Add the security group rule like you see in the image above and then click the review and launch button. . . then launch button… then select a keypair. I’ll create one for the project and remember to download the key pair (very important) and then launch the instance.

Scroll down and click view Instances button

Move the downloaded key pair silence.pem to a folder. I’ll create a new folder (Cred) on my Desktop for keys.

4. TIME TO START A NEW TERMINAL FOR YOUR EC2 INSTANCE/SERVER

Still on my local machine, you may want to start a new terminal for this step.

cd into the folder you have your key pair and change the access right. . .

cd /home/odion/Desktop/Cred

 (this is for my local machine, you should use your username and directory)

chmod 400 silence.pem

Now time to login into to the ec2 instance/server (copy your IPV4 public DNS from your running Ec2 Instance)

ssh -i silence.pem ubuntu@ec2-34-210-21-124.us-west-2.compute.amazonaws.com

Congratulations! You’re now in your Ec2 instance. We will patch the instance by running. . .

sudo apt-get update && sudo apt-get -y upgrade

Let’s create a user

sudo su

The command above will place you in the root directory

adduser deploy

and it’ll prompt you to add a password, I’ll use deploy. . . leave the rest of the fields blank by clicking enter. 
To check if the user was created. . .

id deploy

Give the new user sudo rights

sudo nano /etc/sudoers

Add:

deploy  ALL=(ALL:ALL) ALL

and save the file by clicking Ctrl X. It’ll ask for yes and no…type y and click Enter.Switch to the deploy user and generate an ssh key for the user:

su - deploy
ssh-keygen

Do not set a passphrase and just click enter.
List the directory:

ls -al

And now you have .ssh directory.

cd .ssh
ls

You have id_rsa and id_rsa.pub

cat id_rsa.pub

Copy the the key and paste in an empty notepad. head over to your GitHub account.

5. SETUP GIT REPOSITORY

 Off to your GitHub account and create new repository

On the new page remember to copy ssh git link to deploy.rb Back to your local machine terminal, type. . .

git init
git add .
git commit -am '1st_commit'
git remote add origin git@github.com:ohiodn8/commodre.git
git push -u origin master

Refresh your github page, and your app directory should show.
Note: If you get an ssh error and you’re unable to push your app to your GitHub repository, follow this link. It may resolve the issue.
click the user icon at the top right of your GitHub page and click Settings

Click SSH and GPG Keys

Click the New SSH key Button

Name it what ever you like. I’ll name mine commodre key. Paste in the id_rsa.pub key you copied from your Ec2 Server and then click save SSH key button.
Head back to the Ec2 Instance/Server Terminal and install git (you probably have it already)

sudo apt-get install git

To test the ssh connection, run. . .

ssh -T git@github.com

It may ask for a yes/no… type yes and you should get a response like the image below. 

6. 

Now head over to your Local Machine terminal. . . this step is important. If you ever tried the Capistrano setup before, and while deploying got some SSH NET error, this might just fix it.On your local machine terminal:

cd /home
ls

You should see your user directory, mine is odion.

cd odion/.ssh
ls

I generated id_rsa and id_rsa.pub in here after getting the SSH NET error and that fixed, so you’ll probably want to try it. . .  (if you followed the link in step  5, you already generated this key and you don’t have to follow the step below).

ssh-keygen
ls

You should see id_rsa and id_rsa.pub (if you have this in the directory, no need to generate the keys).

cat id_rsa.pub

Copy the keys and just like you did with the Linux Instance keys, paste this into your GitHub/SSH keys settings, and after that return to your local machine and type. . .(if you followed the link in step 5 just jump to the next step).

ssh -T git@github.com

Back to your Ec2 Instance/Server, you should still be in the .ssh directory. So, in the .ssh directory :

nano authorized_keys

Paste the same keys you copied from your local machine (and put in GitHub) in the authorized_keys, and then save.

7. TIME TO INSTALL NGINX:

Still in your Ec2 Instance/Server:

cd ..
pwd

You should get something like /home/deploy

sudo apt-get install nginx

Once nginx is installed, logout by typing

exit

This should take you back to the root user. Log back in. . .

su - deploy

It’ll prompt you for your password, remember we used deploy.

We have to edit a file. Instead of nano we’ll be using vim

sudo vi /etc/nginx/sites-available/default

and comment out every field. To comment out, click i on your keyboard and you should see insert at the bottom of the terminal. Now comment the white fields out by adding # in front( reason why we’re using vim), they should be blue. At the bottom of the page paste. . .

upstream app {  # Path to Puma SOCK file, as defined previously  server unix:/home/deploy/commodre/shared/tmp/sockets/puma.sock fail_timeout=0;}
server {  listen 80;  server_name localhost;
  root /home/deploy/commodre/current/public;
  try_files $uri/index.html $uri @app;
  location / {    proxy_set_header X-Forwarded-Proto $scheme;    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    proxy_set_header X-Real-IP $remote_addr;    proxy_set_header Host $host;    proxy_redirect off;    proxy_http_version 1.1;    proxy_set_header Connection ”;    proxy_pass http://app;  }
  location ~ ^/(assets|fonts|system)/|favicon.ico|robots.txt {    gzip_static on;    expires max;    add_header Cache-Control public;  }
  error_page 500 502 503 504 /500.html;  client_max_body_size 4G;  keepalive_timeout 10;}

I’m guessing you know how to save with vim. If you’re more of a nano user (like me), just click ESC button, Shift : button, typewq! and hit Enter.

8. TIME TO INSTALL RVM, RUBY, NODEJS AND POSTGRESQL

 Still on the Ec2 Instance/server

gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -sSL https://get.rvm.io | bash -s stable 
source /home/deploy/.rvm/scripts/rvm
rvm install ruby

This may prompt you for your deploy password (which is deploy). When the installation is complete, you can type ruby -v to check the version of ruby.  If you have another version of ruby on your local machine, install that using rvm install ruby-2.*.*To list the versions, type 

rvm list

Install bundler. . .

gem install bundler --no-ri --no-rdoc

Install node JS. . .

sudo apt-get install -y nodejs

If you don’t install nodejs, your cap deployment will stop mid way and return an error.
Install and Setup Postgresql. . .

sudo apt-get install postgresql postgresql-contrib libpq-dev
sudo -u postgres createuser -s commodre
sudo -u postgres psql
\password commodre 

Type a password. I’ll use commodre.Quit by typing \qCreate a database:

sudo -u postgres createdb -O commodre commodre_production

Time To Create Directories for the capistrano deployment:

Still in the Ec2 Instance/Server:

mkdir commodre
mkdir -p commodre/shared/config
nano commodre/shared/config/database.yml

Paste the following codes and save. . .

production:  adapter: postgresql  encoding: unicode  database: commodre_production  username: commodre  password: commodre  host: localhost  port: 5432
nano commodre/shared/config/secrets.yml

Paste the following code (and save):

production:  secret_key_base: <%= ENV[“SECRET_KEY_BASE”] %>

9. ALMOST DONE. . . 

Go to your application folder on your Local Machine /config/deploy/production.rbScroll to the bottom and add

server ‘34.210.21.124’, user: ‘deploy’, roles: %w{web app db}

Put your Ec2 public IPV4 where it says server and save.

On our local machine terminal, try to find your way to your application

pwd
cd /home/odion/sites/commodre

(That’s for me. . . try to find your way to your app. . .)
Now run. . .

cap production deploy 

. . .and that should do it.
Okay! To your Ec2 Instance/Server. . .  your rails app should be deployed in here now.Lets add a secret key to our secrets.yml (well, to our server environment)

cd /home/deploy/commodre/current
ls

that should list your rails app root directory.

rake secret

if that doesn’t work bundle exec rake secret
 You’ll get a long string. . . copy and paste in an empty notepad, then run. . .

sudo nano /etc/environment

paste to the bottom:

export SECRET_KEY_BASE=rake secret
ruby -e ‘p ENV[“SECRET_KEY_BASE”]

where rake secret is the long string you copied, like so. . .

 Then run. . .

 echo $SECRET_KEY_BASE

If that doesn’t return your rake secret, logout by typing. . .

exit

and log back in with 

su - deploy 

try to run it again

echo $SECRET_KEY_BASE

It should return your rake secret, and we’re good.

Restart nginx server. . .

sudo service nginx restart

and you may see this error 

(if you don’t, great!)

It happens. . . guess we made an error, let’s find out whats wrong

sudo vi /etc/nginx/sites-available/default

And that’s the error. . .

 It is still white comment out the bracket.

Now lets try to restart the server

sudo service nginx restart

No error this time. Off to a browser and put your IPV4 address or Public IPV4 DNS. I’ll use the IP address.
Great! Now we’re served a 502 error page. . . just where we want to be. Let’s fix this:  To fix the nginx 502 error, First thing is to know what happened, so we’re going to check the nginx log.  Run. . .

sudo cat /var/log/nginx/error.log

 And there’s the error right there. . . 
It tried to connect to unix:/home/deploy/commodre/shared/tmp/sockets/puma.sock, and it says “No such File”. Remember we fixed that as our upstream path. Now this is because we haven’t started puma server itself, so lets go do that.Make sure you’re in your app directory (still in Ec2 Instance/Server terminal). . .

pwd 

should return
/home/deploy/commodre/currentIf you’re not in the current directory, please get there and run 

bundle exec puma -e production -b unix:/home/deploy/commodre/shared/tmp/sockets/puma.sock

and puma server should start up. Go  back to your web browser and refresh the 502 error page. Refresh page. . . and everything should be working fine. We don’t want puma running like that, so we’re going to stop the server and daemonize it. Add a -d to the code:

bundle exec puma -e production -d -b unix:/home/deploy/commodre/shared/tmp/sockets/puma.sock

You can check to see if the process is running. . .

ps aux | grep puma

 Restart the daemon puma server :
kill -s SIGUSR2 24673
Stop the server:kill -s SIGTERM 24673
THE ENDI’ll continue this tutorial by adding a monitoring tool known as Monit in a part 2 tutorial. Hope this long tutorial helped you.