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:
- Set up a Rails 6 app with a few routes and run it locally.
- Create an AWS account.
- 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:
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:
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.
On the IAM homepage, under IAM Resources,
click on Users: 0.
After that, click Add User.
You can fill out the user name of your choice and then select the checkbox for Programmatic access.
On the next screen, select Attach existing policies directly
and then use the search box to search for AdministratorAccess.
On the next page, add a name tag so you can identify your user later from the list of IAM credentials:
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 andconfig
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.
On the next page, select Standard Create
; then, under Engine Options,
select PostgreSQL.
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.
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.
.
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:
At the bottom of the next page, select Inbound
and edit the rules to look as follows:
Also, make sure the Outbound
rules look like the following:
Create Elasticbeanstalk App
Navigate to the elastic beanstalk home page and click Create New Application.
Fill out the new application form as necessary.
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.
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.
Once done, you should see a page that looks like this:
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:
.
Next, add DATABASE_URL
and RAILS_ENV
, and then click on 'Apply':
Note that your DB URL is built using the
Endpoint
you took note of earlier from the RDS dashboard. It is in the format ofpostgresql://postgres:YOURPASSWORD@ENDPOINT:5432/movie_api_db
. If you do not remember the password you chose, you can change it in theModify
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
:
You can change the version label to something more meaningful:
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.