Blog

Vagrant up your Rails Development

Placeholder Avatar
Tianwen Chen
June 2, 2017

We’ve come a long way with web development technologies. To the point where developing on a virtual machine is now a realistic option as the performance issues that once plagued VMs have all but been eradicated.

As the sole VM management tool, Vagrant utilises several virtualisation tools VirtualBox, VMware, etc.) and provides a whole host of benefits. It makes your development environment as close as production as possible and it takes away the excuse of “It works on my computer”. If you need to work on different projects, you will appreciate not having your machine messed up by different setups. Here I will show you a simple example of spinning up a Rails project using Vagrant.

Caveat: In the guide we refer to a Host Machine as your local environment. A Guest Machine is the virtual machine environment.

1. Create a Git Repository

Where RAILS_PROJECT is seen in the instructions below, feel free to replace with a project name of your choice. You don’t have to create a Rails project using rails new RAILS_PROJECT from the beginning, since this will be covered in later section. So in your terminal window:

bash $ cd RAILS_PROJECT $ git init $ echo "Vagrant up your Rails development" > README.md $ git add . $ git commit -m "initial commit"

2. Initialise VM Box

If you don’t have it already installed, download Vagrant and install (see Installation). Do the same with VirtualBox. Next, in your browser, go to https://atlas.hashicorp.com/boxes/search and find the box you need (e.g. Ubuntu, Heroku Ceder, AWS… you name it). Note that all the boxes come with a version so that you can upgrade them when an update is released. Here, I would like to use ubuntu/xenial64. In a terminal, type:

bash $ vagrant init --box-version=20170113.0.0 ubuntu/xenial64

This generates a configuration file Vagrantfile in the project folder with the following content (you’ll see a lot of comments generated by Vagrant, but we’ve kept it short for easier reading):

```ruby # -- mode: ruby -- # vi: set ft=ruby :

… comments generated by Vagrant

Vagrant.configure(“2”) do |config| # … comments generated by Vagrant config.vm.box = “ubuntu/xenial64” config.vm.box_version = “20170113.0.0” end ```

To forward my current github SSH key to the VM so that I can seamlessly use git in the box (especially for bundle installing private gems), I inserted this line into the configuration block:

ruby config.ssh.forward_agent = true

Although Vagrant has a built-in implementation to sync files between host and guest machines (see Vagrant - Synced Folders), its performance is still not the best. The solution is to simply use NFS (suggested in How to make Vagrant performance not suck):

ruby config.vm.synced_folder '.', '/vagrant', type: "nfs" # this is required if you wish to use NFS under VirtualBox # @see section Prerequisites in https://www.vagrantup.com/docs/synced-folders/nfs.html config.vm.network "private_network", ip: "192.168.33.10"

Now your file should look like:

```ruby # -- mode: ruby -- # vi: set ft=ruby :

… comments generated by Vagrant

Vagrant.configure(“2”) do |config| # … comments generated by Vagrant config.vm.box = “ubuntu/xenial64” config.vm.box_version = “20170113.0.0” config.ssh.forward_agent = true config.vm.synced_folder “.”, “/vagrant”, type: “nfs” config.vm.network “private_network”, ip: “192.168.33.10” end ```

Now that our box is initialised, we need to create the setup script for Rails. You can use either general shell scripts or whatever makes you look like a hipster (e.g. Chef, Docker and etc.). Since I’m from the ice age, I will just show you the shell scripts.

2.1 Ensure Ubuntu Package Index is Up to Date

I’m using Ubuntu for this example so, to make sure I’m using the most current package index, I put these lines into the configuration block of Vagrantfile:

ruby config.vm.provision "shell", inline: <<-SHELL apt-get update SHELL

2.2 Install RVM and Ruby

First we must specify a Ruby version. Create a new file in the main directory for the project called .ruby-version and put in it the following line:

2.3.3

Then we need to create a folder (vagrant) and file (vagrant/install_rvm.sh) to prepare a script for RVM installation (from Using RVM with Vagrant). The contents of the file will be as follows:

bash #!/usr/bin/env bash gpg --keyserver hkp://keys.gnupg.net --recv-keys Matenia Rossides9B6B1796C275462A1703113804BB82D39DC0E3 curl -sSL https://get.rvm.io | bash -s $1

Finally, we need to create a file vagrant/install_ruby.sh to install Ruby with the given version (also from Using RVM with Vagrant):

```bash #!/usr/bin/env bash source $HOME/.rvm/scripts/rvm || source /etc/profile.d/rvm.sh

rvm use –default –install $1

shift

if (( $# )) then gem install $@ fi

rvm cleanup all ```

Now we put them together into our Vagrantfile, and add the following lines:

ruby # install rvm and ruby ruby_version = File.read(File.expand_path(".ruby-version", __dir__)).to_s.chomp config.vm.provision "shell", privileged: false, path: "vagrant/install_rvm.sh", args: "stable" config.vm.provision "shell", privileged: false, path: "vagrant/install_ruby.sh", args: ruby_version

NOTE: that privileged: false is required. Otherwise, the scripts will be run as root.

2.3 Get Rails Ready

We still need to initialise a Rails project and install postgresql. First create a new file vagrant/setup.sh and add the following to it:

```bash #!/usr/bin/env bash # install javascript runtime, which is required by gem uglifier sudo apt-get install -y nodejs nodejs-legacy npm

install postgresql

sudo apt-get install -y postgresql postgresql-contrib postgresql-client libpq-dev # create superuser using current linux user sudo -u postgres createuser -s -e whoami

gem install

cd /vagrant gem install bundler –no-ri –no-rdoc

to check whether Rails has been initialised

# if no, create a Rails project using the folder basename ($1), which is mounted to /vagrant in VM if [ ! -f Gemfile ]; then gem install rails –no-ri –no-rdoc rails new $1 –skip-bundle –no-skip-git –database=postgresql mv $1/* . rm -r $1 fi

all setup in README file could go here

bundle install

bundle exec rake db:create db:migrate db:seed ```

HINT: If everything is executed in this script, no more setup section should be required in the README

Add this, together with port forwarding configuration, to the Vagrantfile:

ruby # make sure that the port Rails uses is forwarded to VM config.vm.network "forwarded_port", guest: 3000, host: 3000 # current working folder basename base_name = `basename $(pwd)` config.vm.provision "shell", privileged: false, path: "vagrant/setup.sh", args: base_name

The complete Vagrantfile should now look like:

```ruby # -- mode: ruby -- # vi: set ft=ruby :

Vagrant.configure(“2”) do |config| config.vm.box = “ubuntu/xenial64” config.vm.box_version = “20170113.0.0”

config.ssh.forward_agent = true

config.vm.synced_folder ".", "/vagrant", type: "nfs"

config.vm.provision “shell”, inline: «-SHELL apt-get update SHELL

# install rvm and ruby ruby_version = File.read(File.expand_path(“.ruby-version”, dir)).to_s.chomp config.vm.provision “shell”, privileged: false, path: “vagrant/install_rvm.sh”, args: “stable” config.vm.provision “shell”, privileged: false, path: “vagrant/install_ruby.sh”, args: ruby_version

# this is required if you wish to use NFS under VirtualBox
# @see section Prerequisites in https://www.vagrantup.com/docs/synced-folders/nfs.html
config.vm.network "private_network", ip: "192.168.33.10"   # make sure that the port Rails uses is forwarded to VM   config.vm.network "forwarded_port", guest: 3000, host: 3000   # current working folder basename   base_name = `basename $(pwd)`   config.vm.provision "shell", privileged: false, path: "vagrant/setup.sh", args: base_name end ```

Save the Vagrant setup in your terminal window:

bash $ git add . $ git commit -m "Getting ready for Vagrant up! "

3. Rails Up

Finally, it’s time to Rails up! Enter the following commands in your terminal:

bash $ vagrant up

This is provisioning and bringing up the box, so it may be a long wait. When it’s done then run:

bash $ vagrant ssh

You should now see your terminal has sshed into the box:

bash ubuntu@ubuntu-xenial:~$

Go to the default working directory in your new Guest Machine:

bash $ cd /vagrant

Start Rails server and allow connections from outside of guest machine, since default domain localhost will restrict connections within guest machine.

bash $ rails s -b 0.0.0.0

Then open http://localhost:3000 as usual to start fancy development with Rails!

You can continue from hereon with your regular development and be able to view the changes you make through the browser.

Closing Down

When you’re ready, you can shut down by holding down CTRL + C in the terminal window to terminate the Rails server. And then type:

bash $ exit

You should now have exited the ssh and you can shut down your new VM with the following command:

bash $ vagrant halt

Bonus

It would be even better if you could create shortcut functions to send commands over SSH to guest machine. Add these lines at the end of your shell startup file, e.g. ~/.profile or ~/.bashrc.:

```bash v () { command=”cd /vagrant; $@” vagrant ssh -c $command }

vb () { command=”cd /vagrant; bundle exec $@” vagrant ssh -c $command } ```

Firstly, from your Host Machine (i.e. yourusername$ not ubuntu@ubuntu-xenial:~$), reload your shell startup file:

bash $ source ~/.profile

Then you could run:

bash $ v rails s -b 0.0.0.0

or

bash $ v rake spec

In this way, you don’t need to repeat yourself typing vagrant ssh then cd /vagrant in order to run a command under the working directory in the guest machine. You will still be able to debug your Rails app at breakpoints you’ve placed in both Rails console/server and rspec/unittest suite, just as you would do on your local machine.

Finish

Don’t forget to save what we have in local machine terminal:

bash $ git add . $ git commit -m "Ready for Rails up!"

Happy programming! Fin.