Good news, everyone!
Recently I made some experiments to setup a Rails 6 dev environment using Vagrant.
I started from rails/rails-dev-box and applied some changes.

VM Setup

These are the features of the Vagrant file that I’m using:

  • Ubuntu 18.04 LTS box;
  • ability to install different ruby versions;
  • project files (placed in ./project) kept in sync using rsync.

Vagrantfile (which I keep in ./_vagrant directory):

Vagrant.configure('2') do |config|
  config.vm.box      = 'ubuntu/bionic64' # 18.04 LTS
  config.vm.hostname = 'my-project'
  config.vm.network :forwarded_port, guest: 3000, host: 3000
  config.vm.provider 'virtualbox' do |vb|
    # vb.check_guest_additions = false
    vb.gui    = true
    vb.memory = ENV.fetch('RAILS_DEV_BOX_RAM', 4096).to_i
    vb.name   = 'my-project'
    vb.cpus   = ENV.fetch('RAILS_DEV_BOX_CPUS', 2).to_i
  end
  config.vm.provision 'shell' do |sh|
    sh.env        = { 'RUBY_VERSION' => '2.7.2', 'SWAP_SIZE' => '2G' }
    sh.keep_color = true
    sh.path       = 'bootstrap.sh'
    sh.privileged = false
  end
  config.vm.synced_folder './project', '/project', type: 'rsync', rsync__exclude: ['.git/']
end

Dev Environment Setup:

Main features:

  • RVM setup + Ruby install;
  • chromedriver to run feature specs;
  • install NodeJS (14.x) using nodesource.com;

bootstrap.sh:

#!/usr/bin/env bash

# Suppress command output and exit on error
function check_result {
  echo "--- $1 ---"
  shift
  [email protected] > /dev/null
  result=$?
  test $result -eq 0 || \
    { echo "!!! error: $result"; exit $result; }
}

# Suppress installation steps output
function apt_install {
  echo "--- Installing $1 ---"
  shift
  sudo apt -y install "[email protected]" >/dev/null 2>&1
}

echo '~~~ Bootstrap script ~~~'

echo '--- Adding swap file ---'
sudo fallocate -l $SWAP_SIZE /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap defaults 0 0' | sudo tee -a /etc/fstab

# Prevents "Warning: apt-key output should not be parsed (stdout is not a terminal)".
export APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo -E apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list

check_result 'Updating package information' sudo apt-get -y update

apt_install 'development tools' build-essential autoconf libtool

echo '--- Setup RVM ---'
echo silent >> ~/.curlrc
check_result 'Add RVM key' gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
\curl -sSL https://get.rvm.io | check_result 'Installing RVM' bash -s stable

source $HOME/.rvm/scripts/rvm
check_result "Installing Ruby $RUBY_VERSION" rvm install $RUBY_VERSION

check_result 'Update Ruby gems' gem update --system -N
check_result 'Installing Bundler' gem install bundler -N

apt_install Git git
apt_install SQLite sqlite3 libsqlite3-dev
apt_install Redis redis-server
apt_install 'Nokogiri dependencies' libxml2 libxml2-dev libxslt1-dev
apt_install 'chromedriver' chromium-driver

# PostgreSQL setup
apt_install PostgreSQL postgresql postgresql-contrib libpq-dev
sudo -u postgres createuser --superuser vagrant
cat > /tmp/pg.conf <<- EOF
local   all             postgres                                trust
local   all             all                                     trust
local   replication     all                                     trust
host    all             all             127.0.0.1/32            trust
host    all             all             ::1/128                 trust
host    replication     all             127.0.0.1/32            trust
host    replication     all             ::1/128                 trust
EOF
sudo cp /tmp/pg.conf /etc/postgresql/10/main/pg_hba.conf
sudo systemctl restart postgresql

# NodeJS setup
curl -sL https://deb.nodesource.com/setup_14.x | check_result 'Update NodeJS sources' sudo bash -
apt_install 'NodeJS' nodejs yarn

# Setup locales
sudo update-locale LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LC_ALL=en_US.UTF-8

# Shell utils
cat >> $HOME/.bashrc <<- EOF
alias be="bundle exec"
export DB_HOST="localhost"
export REDIS_URL="redis://localhost:6379/0"
export CHROMEDRIVER_PATH="`which chromedriver`"
cd /project
EOF

echo '--- Project setup ---'
source $HOME/.bashrc
cd /project
check_result 'bundle install' bundle install
check_result 'yarn install' yarn install --check-files
check_result 'rails db:reset' bin/rails db:reset

echo '~~~ Ready! ~~~'

Customizations

To setup MySQL in place of PostgreSQL:

debconf-set-selections <<< 'mysql-server mysql-server/root_password password root'
debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password root'
install MySQL mysql-server libmysqlclient-dev libssl-dev
# Set the password in an environment variable to avoid the warning issued if set with `-p`.
MYSQL_PWD=root mysql -uroot <<SQL
CREATE USER 'rails'@'localhost';
SQL

To install ASDF in place of RVM:

git clone https://github.com/asdf-vm/asdf.git $HOME/.asdf --branch v0.8.0
. $HOME/.asdf/asdf.sh
asdf plugin-add ruby https://github.com/asdf-vm/asdf-ruby.git
asdf install ruby 2.7.2

Project Setup

The database configuration is pretty standard - project/config/database.yml:

default: &default
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db_development

test:
  <<: *default
  database: db_test

Selenium configuration for RSpec (for feature and system specs) - project/spec/support/selenium.rb:

Capybara.register_driver :selenium_chrome_headless do |app|
  Selenium::WebDriver::Chrome::Service.driver_path = ENV['CHROMEDRIVER_PATH'] if ENV['CHROMEDRIVER_PATH']
  capabilities = ::Selenium::WebDriver::Remote::Capabilities.chrome(
    'goog:chromeOptions' => {
      'args': %w[disable-dev-shm-usage disable-gpu headless no-sandbox window-size=1600,1000]
    }
  )
  options = {
    browser: :chrome,
    desired_capabilities: capabilities,
  }
  Capybara::Selenium::Driver.new(app, options)
end

RSpec.configure do |config|
  config.before(:each, type: :feature, js: true) do |_spec|
    Capybara.current_driver = :selenium_chrome_headless
    Capybara.javascript_driver = :selenium_chrome_headless
  end

  config.before(:each, type: :system) do
    driven_by(:selenium_chrome_headless)
  end
end

Usage

  • Start the machine (and create the machine the first time): vagrant up
  • Enter in the machine: vagrant ssh
  • Start the server (then point the browser to localhost:3000): bin/rails s -b 0.0.0.0
  • Watch for changes (in another shell): vagrant rsync-auto
  • Stop the machine: vagrant halt
  • Destroy the machine (and delete the VM): vagrant destroy