Deploy a Rails App to an EC2 Instance Using Capistrano

Screenshot 2020 09 07 at 7.36.40 pm
Mohd Sameer Author
August 31, 2020
Aws capistrano

This post is part of the AWS for the Bootcamp Grad series. The series consists of exercises to build familiarity with various aspects of the AWS ecosystem. Again, all of these posts are “exercises” for introductory exposure to AWS — they are NOT represented as best practices.

(This post is part of the AWS for the Bootcamp Grad series. The series consists of exercises to build familiarity with various aspects of the AWS ecosystem. Again, all of these posts are “exercises” for introductory exposure to AWS — they are NOT represented as best practices.)

In this exercise, you will deploy a Rails app from Github to an EC2 instance using Capistrano. You will utilize AWS RDS (Relational Database Service) for your production database.

Specifically, you will do the following:

A bit of background on some of the tooling used in this exercise:

Nginx: Nginx is a web server that you will install and use on your EC2 instance. Nginx listens for HTTP requests on port 80 and responds with HTML.

Passenger: Passenger is a web application server for your Rails application. It integrates with Nginx and provides the app server layer needed to run your Rails application. This blog post explains the role of the web server and app server in a Rails application, and how Passenger is a unique type of app server.

Capistrano: Capistrano is a deployment tool that helps you get your app from Github to the EC2 instance, and to run the various commands needed to deploy the new code (compiles assets, runs migrations, etc). It maintains a release history and allows for easy rollbacks. Capistrano essentially works by SSH-ing into your EC2 server and executing commands for you.

This exercise draws heavily from the following two Passenger tutorials.

However, due to some key differences in this exercise (incorporating an RDS database, handling environment variables, etc), I created this separate exercise.

Navigate to the RDS dashboard from the AWS console. Click Instances from the sidebar on the left and then the button to Launch DB Instance.

Select PostgreSQL as your Engine Option and click Next. Select the Dev/Test option as it is free and you do not need multiple availability zones. Click Next.

Click the option to Only Enable Options for Free Tier. For the Master Identifier, enter hello-world; for the Master Username, enter hello_world. Enter a password and click Next.

Accept all of the default options in the Configure Advanced Settings section and launch your instance.

Once your RDS instance is provisioned, you will need to relax the inbound TCP traffic rules. From the RDS dashboard, select your instance, click on the the Security Group rule for inbound traffic. Edit the inbound rules to allow traffic from anywhere (0.0.0.0/0).

Follow the steps in the Launch an EC2 Instance section of Exercise 1.1 — except select the Ubuntu Server 16.04 Server as your Amazon Machine Image. Also, in the final step, you will NOT need to create a new key-pair. You can select and utilize the key-pair file from your previous exercises.

Note: The following steps will work for rails 5.0 and ruby 2.3.0 (If you are using Rails 5.1+, production secrets will need to be handled differently).

Create the Rails App:

From your local computer:

rails new hello_world -d postgresql
cd hello_world
bundle install
rails g scaffold person name:string

Add Gems to your Gemfile

Add gem ‘figaro’ to your Gemfile (Figaro will help you manage your environment variables securely).

Then, add the following Capistrano-related gems to the development section of your Gemfile:

gem 'capistrano'
gem 'capistrano-bundler'
gem 'capistrano-passenger', '>= 0.1.1'
gem 'capistrano-rails'
gem 'capistrano-rvm'
gem 'capistrano-figaro-yml', '~> 1.0.2'

After you have done that, run:

bundle install 
bundle exec figaro install
bundle exec cap install

Configure Capistrano

That last command created a number of new files, including the Capfile at the root of your project. The Capfile is the entry point for Capistrano and includes some of the instructions that Capistrano will execute to deploy your application.

Edit the Capfile to the following:

require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
require "capistrano/rvm"
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
require "capistrano/passenger"
require 'capistrano/figaro_yml'
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

Configure the config/deploy.rb file as shown below, making sure to edit the the rvm_ruby_version value to the ruby version for your project and repo_url value to where this repo will be located on github (commit and push now if needed). This file sets variables that Capistrano will use when deploying to any environment (staging, production, etc).

set :application, "hello_world"set :repo_url, "git@github.com:<your-github-username>/hello_world.git"set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')set :rvm_ruby_version, '<your-ruby-version>'set :passenger_restart_with_touch, true

Configure the config/deploy/production.rb file to the following, being sure to insert the public IP of your EC2 instance and path to your key-pair file as indicated below. This file sets variables that are specific to deployment in the production environment.

role :app, %w{deploy_user@<public-ip-of-your-ec2-instance>}
role :web, %w{deploy_user@<public-ip-of-your-ec2-instance>}
role :db, %w{deploy_user@<public-ip-of-your-ec2-instance>}
set :ssh_options, {
keys: %w(<absolute-path-to-your-ec2-key-pair-file>),
forward_agent: false,
auth_methods: %w(publickey password)
}

In essence, the above provides Capistrano with the information it needs to SSH into your EC2 instance as the deploy_user. Note that the deploy_user is a user we will create on the EC2 instance in a later step. This user will not have sudo privileges.

Manage Your Environment Variables and Create Your Database

Edit the production section of your config/database.yml as follows:

production:
<<: *default
database: hello_world_production
username: hello_world
password: <%= ENV['HELLO_WORLD_DATABASE_PASSWORD'] %>
host: <%= ENV['HELLO_WORLD_DATABASE_HOST'] %>
port: <%= ENV['HELLO_WORLD_DATABASE_PORT'] %>

Next, you will add the ENV variables to the config/application.yml file (it was created when you ran the figaro command earlier and will not be committed to source control).

Before editing this file, gather your environment variables.

First, run bundle exec rake secret. This output will be your SECRET_KEY_BASE value (NOTE: this will not work with Rails 5.1+).

Then, navigate to your RDS instance on the AWS console to get the endpoint value of your instance — something like: hello-world.stuff.us-east-1.rds.amazonaws.com.

Now, add the following to your config/application.yml file (inserting the correct values for each key):

HELLO_WORLD_DATABASE_PASSWORD: <password-you-created-on-RDS>
HELLO_WORLD_DATABASE_HOST: <endpoint-value-of-RDS-instance>
HELLO_WORLD_DATABASE_PORT: "5432"
SECRET_KEY_BASE: <output-from-previous-command>

Now you are ready to create your production database:

RAILS_ENV=production rails db:create

Initialize your git directory, commit your code and push it to Github.

SSH into your EC2 instance:

SSH -i <path-to-key-pair> unbuntu@<ec2-public-ip>

Prepare the Machine

Run the following commands

sudo apt-get update
sudo apt-get install -y curl gnupg build-essential
sudo apt-get install libpq-dev

Install RVM

First, install RVM with the following commands.

sudo gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3curl -sSL https://get.rvm.io | sudo bash -s stablesudo usermod -a -G rvm `whoami`if sudo grep -q secure_path /etc/sudoers; then sudo sh -c “echo export rvmsudo_secure_path=1 >> /etc/profile.d/rvm_secure_path.sh” && echo Environment variable installed; fi

LOGOUT of your EC2 instance and LOG BACK IN. Otherwise, RVM will not be activated.

Next, install the ruby version for your project (insert your version number in the following commands):

rvm install ruby-<version-number>
rvm --default use ruby-<version-number>

Install bundler, node and rails (with the Rails version for your project).

gem install bundler -no-rdoc --no-risudo apt-get install -y nodejs &&
sudo ln -sf /usr/bin/nodejs /usr/local/bin/node
gem install rails -v <your-rails-version>

Install Passenger and Nginx

The following commands will install Passenger and Nginx:

sudo apt-get install -y dirmngr gnupgsudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7sudo apt-get install -y apt-transport-https ca-certificates

sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger xenial main > /etc/apt/sources.list.d/passenger.list'
sudo apt-get updatesudo apt-get install -y nginx-extras passenger

Configure Nginx to Work with Passenger

Uncomment the following line in the http block of the /etc/nginx/nginx.conf file:

include /etc/nginx/passenger.conf;

Then restart nginx: sudo service nginx restart

As mentioned earlier, you will use Capistrano to deploy your app as the deploy_user (this user will not have sudo privileges). In this step, you will create that user on your EC2 instance and give them the permissions needed to complete the deployment.

sudo adduser deploy_user

Enter and confirm a password for the user. Leave the other requested inputs blank.

To allow the deploy_user access to the instance, you need to install the ubuntu user’s SSH key on the deploy_user’s directory:

sudo mkdir -p ~deploy_user/.ssh
touch $HOME/.ssh/authorized_keys
sudo sh -c "cat $HOME/.ssh/authorized_keys >> ~deploy_user/.ssh/authorized_keys"sudo chown -R deploy_user: ~deploy_user/.ssh
sudo chmod 700 ~deploy_user/.ssh
sudo sh -c "chmod 600 ~deploy_user/.ssh/*"

Create the directory where your application code will be cloned and change the ownership of that directory to the deploy_user:

sudo mkdir -p /var/www/hello_world
sudo chown deploy_user: /var/www/hello_world

Login as Deploy User

To login:

sudo -u deploy_user -H bash -l

Set up Github Credentials:

Run the following commands (insert your email address into the first command):

ssh-keygen -t rsa -C "<your-email-address"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub

Take the output from the last command and add the SSH key to your Github account.

Set up RVM (as deploy_user)

Set the rvm-version (insert the version for your app):

rvm use ruby-<insert-version-number>

Then run the following command:

passenger-config about ruby-command

The output will look something like the following

passenger-config was invoked through the following Ruby interpreter:
Command: /usr/local/rvm/gems/ruby-2.3.3/wrappers/ruby
Version: ruby 2.3.3p85 (2015-02-26 revision 49769) [x86_64-linux]
...

Copy the value shown as the command to your clipboard, e.g.: /usr/local/rvm/gems/ruby-2.3.3/wrappers/ruby

Edit Nginx Configuration File

Exit from the deploy_user by typing exit.

Create the following file etc/nginx/sites-enabled/hello_world.conf and add the following content:

server {
listen 80;
server_name <insert-your-ec2-public-dns>;

root /var/www/hello_world/current/public;

passenger_enabled on;
passenger_ruby <path-output-from-previous-command>;
}

Restart Nginx withsudo service nginx restart

Return to directory of your app on your local machine. Run the following command to setup your environment variables on the EC2 instance.

bundle exec cap production setup

(From your EC2 instance, as the deploy_user, you will now see the application.yml file in the /var/www/hello_world/shared/config directory)

Then, deploy your app from your local machine with the following:

bundle exec cap production deploy

When that completes, you will now have /var/www/hello_world/current and /var/www/hello_world/releases directories on your EC2 instance. The current directory is symlinked to the latest release in the releases directory (you can see that with a ls -la)

Navigate to the people endpoint at the public DNS address of your EC2 instance, and you should see your app. For example: http://ec2-stuff.compute-1/amazonaws.com/people

Navigate to the instances section of the EC2 dashboard, select your instance, and click on Actions. Select Instance State → Terminate. Confirm that you want to terminate.

Navigate to the instances section of the RDS dashboard, select your instance, and click on Actions → Delete. Follow the rest of the instructions that are meant to save you from unintentionally deleting your database. You do not need to create a final snapshot.


Tags



Comments (1)

Av 5
Ritika 21 days ago

Nice Post