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.