Why and How to Host your Rails 6 App with AWS ElasticBeanstalk and RDS
Tags: ruby, rds, beginners, elasticbeanstalk
Published: 2020-05-05 15:57:09

*This article was originally written by [Amos Omondi][1] on the [Honeybadger Developer Blog][2].*  

When writing an application, one of the major issues you have to think about is how the application will be shared with the rest of the world.

One common approach has been to launch on Heroku. It's easy to set up and is fully managed. But, it's also common for teams to drop Heroku later.  As their traffic grows, Heroku becomes too expensive and inflexible. 

What if it were possible to deploy a new application with Heroku-like ease without giving up the flexibility and cost-savings that you get from a more general-purpose platform like AWS? It is possible, using Elastic Beanstalk -- a service from AWS.   

In this article, I'm going to walk you through setting up a Rails 6 application and running it on AWS using Elasticbeanstalk as the compute base and RDS (Relational Database Service) - in particular, the Postgres service - as the data store.

At the end of this tutorial, you should be able to do the following:

1. Set up a Rails 6 app with a few routes and run it locally.
2. Create an AWS account.
3. Set up and deploy an app to Elasticbeanstalk using the free-tier resources.

Let's dive in.

## What are Elasticbeanstalk and RDS?

To get a clear idea of what Elasticbeanstalk is and the problem it solves, first, let's talk about Amazon's EC2 offering.

EC2 stands for Elastic Compute Cloud 2. This service allows you to provision VPCs, which are basically just computers, running whichever OS you choose (e.g., Ubuntu). Your app will then live inside this computer and access its resources, such as the file system and RAM, to deliver its tasks. In the end, your app will run similar to how it runs on your local machine, only in a machine owned by Amazon and accessible via the internet using Amazon's infrastructure.

Now, imagine a user named Alice, who has provisioned an instance on EC2. Alice will need to do the following:

- Set up a security group to allow requests to her app. 
- Set up a load balancer. 
- SSH into the instance, set up her app and environment secrets, and so on. 

While this gives you full control of your machine and what and how it runs, sometimes, you want to focus on the app and not the infrastructure. This is where Elasticbeanstalk comes in.

Elasticbeanstalk provides a CLI that makes it easier to do all this and will automate most of it, such as creating security groups and load balancers. While the underlying infrastructure is still EC2, a layer of abstraction is added on top of it, with a visual dashboard that allows you to set up environment variables, databases, and auto-scaling, as well as obtain logs and perform other functions, in a very simple manner.

## What is Rails?

Many tools can be used to get a  web application up and running. Usually, the library or framework you end up using is mostly dictated by the language in which it is programmed.

If your language of choice happens to be Ruby, a popular framework you can choose to use is Rails (officially called [Ruby on Rails](https://rubyonrails.org/)). Rails was created at [Basecamp](https://basecamp.com) in 2003, and over the years, it has evolved into a full-featured and very mature framework that includes almost anything you can think of to build a modern web app.

Some of the things you can build with rails include something as simple as a personal blog to something as complex as [Airbnb](https://airbnb.com) and [Github](https://github.com). I am sure you are familiar with these two companies, and yes, they do run on Rails!

Although this article uses examples of deploying a Rails app to AWS, most of the main concepts remain the same regardless of the language and framework used, such as Python/Django or PHP/Laravel.


## Setting up Rails

> Note that the commands depicted will work on a UNIX/Linux-based system out of the box. If you are on Windows, consider using the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) and/or [Microsoft Windows Terminal](https://github.com/microsoft/terminal).

For starters, verify your Ruby version:

```bash
ruby -v
```

Anything `2.5.0` and above is good to go. If not, go [here](https://www.ruby-lang.org/en/) to get the latest version. I have version 2.6.5 installed.

If everything looks okay with your Ruby installation, go ahead and install Rails.

```bash
gem install rails
```

Once that command runs, confirm your Rails version:

```bash
rails --version
```

If you see anything above 6.0.0, then you're good to go for the rest of this tutorial.

## Setting up Postgres

We will be using Postgres DB as our data store for this tutorial. [Here](https://www.2ndquadrant.com/en/blog/pginstaller-install-postgresql/) is an excellent guide to installing it on any platform.

## Adding and Running Our Code

> We will build a simple API to store movie data, such as the name, year of release, and genre. The API will only have 2 endpoints, GET & POST, for demonstration purposes.

Create a new Rails API app with the following command:

```bash
rails new movie-api --api --database=postgresql
```

> Once the above command runs successfully, make sure to change the directory to the project folder created before running the next commands.

Then, we can run the following command to generate our model:

```bash
rails generate model Movie name:string year:integer genre:string
```

Now let's set up the database and run migrations:

```bash
rails db:setup
```

```bash
rails db:migrate
```

If these two commands are successful, you should be able to see a new migration in the `db/migrate` folder with code similar to the following:

```ruby
class CreateMovies < ActiveRecord::Migration[6.0]
  def change
    create_table :movies do |t|
      t.string :name
      t.integer :year
      t.string :genre

      t.timestamps
    end
  end
end
```

We will then go ahead and add the controller logic code for our API endpoints:

```bash
rails g controller api/Movies
```

Then, add the following code to the file `app/controllers/movies_controller.rb`:

```ruby
class Api::MoviesController < ApplicationController
    # GET /movies
    def show
        @movies = Movie.all
        render json: @movies
    end

    # POST /movies
    def create
        @movie = Movie.new(movie_params)

        if @movie.save
            render json: @movie
        else
            render error: {error: 'Failed to add movie record'}, status: 400
        end
    end

    private

    def movie_params
        params.require(:movie).permit(:name, :year, :genre)
    end
end
```

Let's set up the routes. This code goes into `config/routes.rb.`

```ruby
Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  namespace :api do
    resource :movies
  end
end
```

At this point, you can run a sanity check using the `rails routes` command to verify that everything is working properly. You should see output containing something similar to the following:
![Routes](https://www.honeybadger.io//images/blog/posts/rails-6-aws-elastic-beanstalk/routes.png "API Routes")

Before running our server, let's add some seed data to `db/seeds.rb`:

```ruby
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the Rails db:seed command (or created alongside the database with db:setup).
movies = Movie.create([
    { name: 'Star Wars', year: 1977, genre: 'SCI-FI'}, 
    { name: 'Lord of the Rings', year: 2001, genre: 'Fantasy' }
])
```

Run the following command to add the data to the DB:

```bash
rails db:seed
```

You can now run the API with the following command:

```bash
rails s
```

If you navigate to `http://127.0.0.1:3000/api/movies,` you should see our seed data:

```json
[
    {
        "id": 1,
        "name": "Star Wars",
        "year": 1977,
        "genre": "SCI-FI",
        "created_at": "2020-01-01T10:04:56.100Z",
        "updated_at": "2020-01-01T10:04:56.100Z"
    },
    {
        "id": 2,
        "name": "Lord of the Rings",
        "year": 2001,
        "genre": "Fantasy",
        "created_at": "2020-01-01T10:04:56.108Z",
        "updated_at": "2020-01-01T10:04:56.108Z"
    }
]
```


## Creating Your AWS Account

For starters, go to [this website](aws.amazon.com). If you don't have an account yet, or you haven't signed into one from your browser, you should see a page similar to this:

![AWS Home Page](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/aws_home.png "AWS Home Page")

Go ahead and click the orange `Create an AWS Account` button on the top-right corner (or if you have an account, sign into your console). Once you have filled in the signup form (make sure to pick `Account Type` as `Personal` when filling in your address), you'll be dropped right into the console. Don't forget to verify your email address!

Don't worry if things look overwhelming. The UI is quite simple to navigate once you know where you want to go.

## Set up an IAM User

The next thing we need to do is set up an IAM user. This will give us access to API keys we can use to SSH and access our resources from outside AWS.

It is also a good idea to have a separate IAM user with access to only the resources the user needs instead of using the default admin credentials for security purposes.

On the home page, search for IAM and navigate to the IAM home page.
![IAM Search](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/IAM.png "IAM Search")

On the IAM homepage, under `IAM Resources,` click on `Users: 0.`
![IAM Resources](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/iam_resources.png "IAM Resources")

After that, click `Add User.` You can fill out the user name of your choice and then select the checkbox for `Programmatic access.`
![Programmatic Access](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/programmatic_access.png "Programmatic Access")

On the next screen, select `Attach existing policies directly` and then use the search box to search for `AdministratorAccess.`
![Administrator Access](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/administrator_access.png "Administrator Access")

On the next page, add a name tag so you can identify your user later from the list of IAM credentials:
![tags](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/tags.png "tags")

Finally, on the review page, click on `Create User.`

On the next page, download the CSV file using your credentials. We will need them for the last part.

Once you have the file called `credentials.csv,` you can open it in any spreadsheet app or editor to see the values in it. We are mostly interested in `Access key ID` and `Secret accesss key.`

The last thing you need to do is to go to your HOME folder and create a folder called `.aws`. Inside this folder, place a file called `config.` Notice that the folder name starts with a `.` and the file has no extension. The full path should be something like `/Users/your-user/.aws/config.`

> If you are unable to create the `.aws` folder and `config` file, you can skip it for now. The important thing is to have the CSV file with your credentials on hand for later use.

Place the following into the `config` file:

```text
[profile eb-cli]
region = us-east-1
aws_access_key_id = your-aws-access-key-id
aws_secret_access_key = your-aws-secret-access-key
```

> You can find your AWS region on the top-right corner of the AWS account page when you sign in.

## Create RDS DB

We will now go ahead and create the Postgres DB with which our app will be communicating. Similar to IAM, you can use the searchbox on the home page to search for RDS and navigate to it.

On the home page of RDS, click on `Create database.`
![rds](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/rds.png "rds")

On the next page, select `Standard Create`; then, under `Engine Options,` select `PostgreSQL.`
![DB Create](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/db_create.png "DB Create")

As you continue scrolling, pick `Free tier` under `Templates`, and under `Settings,` let the `DB instance identifier` be `movie-api.` You can leave the `Master username` as `postgres,` but go ahead and add a password.
![DB Settings](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/db_settings.png "DB Settings")

Skip over the sections `DB instance size,` `Storage,` and `Availability & durability.` Under `Connectivity,` select `Additional connectivity configuration` and set `Publicly accessible` to `Yes` and `VPC Security group` to `Create new.`
![Connectivity](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/db_connectivity.png "Connectivity").

Continue and skip over `Database authentication.` Under `Additional configuration,` make sure to add the `Initial database name`; `movie_api_db` will do. Once set, skip everyting else and click `Create Database` at the bottom of the page.

Lastly, back on the RDS dashboard, click on the default group under `VPC Security groups` on the right column:
![Security Group](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/security_group.png "Security Group")

At the bottom of the next page, select `Inbound` and edit the rules to look as follows:
![Inbound rules](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/inbound_rules.png "Inbound rules")

Also, make sure the `Outbound` rules look like the following:
![Outbound rules](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/outbound_rules.png "Outbound rules")

## Create Elasticbeanstalk App

Navigate to the elastic beanstalk home page and click `Create New Application.`
![Elasticbeanstalk](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/elastic_beanstalk_home.png "Elasticbeanstalk")

Fill out the new application form as necessary.
![Application](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/application.png "Application")

You will then see a page with the message `No environments currently exist for this application. Create one now.` Click on `Create one now.`

Next, select `Web server environment.`
![Web server env](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/web_server_environment.png "Web server env")

In the next section, change the `Environment name` to `production-env.` Leave the `Domain` blank. Then, under `Base Configuration,` select `Preconfigured platform` and choose `Ruby` from the dropdown. You can leave `Application code` on `Sample application`; then, go ahead and click `Create environment.` This will take some time, so be patient.
![Base config](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/eb_base_config.png "Base config")

Once done, you should see a page that looks like this:
![Production Env](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/production_env.png "Production Env")

Find the URL provided after the environment ID. Click on it to check out the default Ruby app on elasticbeanstalk. Very soon, your API will be running on the same URL.


## Make Your App Ready for Deployment

To make the app ready for deployment, we need to first configure our web server.

Since Rails ships with Puma, a production-ready web server, as its default server, you can directly edit the config file at `config/puma.rb.`

Edit your file to look like the following:

```ruby
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }

# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together,
# the concurrency of the application would be max `threads` * `workers.`
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
workers ENV.fetch("WEB_CONCURRENCY") { 2 } # <------ uncomment this line

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
preload_app! # <------ uncomment this line

# Allow Puma to be restarted by the `Rails restart` command.
plugin :tmp_restart
```

Also, edit the bottom of `config/database.yml` by commenting out the existing `production` config and using this instead:

```yml
production:
  url: <%= ENV['DATABASE_URL'] %>
```

Lastly, go to your Elasticbeanstalk console on AWS, select the `production-env` environment, and then go to `Configuration.`

On the configuration overview screen, click on `Modify` for the `Software` portion:

![Configuration Overview](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/configuration_overview.png "Configuration Overview").

Next, add `DATABASE_URL` and `RAILS_ENV`, and then click on 'Apply':

![EB env vars](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/eb_env.png "EB env vars")

> Note that your DB URL is built using the `Endpoint` you took note of earlier from the RDS dashboard. It is in the format of `postgresql://postgres:YOURPASSWORD@ENDPOINT:5432/movie_api_db`. If you do not remember the password you chose, you can change it in the `Modify` section of your DB's RDS dashboard.

## Manual Deployment

This is as simple as creating a zip file of your app from the command line and then uploading it on your Elasticbeanstalk console.

First of all, `cd` into the project folder. To create a zip file, you can then run the following command:

```bash
zip -r deploy_1.zip .
```

`deploy_1.zip` will be the name of the zip folder created, and it will appear in your project directory, along with the other files. Done? Excellent. On to AWS.

From the Dashboard of Elasticbeanstalk, click on `Upload and Deploy`:

![Upload and Deploy](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/upload_and_deploy.png "Upload and Deploy")

You can change the version label to something more meaningful:

![Version label](https://www.honeybadger.io/images/blog/posts/rails-6-aws-elastic-beanstalk/version_label.png "Version label")

Once Elasticbeanstalk has finished updating the environment, you can visit your environment URL to see your API running! You can use a free service, such as [this](https://reqbin.com/), to send some requests and populate your DB on AWS.

## Deployment with EB CLI

The Elasticbeanstalk CLI makes most of what you have had to do manually up to this point quite easy to accomplish with a few commands. I'll show you how to set it up and use it on our current project. This all depends on having your IAM user set up correctly, so make sure everything from that step is okay or that you have the CSV with your credentials ready.

For most computers, you should already have Python installed. Installation will, therefore, be as easy as the following:

```bash
pip install awsebcli --user
```

On MacOS, you can also use:

```bash
brew install awsebcli
```

You can read more about installation [here](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install-advanced.html).

Once the installation is finished, `cd` to your project folder and run `eb init.`

Follow the prompts as per your AWS environment. Select a region by entering the correct number selection:

```bash
Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) cn-northwest-1 : China (Ningxia)
14) us-east-2 : US East (Ohio)
15) ca-central-1 : Canada (Central)
16) eu-west-2 : EU (London)
17) eu-west-3 : EU (Paris)
```

For the Ruby version portion, select the relevant version using Puma:

```bash
1) Ruby 2.6 (Passenger Standalone)
2) Ruby 2.6 (Puma)
3) Ruby 2.5 (Passenger Standalone)
4) Ruby 2.5 (Puma)
5) Ruby 2.4 (Passenger Standalone)
6) Ruby 2.4 (Puma)
7) Ruby 2.3 (Passenger Standalone)
8) Ruby 2.3 (Puma)
9) Ruby 2.2 (Passenger Standalone)
10) Ruby 2.2 (Puma)
11) Ruby 2.1 (Passenger Standalone)
12) Ruby 2.1 (Puma)
13) Ruby 2.0 (Passenger Standalone)
14) Ruby 2.0 (Puma)
15) Ruby 1.9.3
```

I picked 2.

Go through the rest of the prompts and select 'no' to using `CodeCommit` and 'no' to set up SSH.

> If you did not set up the AWS config, the CLI will prompt you for your AWS keys. Add them as required.

Once this is done, the CLI will exit. You can then run commands, such as `eb status,` to check the status of the app we deployed.

```bash
Environment details for: production-env
  Application name: movie-api
  Region: us-east-2
  Deployed Version: Deploy 2-2
  Environment ID: e-mab3kjy6pp
  Platform: arn:aws:elasticbeanstalk:us-east-2::platform/Puma with Ruby 2.6 running on 64bit Amazon Linux/2.11.1
  Tier: WebServer-Standard-1.0
  CNAME: production-env.qnbznvpp2t.us-east-2.elasticbeanstalk.com
  Updated: 2020-01-22 23:37:17.183000+00:00
  Status: Ready
  Health: Green
```

To deploy a new version, simply run `eb deploy.`

And, that's it! You can read more about other CLI commands you can try out [here](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3.html)

## Summary

In this tutorial, we have learned how to set up a simple Rails API, how to set up AWS resources, such as Elasticbeanstalk and RDS, and how to deploy the app to use them.

We also covered how to use the Elasticbeanstalk CLI to automate deployments to our cloud app. You have now learned how to get from a working app on your local machine to a working app shared with the world on AWS.

[1]: https://www.honeybadger.io/blog/rails-6-aws-elastic-beanstalk/#authorDetails
[2]: https://www.honeybadger.io/blog/rails-6-aws-elastic-beanstalk/