Vagrant Provisioning with ruby and node commands
I love sharing my adventures in tests and coding as a way to solidify my learning, but also in the off chance it can help you as well.
This time, I’m going through how to set up Vagrant to provision
processes related to rvm & ruby and nvm & node. The set up allows the
provisioner to even run bundle install
or npm install
as a
provisioning step.
If you want to step up your vagrant (shell) provisioning to another level with ruby and node, this is the article for you!
This article will take you about six minutes or less to read as there are code and vagrant output.
Introduction
After an embarrassing interview with a candidate where I could not run our awesome test suite in front of them, I wanted to get our project running on a automated build system.
For me, this means setting up our system in an automated manner with Vagrant. I just want to have a consistent environment to work from.
It’s easy to install the system level dependencies, like postgresql or even ruby.
Next Level Automation
Even with the system level dependencies installed, I want to get the project’s dependencies installed. Installing nokogiri with native extensions can feel like forever. I want this process to part of during Vagrant’s provisioning process.
This is tricky because Vagrant’s provisioning system is basically scripting out the root user, so installing Ruby management tools like rvm is not useful, the main user in a Vagrant system is vagrant.
To get around this, use the privileged: false option in the provisioning process. This would be scripting out the vagrant user instead of root.
rvm & ruby
Even with this option, installing rvm is easy, but using rvm in a simple script
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.provider 'virtualbox' do |v|
v.memory = 512
v.cpus = 2
end
# install rvm
config.vm.provision :shell, privileged: false, inline: <<-SHELL
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
rvm install 'ruby-2.6.0'
SHELL
end
$ vagrant provision
still errors with:
default: Primary key fingerprint: 7D2B AF1C F37B 13E2 069D 6956 105B D0E7 3949 9BDB
default: GPG verified '/home/vagrant/.rvm/archives/rvm-1.29.9.tgz'
default: Installing RVM to /home/vagrant/.rvm/
default: Adding rvm PATH line to /home/vagrant/.profile /home/vagrant/.mkshrc /home/vagrant/.bashrc /home/vagrant/.zshrc.
default: Adding rvm loading line to /home/vagrant/.profile /home/vagrant/.bash_profile /home/vagrant/.zlogin.
default: Installation of RVM in /home/vagrant/.rvm/ is almost complete:
default:
default: * To start using RVM you need to run `source /home/vagrant/.rvm/scripts/rvm`
default: in all your open shell windows, in rare cases you need to reopen all shell windows.
default: <warn>Thanks for installing RVM 🙏</warn>
default: Please consider donating to our open collective to help us maintain RVM.
default:
default: 👉 Donate: <code>https://opencollective.com/rvm/donate</code>
default: /tmp/vagrant-shell: line 4: rvm: command not found
WHY?
The worst part of is: I can manually log in to the machine, run the same command and it will just work. Why?!
The key: the vagrant provisioner is the vagrant user, but it does not load up ANY profile information, that include bash - why should it?!
Before doing any commands to load up ruby, just execute:
source /home/vagrant/.profile
Making this change in the above script, the whole script becomes:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.provider 'virtualbox' do |v|
v.memory = 512
v.cpus = 2
end
# install rvm AND ruby
config.vm.provision :shell, privileged: false, inline: <<-SHELL
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
source /home/vagrant/.profile
rvm install 'ruby-2.6.0'
SHELL
end
This will give results:
default: gpg: Signature made Wed Jul 10 08:31:02 2019 UTC
default: gpg: using RSA key 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
...
default:
default: 👉 Donate: <code>https://opencollective.com/rvm/donate</code>
default: Searching for binary rubies, this might take some time.
default: Found remote file https://rvm_io.global.ssl.fastly.net/binaries/ubuntu/18.04/x86_64/ruby-2.6.0.tar.bz2
default: Checking requirements for ubuntu.
default: Installing requirements for ubuntu.
default: Updating system
Now in the provisioning script, the provisioner can run ruby commands
like bundle
, rake
, and even rspec
!
...
default: Running: /var/folders/1g/_2d999fd23s3l50kjmvq8_yc0000gn/T/vagrant-shell20190822-58203-hg86p4.sh
default: Already installed ruby-2.6.0.
default: Using rake 12.3.2
default: Using concurrent-ruby 1.1.5
default: Using i18n 1.6.0
...
Now there’s nothing stopping from having a fully built developer machine with Vagrant!
nvm & node
The issue above with rvm also happens with nvm, the node version manager. When nvm is not configured properly in the provisioner, the following error appears:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.provider 'virtualbox' do |v|
v.memory = 512
v.cpus = 2
end
# install nvm
config.vm.provision :shell, privileged: false, inline: <<-SHELL
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
nvm install node
SHELL
end
Like rvm, trying to have nvm install node in the provisioner returns an error:
default: => Downloading nvm from git to '/home/vagrant/.nvm'
=> default:
default: Cloning into '/home/vagrant/.nvm'...
default: => Compressing and cleaning up git repository
default: => Appending nvm source string to /home/vagrant/.bashrc
default: => Appending bash_completion source string to /home/vagrant/.bashrc
default: => Close and reopen your terminal to start using nvm or run the following to use it now:
default:
default: export NVM_DIR="$HOME/.nvm"
default: [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
default: [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
default: /tmp/vagrant-shell: line 2: nvm: command not found
The solution is similar to rvm, load nvm’s configuration into the provision script, the whole script is:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.provider 'virtualbox' do |v|
v.memory = 512
v.cpus = 2
end
# install nvm AND node
config.vm.provision :shell, privileged: false, inline: <<-SHELL
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
nvm install node
SHELL
end
Now the output is:
default: => bash_completion source string already in /home/vagrant/.bashrc
default: => Close and reopen your terminal to start using nvm or run the following to use it now:
default:
default: export NVM_DIR="$HOME/.nvm"
default: [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
default: [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
default: Downloading and installing node v12.9.0...
default: Downloading https://nodejs.org/dist/v12.9.0/node-v12.9.0-linux-x64.tar.xz...
...
default: Now using node v12.9.0 (npm v6.10.2)
default: Creating default alias: default -> node (-> v12.9.0 *)
Conclusion
In my quest to fully automate vagrant provisioning so a developer box can be a single step, I learned:
privileged: false
runs scripts as the vagrant user.- The
privileged: false
mode does not load any user settings, like profiles or paths. - To have the vagrant provisioner run commands that managed services like rvm or nvm uses, just load the bash profile OR follow its commands.
With this, I am a leap closer to having a fully automated build for developer boxes.
I can’t wait for the next team member to join to show them how easy it is to set up our development environment!
I’m always looking for good people to join my team. Even if you’re not looking for a position now, contact me to stay in touch, because you never know!