Build a rails stack using Chef, Vagrant and Berkshelf

In this post, I am going to be creating a demo Rails stack using Chef, Vagrant and Berkshelf.

Chef is an open source configuration management system, it allows you to treat your infrastructure as a code. Vagrant allows you to create Virtualbox machines, they also have support for Vmware. And, finally Berkshelf handles cookbook dependencies, you can think of it as a bundler for your cookbooks. One of the things I like about Brekshelf is that it comes with set of principles, it really helps you to leverage community cookbooks, I hope to demonstrate that in this blog post.

Manage cookbook dependencies using Berkshelf

I assume that you already have Chef, Vagrant and Berkshelf installed.  Now, lets think about all the different software we need for Rails stack. We need apache, mysql, Ruby, Rails ,git, and we obviously need code repo which we are going to deploy . I created a new app using:

rails new rails_base

I hosted the code on github.  Lets start by creating a new cookbook using Berkshelf.

berks cookbook rails_cookbook

Our first step is to add dependencies to our newly created cookbook, cd into rails_cookbook and add these lines to metadata.rb.

depends "mysql"
depends "ssh_known_hosts"
depends "application_ruby"
depends "database"

Basically we are adding community cookbook dependcies for our cookbook. We’ll use git cookbook to install git, mysql  cookbook will install mysql-client ,server and also ruby driver for mysql. I am also including community cookbook ssh_known_hosts, this cookbook will add github to known hosts, so it doesn’t fail during checkout. Application_ruby is the main cookbook which will install passenger, rails and all the other dependencies for a rails app, like bundler, rake and etc. Database cookbook allows you to manage databases, you can do things like creating users and database they have support for multiple database, in our case we are going to use it for mysql. Since we’ve added these recipes into metada.rb, lets go ahead and use berkshelf to download them. You can do that by doing this command

berks install
Using rails_cookbook (0.1.0)
Using application_ruby (2.1.0)
Using git (2.6.0)
Using dmg (2.0.0)
Using build-essential (1.4.0)
Using yum (2.3.0)
Using windows (1.9.0)
Using chef_handler (1.1.4)
Using runit (1.1.4)
Using mysql (3.0.2)
Using openssl (1.0.2)
Using ssh_known_hosts (1.0.0)
Using partial_search (1.0.2)
Using database (1.4.0)
Using postgresql (3.0.2)
Using apt (1.10.0)
Using aws (0.101.2)
Using xfs (1.1.0)
Using unicorn (1.3.0)
Using apache2 (1.6.6)
Using passenger_apache2 (2.0.4)
Using application (3.0.0)

By default all these cookbook get downloaded to ~/.berkshelf . You can override by setting environment variable BERKSHELF_PATH. Now lets go ahead and create a recipe for our cookbook. Open recipes/default.rb

include_recipe "git"
include_recipe "mysql"
include_recipe "mysql::server"
include_recipe "mysql::ruby"
mysql_connection_info = {:host => "localhost",
                        :username => 'root',
                        :password => 'rootpass'}

This will install git, mysql, mysql server and mysql support for ruby.  My_connection_info hash contains information for root user, it will install mysql and set the root password to ‘rootpass’

Next, we need to create user for our rails app, in my case I will create a username demo. I am going to use chef resources user and group resource to create the user and group. We are also going create ‘demo’ database and ‘demo’ user in mysql.

Mysql database user and OS user

group "demo" do
 gid 505
 action :create # see actions section below
user "demo" do
  uid 505
  gid 505
  action :create 
mysql_database 'demo' do
 connection mysql_connection_info
 action :create
mysql_database_user 'demo' do
  connection mysql_connection_info
  password 'awesome_password'
  action :create

Rails app deployment

Lets focus actual rails components, paste following config in the default.rb

application "" do
  path "/usr/local/www/demo"
  owner "demo"
  group "demo"
  deploy_key node['demo']['deploy_key']
  repository ''
  revision "HEAD"
  rails do
    gems ['bundler']
    database do
    database "demo"
    username "demo"
   password "awesome_password"
   passenger_apache2 do
end is the domain we are going to use for testing.  But We need to create a template for our vhost. So let’s create template in template/default/ . Since application cookbook is just using apache cookbook, I copy pasted the template from this link.

<VirtualHost *:80>
 ServerName <%= @params[:server_name] %>
 ServerAlias <% @params[:server_aliases].each do |a| %><%= a %> <% end %>
 DocumentRoot <%= @params[:docroot] %>
 RewriteEngine On
 <Directory <%= @params[:docroot] %>>
   Options <%= [@params[:directory_options] || "FollowSymLinks" ].flatten.join " " %>
   AllowOverride <%= [@params[:allow_override] || "None" ].flatten.join " " %>
   Order allow,deny
   Allow from all
 <Directory />
   Options FollowSymLinks
   AllowOverride None
 <Location /server-status>
   SetHandler server-status
   Order Deny,Allow
   Deny from all
   Allow from
 LogLevel info
 ErrorLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-error.log
 CustomLog <%= node['apache']['log_dir'] %>/<%= @params[:name] %>-access.log combined
 <% if @params[:directory_index] -%>
 DirectoryIndex <%= [@params[:directory_index]].flatten.join " " %>
 <% end -%>
 RewriteEngine On
 RewriteLog <%= node['apache']['log_dir'] %>/<%= @application_name %>-rewrite.log
 RewriteLogLevel 0
 # Canonical host, <%= @params[:server_name] %>
 RewriteCond %{HTTP_HOST}   !^<%= @params[:server_name] %> [NC]
 RewriteCond %{HTTP_HOST}   !^$
 RewriteRule ^/(.*)$        http://<%= @params[:server_name] %>/$1 [L,R=301]
 RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
 RewriteCond %{SCRIPT_FILENAME} !maintenance.html
 RewriteRule ^.*$ /system/maintenance.html [L]

path “/usr/local/www/demo” is straight forward, it’s going to create the document root and deploy the app in “/usr/local/www/demo”, owner “demo”group “demo”  is user for running rails app, deploy_key  is private key for deploying the app. Since it’s really long, I just created an attribute for deploy key in attributes/default.rb . BTW you should create attributes for like app user and etc. I am keeping it simple so you can visualize all the different attributes in one file. revision “HEAD” mean it’s going to deploy code from HEAD.

rails do
gems ['bundler']&nbsp;&nbsp;&nbsp;<br>database do
  database "demo"
  username "demo"
  password "awesome_password"
passenger_apache2 do

Here is important information about bundler from application ruby cookbook readme file.

For applications that use Bundler, if a Gemfile.lock is present then gems will be installed withbundle install –deployment, which results in gems being installed inside the application directory.

  • gems: an Array of gems to install

  • bundler: if true, bundler will always be used; if false it will never be. Defaults to true if gemsincludes bundler

passenger_apache2  will install passenger. If you type vagrant up now, chef will install and configure all this software on the VM. If you got get an error:

STDERR: touch: cannot touch `/usr/local/www/demo/current/tmp/restart.txt’: No such file or directory  .

Just run vagrant provision it should fix it. Now, If you goto in your browser you should see demo rails app. Alternatively you can edit your hosts file and add this entry should load fine. Which mean vhost is configured and passenger is installed. You can ssh into the VM by doing vagrant ssh to verify everything. As you can see we were able to build different pieces needed by Rails by leveraging community cookbooks. I hope it was helpful! I’ve put the code on github


Subscribe to Our Newsletter

Join our community of DevOps enthusiast - Get free tips, advice, and insights from our industry leading team of AWS experts.