Blog icon

Vagrant up your Rails development

By Tianwen Chen,
Tianwen Chen
Scroll down to read

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:

$ 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:

$ 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):

# -*- 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:

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):

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:

# -*- 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:

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:

#!/usr/bin/env bash
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
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):

#!/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:

# 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:

#!/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:

# 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:

# -*- 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:

$ 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:

$ vagrant up

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

$ vagrant ssh

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

ubuntu@ubuntu-xenial:~$

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

$ cd /vagrant

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

$ 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:

$ exit

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

$ 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.:

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:

$ source ~/.profile

Then you could run:

$ v rails s -b 0.0.0.0

or

$ 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:

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

Happy programming! Fin.

To find out how reinteractive can turn your web application vision into reality, get in touch with us through our contact form or call us on +61 2 8019 7252.

blog comments powered by Disqus