Deploying Ruby Applications to AWS Elastic Beanstalk with Git
With the release of Ruby container support on AWS Elastic Beanstalk, it is now possible to deploy Rails (or any Rack-based) applications onto the AWS infrastructure in just a few easy steps. The container is backed by Amazon EC2 and automatically provisions the machines to use Phusion Passenger (with nginx under the hood). You can run your own local database with your application, or better yet, you can have Elastic Beanstalk automatically set you up with a MySQL database backed by Amazon Relational Database Service (RDS). All of the machines sit behind a load balancer with auto-scaling support automatically configured so you don’t even have to think about ops. Scaling is built-in.
But enough with the awesome features. Let’s deploy an app!
Setting up the application
Before we can deploy an application to Elastic Beanstalk, we first have to create one. Let’s generate an empty Rails skeleton and set it up against a Git repository. We will be using the repository later to deploy our app.
$ rails new fooapp $ cd fooapp $ git init && git add -A && git commit -m "Initial commit"
Initializing our Elastic Beanstalk environment
At this point we have an application, but we have not yet configured it to deploy to Elastic Beanstalk. In order to do that, we need to install the AWS Elastic Beanstalk command line tool. This package provides us a binary,
eb, which we will use to manage and deploy our applications.
Note that you do need to have Python (2.7 or 3.0) installed in order to use the CLI tools. If you are on OS X or a modern Linux OS, you should already have this installed. If not, visit Python’s website for download information. In order to verify if you have Python on your machine, you can type
We can now initialize our application with the CLI tool by typing
eb init. The tool will ask us for some information about our deployment such as our AWS credentials, the deployment region, and the solution stack we want to deploy. For our case, we want to use the "64bit Amazon Linux running Ruby 1.9.3" stack. The tool also asks if we want to setup an RDS database instance for our environment. Let’s answer "no" on that one for now.
$ eb init To get your AWS Access Key ID and Secret Access Key, visit "https://aws-portal.amazon.com/gp/aws/securityCredentials". Enter your AWS Access Key ID: **** Enter your AWS Secret Access Key: **** Select an AWS Elastic Beanstalk service region. Available service regions are: 2) US West (Oregon) ... Select: (1 to 6): 2 Enter an AWS Elastic Beanstalk application name (auto-generated value is "fooapp"): Enter an AWS Elastic Beanstalk environment name (auto-generated value is "fooapp-env"): Select a solution stack. Available solution stacks are: ... 10) 64bit Amazon Linux running Ruby 1.9.3 Select: (1 to 10): 10 Create an RDS DB Instance? [y/n]: n
Note that this command will create a
.elasticbeanstalk directory in our project and add it to our
.gitignore. This directory contains the private environment configuration data provided above, as well as other customized settings. We then commit this change to our
.gitignore with the following command:
$ git add .gitignore && git commit -m "Ignore .elasticbeanstalk from Git"
You can see more examples of setting up a project with the
eb tool on the Amazon Web Services Blog.
Starting the environment
Now that our environment is configured, we simply need to start it. Starting the environment can take a couple of minutes. During this time, Elastic Beanstalk is spawning a set of EC2 instances, setting up a load balancer, and establishing health monitoring. Fortunately we only have to do this once per environment. Once Elastic Beanstalk is done, we can start the environment with the following command:
$ eb start Starting application "fooapp". Waiting for environment "fooapp-env" to launch. ... Application is available at "fooapp-env-djr9ywimiu.elasticbeanstalk.com".
After a little bit of waiting, our environment should be live. We can use the environment in a web browser with the URL provided above:
You can now also type
eb status to see the status of your environment:
$ eb status URL : fooapp-env-djr9ywimiu.elasticbeanstalk.com Status : Ready Health : Green
Deploying with Git
eb init command we ran above actually did some nifty things behind the scenes. The most notable thing is that it setup a Git subcommand named
aws.push that can push our code to the environment. This means that whenever we need to deploy our app, we just have to type
git aws.push. Since our environment is running, and our app is ready, let’s do that right now!
$ git aws.push
If everything succeeds, you should have initiated a deploy to your environment. Typing
eb status should now tell us that a deploy is in progress:
$ eb status URL : fooapp-env-djr9ywimiu.elasticbeanstalk.com Status : Updating Health : Grey
Eventually, the status will be green, and we will be able to access our new Rails application:
Notice that gems listed in our
Gemfile were automatically installed, assets were precompiled, and, if we had any, database migrations would have been run too. Migrations and asset compilation happen by default for any Rails 3.x application.
All we have to do now is update our application, commit to our Git repository, and execute the command
git aws.push to push our new changes to deploy new versions of the app. Migrations will be run, and our assets will get updated.
Customizing your environment with
To run commands and drop environment variables onto your system via your app during a deployment, you can add a special
.config file to the
.ebextensions directory in your project root. The config file is formatted as YAML, and is documented on the Customizing and Configuring AWS Elastic Beanstalk Environments documentation page.
For reference, here is a sample
.ebextensions/redmine.config that runs some extra hooks for installing Redmine on Elastic Beanstalk. Note that the environment variables are not necessary;
RACK_ENV already default to the values below. We’re just setting them as an example of some config we might want to perform.
# Sample configuration for Redmine Rails installation option_settings: - option_name: BUNDLE_WITHOUT value: "test:development" - option_name: RACK_ENV value: production packages: yum: git:  container_commands: 01_secret: command: rake generate_secret_token
Skipping migrations and asset compilation
I mentioned you can set environment variables using the
option_settings key in the
.ebextensions/*.config files. Two specific variables you might want to set are:
RAILS_SKIP_MIGRATIONS— set this to
trueif you don’t want Elastic Beanstalk to run migrations on your behalf. You may want to manage migrations yourself.
RAILS_SKIP_ASSET_COMPILATION— set this to
trueif you don’t use asset compilation in your Rails app. Note, however, that your Elastic Beanstalk container is already configured with a JS runtime to properly handle asset compilation, you do not have to do anything fancy like install the
therubyracergem. It should just work.
Setting up Amazon RDS
The final-but-very-optional step for using the Ruby solution stack in Elastic Beanstalk is to hook up an RDS Instance to your application. If you are running any kind of real world application, you most likely have some database. If your application uses a relational database, you can take advantage of Amazon RDS for access to a MySQL (or Oracle) database.
Fortunately, the setup for this is extremely simple. Remember when we said "no" to creating an RDS DB Instance when we ran
eb init? Well, we can initialize another application in the exact same way, but instead of no, we can say yes. We will be prompted to provide a password for this database, but everything else will be directly configured by Elastic Beanstalk on our behalf.
How do we configure it with our Rails app, you ask? Easy. All of the DB information will be passed to our application as environment variables. In Rails, this means we can setup our application to use RDS with a simple
production: adapter: mysql2 encoding: utf8 database: <%= ENV['RDS_DB_NAME'] %> username: <%= ENV['RDS_USERNAME'] %> password: <%= ENV['RDS_PASSWORD'] %> host: <%= ENV['RDS_HOSTNAME'] %> port: <%= ENV['RDS_PORT'] %>
The only other thing we have to do is add
mysql2 to our
Gemfile. It would look something like:
And RDS should now be fully configured for our Rails application.
Testing locally with Passenger
Because we use Passenger in your container, it’s really easy to test your deployment locally in a very similar webserver environment. If you don’t already have passenger installed on your local machine, you can set it up and initialize your server with:
$ gem install passenger $ passenger start
A few quick notes if you’ve never deployed to a Passenger server before:
- Passenger relies on a
config.ruRack file in your root application directory by convention.
- Passenger also relies on a
public/directory for your static assets located in your application root.
Passenger needs both of these paths to exist in order to detect your Rack/Rails application. In a Rails 3.x app, these paths already exist, but if you are rolling your own low-level Rack application, you need to make sure that they are created. Once they have been setup, your application is ready to be tested as if it were deployed to Elastic Beanstalk. You can consult the Phusion Passenger documentation for more information about how to run your application on this snappy webserver.
So that’s it. We just went through the nuts and bolts of deploying a Ruby application to AWS Elastic Beanstalk. Although there is a lot here to read through, the steps themselves are pretty simple. In short, you can deploy an application with a few commands:
eb initon your Git-backed application
eb startto load up your environment
git aws.pushto deploy your code to Elastic Beanstalk
Setting up RDS is just a matter of using the configuration values provided to your application through standard environment variables. And of course if you need to dig deeper and customize settings, you can do so by adding a
There is of course plenty you can do with an Elastic Beanstalk application, and there are plenty more things to talk about with the Ruby solution stack, but hopefully this helps you get a Rack-based application up and running in just a few minutes!