Why and How to Host your Rails 6 App with AWS ElasticBeanstalk and RDS

When you deploy a new Rails app, you typically face a double-bind. If you use an easy platform like Heroku, you could create problems for yourself as your application scales. If you use a more fully-featured platform, you risk wasting time on ops that could be spent on your product. What if you could have both: an easy deployment option that is easy to scale? In this article, Amos Omondi argues that AWS Elastic Beanstalk gives us both, then he shows us everything we need to know to get a Rails 6 app up and running on EB.

  • author photo By Amos Omondi
  • #ruby
  • Mar 25, 2020

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). Rails was created at Basecamp 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 and Github. 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 and/or Microsoft Windows Terminal.

For starters, verify your Ruby version:

ruby -v

Anything 2.5.0 and above is good to go. If not, go here 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.

gem install rails

Once that command runs, confirm your Rails version:

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 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:

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:

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

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

rails db:setup
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:

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:

rails g controller api/Movies

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

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.

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

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

# 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:

rails db:seed

You can now run the API with the following command:

rails s

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

[
    {
        "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. 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

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

On the IAM homepage, under IAM Resources, click on Users: 0. 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

On the next screen, select Attach existing policies directly and then use the search box to search for AdministratorAccess. Administrator Access

On the next page, add a name tag so you can identify your user later from the list of IAM credentials: 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:

[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

On the next page, select Standard Create; then, under Engine Options, select PostgreSQL. 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

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.

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

At the bottom of the next page, select Inbound and edit the rules to look as follows: Inbound rules

Also, make sure the Outbound rules look like the following: Outbound rules

Create Elasticbeanstalk App

Navigate to the elastic beanstalk home page and click Create New Application. Elasticbeanstalk

Fill out the new application form as necessary. 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

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

Once done, you should see a page that looks like this: 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:

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:

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.

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

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:

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

You can change the version label to something more meaningful:

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, 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:

pip install awsebcli --user

On MacOS, you can also use:

brew install awsebcli

You can read more about installation here.

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:

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:

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.

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

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.

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Amos Omondi

    Amos Omondi is a back-end engineer with experience building, deploying and maintaining services across various stacks.

    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial
    “Wow — Customers are blown away that I email them so quickly after an error.”
    Chris Patton, Founder of Punchpass.com
    Start free trial