Using Vagrant for Rails Development
A tutorial for using Vagrant in your Rails development environment on Mac, Linux, and Windows
Overview
This will take about 20 minutes.
Vagrant is a tool to automatically setup a development environment inside a virtual machine on your computer. This means your development environment can exactly match the production server and your coworkers can all run exactly the same software.
Your Rails development environment will stay the same no matter what your development computer setup is like. Plus, when you need to revisit a project 12 months later, you can get up and running within minutes. Future you will thank you for setting it up at the beginning of your project.
We're also going to use Chef which helps us automate how our virtual machine development environment gets setup. It will take care of setting up Ruby and all the other packages on our system. It's pretty rad.
Enough of a sales pitch, let's get to it.
Setting Up Vagrant
Make sure you have 1GB or 2GB of free RAM on your computer before proceeding because Vagrant runs a full operating system inside a virtual machine to run your Rails applications.
The first step is to install Vagrant and VirtualBox on your computer.
VirtualBox is where your virtual machines will run. They will be headless which means that they will run in the background and you will interact with them over SSH.
Next we're going to install two plugins for Vagrant.
vagrant-vbguest
automatically installs the host's VirtualBox Guest Additions on the guest system.vagrant-librarian-chef
let's us automatically run chef when we fire up our machine.
vagrant plugin install vagrant-vbguest
vagrant plugin install vagrant-librarian-chef-nochef
This can take a while.
Create the Vagrant config
First off, hop into a Rails project that you want to setup Chef for and run the following commands.
cd MY_RAILS_PROJECT # Change this to your Rails project directory
vagrant init
touch Cheffile
This will create both the Vagrantfile
and Cheffile
for us to customize.
Your Cheffile
Now we're going to setup our Cheffile
. This file is just like your Rails Gemfile but for Chef. This file defines the Chef cookbooks that we will use in our project. Later on in the Vagrantfile
we will tell Vagrant how to use these cookbooks to setup our environment.
We just want to paste the following code into our Cheffile
:
site "http://community.opscode.com/api/v1"
cookbook 'apt'
cookbook 'build-essential'
cookbook 'system'
cookbook 'ruby_build'
cookbook 'ruby_rbenv', '~> 1.1.0'
cookbook 'postgresql', git: 'https://github.com/ChowOps/chef-postgresql'
cookbook 'vim'
Your Vagrantfile
Our Vagrantfile
defines the operating system and Chef configuration for our virtual machine.
We're going to be using Ubuntu 16.04 xenial 64-bit (change to "xenial32" if you need to use 32-bit) with 2GB of memory. It is also going to forward port 3000 from the virtual machine to our computer so when we run rails server
we can access the server inside the virtual machine from our regular browser. Last but not least, we have Chef setup Ruby 2.2.1 and MySQL inside our VM.
You can replace the Vagrantfile
contents with the following:
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
REQUIRED_PLUGINS = %w(vagrant-vbguest vagrant-librarian-chef-nochef)
plugins_to_install = REQUIRED_PLUGINS.select { |plugin| not Vagrant.has_plugin? plugin }
if not plugins_to_install.empty?
puts "Installing required plugins: #{plugins_to_install.join(' ')}"
if system "vagrant plugin install #{plugins_to_install.join(' ')}"
exec "vagrant #{ARGV.join(' ')}"
else
abort "Installation of one or more plugins has failed. Aborting. Please read the Bike Index README."
end
end
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/xenial64"
# Configure the virtual machine to use 1.5GB of RAM
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "1536"]
end
# Forward the Rails server default port to the host
config.vm.network :forwarded_port, guest: 3000, host: 3000
# Use Chef Solo to provision our virtual machine
config.vm.provision :chef_solo do |chef|
chef.cookbooks_path = ["cookbooks", "site-cookbooks"]
chef.add_recipe "apt"
chef.add_recipe "build-essential"
chef.add_recipe "system::install_packages"
chef.add_recipe "ruby_build"
chef.add_recipe "ruby_rbenv::user"
chef.add_recipe "ruby_rbenv::user_install"
chef.add_recipe "vim"
chef.add_recipe "postgresql::server"
chef.add_recipe "postgresql::client"
chef.json = {
rbenv: {
user_installs: [{
user: 'ubuntu',
rubies: ["2.4.0"],
global: "2.4.0",
gems: {
"2.4.0" => [{ name: "bundler" }]
}
}]
},
system: {
packages: {
install: ["redis-server", "nodejs", "libpq-dev"]
}
},
postgresql: {
:pg_hba => [{
:comment => "# Add vagrant role",
:type => 'local', :db => 'all', :user => 'ubuntu', :addr => nil, :method => 'trust'
}],
:users => [{
"username": "ubuntu",
"password": "",
"superuser": true,
"replication": false,
"createdb": true,
"createrole": false,
"inherit": false,
"login": true
}]
}
}
end
end
Running Vagrant
Now that we've got Vagrant and Chef configured properly, we'll start up the Vagrant virtual machine and ssh into it.
# The commented lines are the output you should see when you run these commands
vagrant up
#==> default: Checking if box 'ubuntu/xenial64' is up to date...
#==> default: Clearing any previously set forwarded ports...
#==> default: Installing Chef cookbooks with Librarian-Chef...
#==> default: The cookbook path '/Users/chris/code/test_app/site-cookbooks' doesn't exist. Ignoring...
#==> default: Clearing any previously set network interfaces...
#==> default: Preparing network interfaces based on configuration...
# default: Adapter 1: nat
#==> default: Forwarding ports...
# default: 3000 => 3000 (adapter 1)
# default: 22 => 2222 (adapter 1)
#==> default: Running 'pre-boot' VM customizations...
#==> default: Booting VM...
#==> default: Waiting for machine to boot. This may take a few minutes...
# default: SSH address: 127.0.0.1:2222
# default: SSH username: vagrant
# default: SSH auth method: private key
# default: Warning: Connection timeout. Retrying...
#==> default: Machine booted and ready!
#==> default: Checking for guest additions in VM...
#==> default: Mounting shared folders...
# default: /vagrant => /Users/chris/code/test_app
# default: /tmp/vagrant-chef-1/chef-solo-1/cookbooks => /Users/chris/code/test_app/cookbooks
#==> default: VM already provisioned. Run `vagrant provision` or use `--provision` to force it
vagrant ssh
#Welcome to Ubuntu 16.04 LTS (GNU/Linux 3.13.0-24-generic x86_64)
#
# * Documentation: https://help.ubuntu.com/
#
# System information disabled due to load higher than 1.0
#
# Get cloud support with Ubuntu Advantage Cloud Guest:
# http://www.ubuntu.com/business/services/cloud
#
#
#vagrant@vagrant-ubuntu-xenial-64:~$
The first time you run vagrant up
will take a while because it will provision your virtual machine with the chef configuration. After the first time, vagrant up
won't have to run Chef and it will boot much faster.
If you ever edit your Vagrantfile
or Cheffile
, you can use the following command to reconfigure the machine.
vagrant provision
Using Rails inside Vagrant
Vagrant sets up the /vagrant
folder as a shared directory between the virtual machine and your host operating system. If you cd /vagrant
and run ls
you will see all the files from your Rails application.
bundle
to install all your gems inside the virtual machine.
rbenv rehash
to make sure the executables from the gems we just installed (like rails) are available.
rake db:create && rake db:migrate
to create and migrate your database.
rails server
from this directory will run your Rails application on port 3000. You will still be able to access Rails just like usual on localhost:3000.
Conclusion
Vagrant is an awesome tool for making a portable development environment that lives with your code. With a pretty simple configuration using Chef, we can be up and running in absolutely no time.
If you need to setup your Vagrant machine again or one of your coworkers needs to get setup, all you have to do is run vagrant up
to have your entire virtual machine ready to go.