Cloves Carneiro Jr Home Beginning Rails Contact

Deploy a Rails application to AWS – using VPC, EC2, RDS and ELB

It’s 2016, and there are probably a gazillion ways and articles about deploying Ruby on Rails applications to AWS, and I’m writing another one of those.

In my defense, I’m writing this as part of some research I’m doing at work, and want to save those notes for later. I will try to make this more interesting than a plain “Rails to EC2” recipe, I’ll be setting up a VPC, build an AMI ready do run Rails, create a database host using RDS, deploy – and run – a Rails app to an EC2 instance, and setup a Elastic Load Balancer in front of it.

If this ever becomes useful to anyone else other than myself, that’s a plus.

Setup your VPC

We’ll use Amazon Virtual Private Cloud, it “enables you to launch Amazon Web Services (AWS) resources into a virtual network that you’ve defined”. I’ll keep it simple and will setup a VPC

After your VPC is ready, you need to create a second subnet, which will be required by RDS when we need to create a DB Security Group, and also when you create a Elastic Load Balancer; so,

We’re done setting up the VPC, I’m not a networking professional; so, I’d rather not mess things up much :).

Start an instance

Now, it’s time to get a EC2 host running:

In a matter of minutes, your instance will be running, and ready to go.

Setup SSH configuration

I always add an entry to new instances in ~/.ssh/config to make connecting to the instance easier, my configuration looks like:

Host <HOST IP>
HostName <HOST IP>
User ubuntu
IdentityFile "~/.ssh/<YOUR KEY>"

With this in place, you can just ssh HOST IP to connect to your EC2 instance.

Install needed software

Now, we need to install software to allow us to run Ruby on Rails web applications in this host. This is a step that DevOps Engineers will probably want to use a tool like Chef or Ansible.

We will first install packages used for building software, then, we’ll install some tools that will be useful later, such as git and postgresql-client.

sudo apt-get install autoconf bison build-essential libssl-dev libyaml-dev
  libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm3 libgdbm-dev
sudo apt-get install postgresql-client libpq-dev nodejs git

Now, it’s time to setup Ruby. I really like using rbenv, so, we’ll install it along with ruby-build:

git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile

Now, we are ready to get Ruby install, let’s go with the most recent version. Having selected the slowest type of instance at AWS, this is the step that will take the longer, time for a coffee break while this runs:

rbenv install -v 2.3.0
rbenv global 2.3.0
gem install bundler
rbenv rehash

Our instance is almost ready, the remaining step it to create a folder where we want to deploy applications to. We’ll also make sure that folder is writable by the ubuntu user, which is what we’ll use for deployment.

sudo mkdir /var/www
sudo chown ubuntu:ubuntu /var/www

And our instance is ready.

Generate an AMI

We don’t want to perform the initial setup we just did every time we start a new instance; so, we’ll create an Amazon Machine Image out of the current state of our instance; so that we can start new instance with all the software we need already setup!

You’ll notice that your SSH connection to that instance will be terminated because the system reboots when an AMI is created.

Run a database instance – RDS

Our sample Rails application connects to a PostgreSQL database; so, we’ll use Amazon Relational Database Service to manage our database instance.

One last thing to do is to make sure we can connect to our PostgreSQL instance, we’ll find that

We’re done with our RDS instance, it takes a few minutes for it to come live.

Deploy your Rails application with Capistrano

The application we will be setting up is hosted at https://github.com/ccjr/books. The last step in our setup is to add secret keys and database credentials to the host we’re going to deploy the application to. I terminated the instance we used to create our AMI, and started a new instance using that image, let’s connect to is, and add our credentials. Our rails application requires a set of enviornment variables to work properly; however, we don’t want that information to be in git; so, we still need to get it into the host somehow. A simple solution is to define define those environment variables in the /etc/environment file, we’ll add the following lines to it:

SECRET_KEY_BASE=3cd60b..
BOOKS_DB_HOST=HOST.us-east-1.rds.amazonaws.com
BOOKS_DB_USER=master
BOOKS_DB_PASSWD=..

Now, the host is really ready to have our application deployed, I did setup Capistrano in our Rails application, here’s what I added to our Gemfile:

gem "capistrano", "~> 3.4"
gem 'capistrano-rails'
gem 'capistrano-bundler'
gem 'capistrano-rbenv'
gem 'capistrano3-puma'

this is what the config/deploy.rb file looks like:

lock '3.4.0'

set :application, 'books'
set :repo_url, 'git@github.com:ccjr/books.git'
set :user, 'ubuntu'
set :rbenv_ruby, '2.3.0'

set :puma_bind, 'tcp://0.0.0.0:9292'

and the config/deploy/production.rb file looks like:

set :stage, :production

server YOUR_INSTANCE_IP, user: 'ubuntu', roles: %w{app web db}

Our instance is ready to be deployed to; so, from the root of our Rails application, run:

bundle exec cap production deploy

....

INFO [796d2396] Finished in 0.506 seconds with exit status 0 (successful).

Woohoo! The first time you deploy, it will take a few minutes because none of your gems will be in the host, but subsequent deployments should take less than 30 seconds.

Now, we need proof that our application is running, I did set it up to run on port 9292, so, in that host, I will run:

$ curl -I http://0.0.0.0:9292
HTTP/1.1 200 OK
..
X-Request-Id: 8601d978-ce8f-469f-9a3e-33dc107ad5b5
X-Runtime: 0.065309

So, it is running using Puma on port 9292, but we don’t want to type that in a browser, let’s setup a load balancer.

Using a Elastic Load Balancer

I think it makes sense to setup an Elastic Load Balancer in front of any application, as it will help you scale later; so let’s do that:

Now, you’re done, you should see the ELB created, it’s where you can get its DNS Name, it looks like NAME-ID.us-east-1.elb.amazonaws.com, you should be able to hit that URL in a browser in a couple of minutes because it takes some time for the ELB to register a host.

We’re finally done, and have an easy-to-scale – that’s the promise – reachable web application running on AWS.

Next Steps

I can think of a few things to do next, mostly for security reasons:

I’ll leave those things as an exercise for you.

Conclusion

That was not trivial! This is far from a state-of-the deployment infrastructure, but we’ve managed to get our environment up and running. I’ve done this a few times in the past, and have always run into issues, I hope this article will help save you some time when setting this up. Feel free to add comments/questions/corrections in the comments.

by Cloves Carneiro Jr

blog comments powered by Disqus