Rails in AWS Elastic Beanstalk

30th Apr 2019
Left: Amazon Elastic Beanstalk; Right: Ruby on Rails

Project background

The objective of this project is to migrate my Ruby on Rails web app from Heroku to Amazon Web Services (AWS) Elastic Beanstalk (EB).

Heroku and EB are Platform as a service (PaaS).

PaaS provides a platform for software developers to develop, run, and manage applications without the complexity of building and maintaining the infrastructure.

Heroku is a Y-Combinator alumni startup founded in 2007. It is acquired in 2010 by Salesforce.

AWS is a subsidiary of Amazon that provides on-demand cloud computing platforms to individuals, companies, and governments, on a metered pay-as-you-go basis.

The reason I'm migrating my Rails web app from Heroku to EB is that I think in the long run, AWS is more cost effective and scalable.

I do not expect my web app to have Airbnb's traffic. However, it will be wise to build your MVP with AWS scalable infrastructure.

Rails configuration

Ruby: v2.5.x
Rails: v5.2.x
Bundler: v2.0.1
Node: v8.15.0

EB configuration

Platform version: Ruby 2.5 with Puma v2.9.2
Release date: 5th Oct 2018
Ruby: v2.5.5-p157
Bundler: v1.16.6
Node: v4.6.0

Deployment problems

Based on my experiments, I was not able to resolve my configuration problem using .ebextensions method.

My goal is to complete the deployment with the most direct and efficient method, not the conventional method.

Here is my approach:

  1. Create the EB environment with the sample code
  2. Update software configurations directly in EC2
  3. Deploy the finalized software

Here are the commands to create EB environment:

# Initialize Git
git init
git add . && git commit -m 'Init commit'

# Create EB
eb init
eb create ENVIRONMENT_NAME --sample

We start an EB environment with sample code because it will work 100%.

With the environment initialized, we will now fix configuration errors.

Problem 1: Bundler configuration

My Rails web app bundler (v2.0.1) is newer than the server (v1.16.6) version. During my app deployment, this is the first error I encounter:

-------------------------------------
/var/log/eb-activity.log
-------------------------------------
+ bundle install
/opt/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)
  from /opt/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/rubygems.rb:308:in `activate_bin_path'
  from /opt/rubies/ruby-2.5.5/bin/bundle:23:in `[main]' (Executor::NonZeroExitStatus)

[2019-04-27T11:36:07.302Z] INFO  [3193]  - [Application deployment app-********************/StartupStage0/AppDeployPreHook/10_bundle_install.sh] : Activity failed.
[2019-04-27T11:36:07.302Z] INFO  [3193]  - [Application deployment app-********************/StartupStage0/AppDeployPreHook] : Activity failed.
[2019-04-27T11:36:07.302Z] INFO  [3193]  - [Application deployment app-********************/StartupStage0] : Activity failed.
[2019-04-27T11:36:07.302Z] INFO  [3193]  - [Application deployment app-********************] : Completed activity. Result:
  Application deployment - Command CMD-Startup failed
[2019-04-28T03:45:57.797Z] INFO  [8753]  - [CMD-TailLogs] : Starting activity...
[2019-04-28T03:45:57.797Z] INFO  [8753]  - [CMD-TailLogs/AddonsBefore] : Starting activity...
[2019-04-28T03:45:57.797Z] INFO  [8753]  - [CMD-TailLogs/AddonsBefore] : Completed activity.
[2019-04-28T03:45:57.797Z] INFO  [8753]  - [CMD-TailLogs/TailLogs] : Starting activity...
[2019-04-28T03:45:57.797Z] INFO  [8753]  - [CMD-TailLogs/TailLogs/TailLogs] : Starting activity...

The commands to fix bundler:

# SSH to EC2 environment
eb ssh

# Update bundler
gem install bundler —-version 2.0.1

# Error encountered while executing:
# gem install bundler —-version 2.0.1
ERROR:  While executing gem ... (URI::InvalidURIError)
    URI must be ascii only "?gems=\u2014-version"

# Error encountered while executing:
# gem update --system
ERROR:  While executing gem ... (Errno::EACCES)
    Permission denied @ rb_sysopen - /opt/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/rubygems/version.rb

After I resolved bundler issue, I faced Rails (v5.2.x) credential error.

Problem 2: Rails credential error

`/home/webapp` is not a directory.
Bundler will use `/tmp/bundler/home/webapp' as your home directory temporarily.
rake aborted!
ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `rails credentials:edit`
/var/app/ondeck/config/environment.rb:5:in `[top (required)]'
/opt/rubies/ruby-2.5.5/bin/bundle:23:in `load'
/opt/rubies/ruby-2.5.5/bin/bundle:23:in `[main]'
Tasks: TOP => environment
(See full trace by running task with --trace) (ElasticBeanstalk::ExternalInvocationError)
caused by: ++ /opt/elasticbeanstalk/bin/get-config container -k script_dir

This error is new to me as this is my 1st time shipping software to AWS production:slight_smile:

I will not elaborate on the credential errors as there are many well-written explanations and solutions.

Reference: https://stackoverflow.com/questions/51466887/missing-secret-key-base-for-production-environment

Problem 3: RAILS_MASTER_KEY

Bundler will use `/tmp/bundler/home/webapp' as your home directory temporarily.
Missing encryption key to decrypt file with. Ask your team for your master key and write it to /var/app/ondeck/config/master.key or put it in the ENV['RAILS_MASTER_KEY']. (ElasticBeanstalk::ExternalInvocationError)
caused by: ++ /opt/elasticbeanstalk/bin/get-config container -k script_dir

I resolve this RAILS_MASTER_KEY error by adding this variable to EB configuration:

  1. Go to EB console's configuration tab
  2. Add RAILS_MASTER_KEY to Environment properties section

EB console configuration environment properties

Problem 4: NodeJS configuration

[user_name@public_dns_name ~]$ mvn -v
-bash: mvn: command not found
[user_name@public_dns_name ~]$ node -v
v4.6.0
[user_name@public_dns_name ~]$ which node
/usr/bin/node
[user_name@public_dns_name ~]$ nodejs -v
-bash: nodejs: command not found

# NodeJS error
Autoprefixer doesn’t support Node v4.6.0. Update it.
/opt/rubies/ruby-2.5.5/bin/bundle:23:in `load'
/opt/rubies/ruby-2.5.5/bin/bundle:23:in `[main]'
Tasks: TOP => assets:precompile
(See full trace by running task with --trace) (ElasticBeanstalk::ExternalInvocationError)
caused by: ++ /opt/elasticbeanstalk/bin/get-config container -k script_dir

This error occurs due to outdated NodeJS version. The fix is to update node version.

Approach 1: n module


sudo npm cache clean -f
# Install n module
sudo npm install -g n
# Install the latest stable node version
sudo n stable

# n module path
/opt/elasticbeanstalk/support/node-install/node-v4.6.0-linux-x64/bin/n -> /opt/elasticbeanstalk/support/node-install/node-v4.6.0-linux-x64/lib/node_modules/n/bin/n
n@3.0.2 /opt/elasticbeanstalk/support/node-install/node-v4.6.0-linux-x64/lib/node_modules/n

# Select node version using n module
n use 10.15.3

I realize that n module will not help me resolve NodeJS error as it is unable to overwrite EC2 default NodeJS (v4.6.0).

Approach 2: nvm

nvm resolve my NodeJS problem.

Problem 5: PostgreSQL database error

+ su -s /bin/bash -c 'leader_only bundle exec rake db:migrate' webapp
`/home/webapp` is not a directory.
Bundler will use `/tmp/bundler/home/webapp' as your home directory temporarily.
rake aborted!
PG::ConnectionBad: could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
/opt/rubies/ruby-2.5.5/bin/bundle:23:in `load'
/opt/rubies/ruby-2.5.5/bin/bundle:23:in `[main]'
Tasks: TOP => db:migrate
(See full trace by running task with --trace) (ElasticBeanstalk::ExternalInvocationError)
caused by: ++ /opt/elasticbeanstalk/bin/get-config container -k script_dir

To resolve this database error, I create an AWS RDS PostgreSQL database and updated my config/database.yml.

Reference: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_Ruby.rds.html

Useful tips

I notice that if my first deployment fails, I will have a difficult time. I think that EB deployment is faulty if there is no last successful version to rollback.

To ensure that I have at least one working app version to rollback, I created the EB with the sample code as described in my approach.

I have excluded Gemfile.lock because it will mess with deployment at an early step, bundle install.

Final thoughts

Everything should work as they were designed in a perfect world. However, we live in an imperfect world. The most effective solution will be unconventional.

The scariest thing is when a dysfunctional system thinks that it's the perfect system.

I do not own the copyright of this photo. It belongs to The Matrix (1999)

There is nothing wrong with people who choose to take the blue pill. But I will choose the red pill and fight until the end.

EB is an imperfect system, so let's go through its EC2 base's front door, guns blazing. Like this:

Live dangerously:metal: