Using Docksal for Local Web Development: How I'm integrating Node, Yarn, Jekyll, and Ruby
Docksal has become one of the de facto standards for local DevOps in the PHP, Drupal, and WordPress sphere. Docksal sits on top of Virtual Box and Docker to make it easy and fast to get up and running with your local development environment. Other players in this realm are Lando and ddev.
Kicking the Tires
I've been using Lando for the past several months but I recently decided to kick the tires on Docksal. To me, Docksal makes more sense than Lando in terms of customization and scripting. As Steve Jobs used to say, "it just works" and I would say that for Docksal. I've also found the Docksal Gitter support chat to be very helpful. In addition, I discovered that Lando can eat up large amounts of CPU where that's not the case with Docksal. In general, Docksal is super easy to get up and running with Drupal 7/8, and WordPress.
The specific usage presented here would probably be considered an edge case for Docksal or Lando. However, at its core, Docksal is simply Debian Linux so you can really just install anything you want to tailor to your needs and that is where some custom commands and scripting comes in handy.
Consistency Across Environments
Your specific Docksal config can be added to your git repo and then anyone using that repo will have the exact same local development environment, awesome for teams working on the same project. Thus, no more,"It works on my local but not yours." In Addition, you can closely match what is in your Docksal virtual machine to remote staging and live environments, that's important for debugging, testing, and upgrades. The concept of matching environments also comes in handy when dealing with specific versions of things like PHP, MYSQL, Node, etc...
Docksal excels at custom scripting, AKA "Commands." First, if you need help getting started with Docksal, check out their excellent documentation first. A Docksal command lives in
/.docksal/commands/[my_command]. You can run a custom command by invoking
fin my_command. The name of the command must always match the name of the custom command file. So if you want to run a custom command
fin foobar, then the name of the corresponding file should be called
fin is Docksal specific which is always used to preface anything in the Docksal CLI realm.
For my custom script (which I am using to develop this site with Docksal locally), I want to install Node, NPM, Yarn, RVM, Ruby, Gulp, Vim, and all their dependancies with specific versions.
For Ruby, I'll use RVM, Ruby Version Manager and for Node, I'll use NVM, Node Version Manager. That way, we can really fine tune things. Docksal does come with a default Node version installed but by using NVM, we'll have ultimate control of the version and that can be dropped into Git as well. A Docksal custom command is essentially a Bash script at its core. I didn't know a lot about Bash scripting before I started doing this so I learned in the process.
The beginning of your custom Docksal command file should have the following. In my case, I'll call it "install", so I'll be running it as
#!/bin/bash # A custom install script for RVM, NVM, VIM, Node, and Yarn. # Execute the whole script inside the cli container, # This allows us to not have to prefix everything with "fin exec" #: exec_target=cli ## ## Script usage: ## fin install
The key element from the above is
exec_target=cli which allows us to write bash commands without the need for prefacing everything with
fin exec. That saves a lot of time so it is as if you're actually logged in to the container or
The first thing I'll do is to check to see if NVM is already installed by invoking:
if ! (which nvm); then
This is a true or false to detect if
which nvm returns anything. If not, then run some commands to install it. The rest of the install is standard for installing NVM:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh --no-use | bash export NVM_DIR="$HOME/.nvm" # Load NVM. [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # Load NVM bash_completion. [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # Source / reload profiles. source ~/.bashrc source ~/.nvm/nvm.sh
Now I check for the specific Node version I want to use and if it is not detected, then once again, run an install. You'll note that I sometimes print out things such as
echo ... to give me visual feedback during the install.
# Source / reload profiles for good measure. echo 'Reloading Profiles' source ~/.bashrc source ~/.nvm/nvm.sh # Check for NVM Node 8.12.0, install if it does not exist. if [[ $(node -v) != "v8.12.0" ]]; then nvm install 8.12.0 fi # Check which node versions are installed and show defaults. echo 'Here are the current node versions installed' nvm ls # set the global version of node being used. echo 'Set the default version of node and "use it":' nvm alias default 8.12.0 nvm use
A few notes with regard to the above code.
nvm use, specifies the Node version I have in my
.nvmrc file in the root of my site or theme where Node will be used. The
nvmrc file simply states the preferred Node version, in my case,
v8.12.0. But I have found that
nvm use is not always necessarily persistent, especially in virtual machines so that's why I set a global default node version once it is installed using
nvm alias default 8.12.0. This sets the stage for Yarn when we will install the node modules which confirms to a specific version of node being used at the time.
Spin a Yarn
The next task us to install Yarn. Yarn is a package manager similar to NPM but it is faster and more efficient than using NPM to install node modules. In the code below, we check if Yarn exits and if not install it. Then we remove the
node_modules folder and proceed to install from scratch. This is always a good idea in case there are remnants of previous older Node modules present. After that, we install Gulp CLI, again using Yarn.
# Install Yarn on Debian. if ! (which yarn); then curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list fi # Check for and remove the 'node_modules' folder for good measure before we install. if [ -d "$node_modules" ]; then echo "removing node_modules folder" rm -R node_modules fi yarn install echo "Yarn is installing node modules" yarn global add gulp-cli echo "Yarn is installing gulp CLI"
Next up, I will install RVM or "Ruby Version Manager." RVM is similar to NVM in that you can have multiple versions of Ruby installed and switch based on a given project's needs. The first step is to install a mpapis public key as RVM verifies and signs all releases. Next we detect if RVM is already installed and if not, then install it. We also source RVM afterwords so it is available system-wide and executable.
# Check to see if RVM is installed. if ! (which rvm); then # RVM security validation key. curl -#LO https://rvm.io/mpapis.asc gpg --import mpapis.asc sudo curl -fsSL "https://get.rvm.io" | bash -s stable --auto-dotfiles --autolibs=enable # Make RVM to be bash function in future shells echo 'source /home/docker/.rvm/scripts/rvm' >> ~/.bashrc fi
The next step is to install an RVM Ruby version so we check to see if that version is installed and if not, then install it.
# Check for RVM Ruby 2.4.3 and install if it does not exist. # Install the required Ruby version if [[ "rvm current" != "ruby-2.4.3" ]]; then echo "Installing Ruby $1..." source /home/docker/.rvm/scripts/rvm rvm install "ruby-2.4.3" fi
Next we set the default Ruby version to be used in This project.
# Set the default ruby version to be used. rvm --default use 2.4.3
Install Bundler and Ruby gems
For this step, I will install Bundler which is a dependency manager and installer for Ruby gems. It's the best way to avoid conflicts and ensure that teams are all working with the same gem versions. For bundler to work, you should have a "gemfile" at the root of your repository or theme so bundler knows what to install. To install bundler, I will first check to see if it's installed and then install it if not.
# Check for and install bundler. if ! (which bundle); then gem install bundler fi
Once that is done, we just need to install our gems.
echo "Here are the Ruby and bundler versions being used." ruby -v bundle -v # Install Gems from gemfile. bundle install
The last thing I do is to install VIM as I have found that sometimes I just need to get a visual check for a few files inside a Docksal container.
# Install vim. if ! (which vim); then sudo apt --assume-yes install vim fi
An example usage for VIM inside our Docksal VM:
fin bash cd ~/ vim .bashrc
Wrapping it up
That is the completion of the script, so far I have had really good success using this but it did take a lot of trial and error as I am new to bash scripting of this magnitude. If you have any suggestions on how to improve this, feel free to leave me a comment on Twitter or on the Gist page for this script.
I have also come to the realization that Jekyll is somewhat slow in compiling compared to Hugo so I am probably going to convert this blog to Hugo sometime in the future and I will update the Docksal install script with that information.