Install the MEAN stack on Vagrant

Introduction

Vagrant is a software solution that provides an easy way to use, export, and configure virtual machines. This guide introduces you to Vagrant and how it can be used to create a development machine with the MEAN stack.

Requirements

For this guide, we are using Ubuntu 14.04, but the process will be very similar if you use other Linux distribution or Mac OS X (you will need to download the correct Vagrant version for your operating system).

*Note: we are assuming our Vagrant machine will be used for development purposes, so we are not going to go depth about security, user permissions, etc.*

Concepts

Box

Vagrant has a special nomenclature for its virtual machines called boxes. These boxes are special formats for virtual machines managed by Vagrant. To use Vagrant, we need to create a box with an operating system. There are many official boxes that support several providers (CentOS, Ubuntu, Debian, Red Hat, etc). You can [take a look at their catalog](https://atlas.hashicorp.com/boxes/search) to find one that fits your needs.

Provider

Vagrant needs a way to create boxes, so it uses providers. Vagrant actually supports different providers such as VirtualBox, AWS, VMWare, Rackspace, or DigitalOcean (you can read more information about providers in [their documentation page](https://www.vagrantup.com/docs/getting-started/providers.html)).

Host

The host machine is the system that supports a virtual machine. In simple terms and focusing in this particular guide, the host system will be the machine where we start Vagrant (for example, our own machine).

Guest

The guest machine is the virtual machine itself. In this particular guide, our guest machine will be the Vagrant box that we are going to use.

Install Vagrant

To install Vagrant, we need to download the specific package from its downloads page and start the installation. Vagrant is available for Debian, CentOS, Mac OS X and even Windows (each in 32 and 64 bits).

In this particular guide, we are going to use VirtualBox as a provider (which can be downloaded from their page). VirtualBox is going to create some virtual network interfaces to allow both host and guest system to communicate and exchange information, so don’t panic if the installation asks you to install new network interfaces (they will be deleted if you remove VirtualBox in the future).

First steps with Vagrant

Vagrant always needs a file called Vagrantfile where it reads all needed configuration. This file is key for Vagrant and can be exported in order to replicate the same box on other systems.

To create our first virtual machine with Vagrant, we are going to make a new directory and move to it:

mkdir ~/mymachine
cd ~/mymachine

We are doing this because Vagrant will use our current directory to initialize our box, so we prefer to keep our system as clean as possible. Now we execute the `init` command:

vagrant init

This will create an example Vagrantfile. You can also pass a machine name as a parameter like this:

vagrant init ubuntu64/trusty

This will create a predefined Vagrantfile for that machine in particular (in this example we used a very standard Ubuntu 64 bits distribution for the VirtualBox provider).

After that, we just need to start the machine:

vagrant up

If the machine doesn’t exist, Vagrant will download it for us and start it. After the process completes, we can use ssh to connect to it:

vagrant ssh

This will connect us to our recently created virtual machine using a privileged user called `vagrant`.

Please notice that this ssh connection is not needed to work with the virtual machine. After the `vagrant up` command, our virtual machine will be running. If we want to stop our vagrant machine at any point, we can use the `halt` command:

vagrant halt

If we want to remove it from our system, we can use destroy:

vagrant destroy

And the virtual machine will be removed by removing all resources (except the Vagrantfile). It is important to note that vagrant halt is not the same as vagrant destroy.

vagrant halt will stop the box execution without removing it from our system, and vagrant destroy will delete the box and its content (except the Vagrantfile). The process of removing a box can't be undone, so be careful when executing these commands.

Sometimes our virtual machine enters an undesirable state, forcing us to reboot it. We can run the commands halt followed by up, but a more direct way is to execute a reload:

vagrant reload

These commands should be enough to work with Vagrant. You can learn more commands in the documentation.

Setup Vagrant: Vagrantfile

As we said before, Vagrant machines are configured using a file called Vagrantfile. For this guide we are going to create our own Vagrantfile to create a box for the MEAN stack.

A simple example of a basic Vagrantfile could be this:

Vagrant.configure("2") do |config|
# Running standard Ubuntu 64 bits version
config.vm.box = "ubuntu/trusty64"
end

If we run the vagrant up command using this file, Vagrant will create a box running Ubuntu 64bits.

Vagrant has a lot of parameters and you can create very complex boxes with particular rules, but we will keep it simple for this guide. We are going to set up some ports for MongoDB and Node.js, a synced folder to work from host system, and a private network by assigning an IP to our box.

Synced folders

Virtual machines let us create links between host and guest folders, so we can use our host machine folder inside our guest machine and vice versa. Using synced folders, we can work from our host machine without fear of losing our files in case our virtual machine get corrupted and we have to remove it. Let’s see how we can set up a synced folder with Vagrant.

Our working folder will be on our host machine, but Vagrant should be able to read its content (and vice versa in case we create files directly on Vagrant). For this case, we can add a new configuration line to our Vagrantfile where we specify a synced folder:

config.vm.synced_folder "/home/user/myproject", "/projects"

With this line, we are telling Vagrant to link the projects folder (which is inside the virtual machine) with our host folder. If we do anything inside our host folder, it will be reflected on our projects folder inside the virtual machine and vice versa. As you may notice, the virtual machine folder can be named as you want (projects is just an example).

For example, MongoDB stores all its database inside a folder that we set in the mongod.conf (configuration parameter called dbpath). If the Vagrant box becomes unstable and we are forced to remove and create again the same box, we are going to lose our database because it was stored inside the box. We can store the database on our host machine by sharing the MongoDB database folder. Assuming that our database folder is on /home/vagrant/database path, we can use this:

config.vm.synced_folder "/home/user/vagrantdb", "/home/vagrant/database"

Any changes inside the database folder from our Vagrant box will now be replicated to our host folder, much like a backup. If we remove the Vagrant box and create it again, our database will be secure and can be restored easily.

You can sync as many folders as you need, so choose wisely to create copies of critical development files in case your box needs to be removed.

Port configuration

Port configuration is another important configuration step we should take. Our host system should be able to connect to the virtual machine using the ports set by our application. For example, the MEAN stack uses MongoDB as a database that runs on port 27017 by default.

If we want to get access using this port from our host machine (for example, if we want to use some MongoDB administration tool such as Robomongo), this port should be forwarded. To do this, we can add a new line on our Vagrantfile:

config.vm.network "forwarded_port", guest: 27017, host: 27017

In this case, we are telling Vagrant to map port 27017 on the host to the same port number on the virtual machine. You can forward as many ports as you need. For the MEAN stack, we should forward the MongoDB (default: 27017) and Node.js (default: 3000) ports:

config.vm.network "forwarded_port", guest: 27017, host: 27017
config.vm.network "forwarded_port", guest: 3000, host: 3000

We can forward TCP or UDP ports. If we don’t specify a protocol, ports will be forwarded using TCP (which happens to be what we need for the MEAN stack). If we want to forward a port by specifying a protocol, we can add the protocol parameter:

config.vm.network "forwarded_port", guest: 27017, host: 27017, protocol: “udp”

*Note: We should have the port collision problem in mind. We can’t forward ports that are already forwarded (for example, if we are running two Vagrant machines forwarding the same port).*

Private network

Now that we set up ports, we need to create a network between our host and the virtual machine. To create a private network between both systems, we add this line:

config.vm.network "private_network", ip: "192.168.10.2"

This will assign 192.168.10.2 as the internal IP of our virtual machine. Note that IP should be unassigned and from the reserved private space address.

We specified a static IP address, but this IP assignment can be done using DHCP if needed:

config.vm.network "private_network", type: "dhcp"

The DHCP protocol will assign a new IP on each startup. We can see what IP was assigned by running the appropriate command inside the virtual machine (in Ubuntu, we can use ifconfig). For this guide, it is a good idea to set a static IP since we are going to use this Vagrant machine for development.

By setting an IP to our Vagrant box, we are able to connect and use it any time. For example, if we have a Node.js server running on port 3000, we can use our browser to access the Vagrant box with its IP address.

Provisioning

Vagrant has a very interesting feature called provision. Provision is a way to execute tasks when our virtual machine starts. This provision will only be done the first time we do the vagrant up command, but we can force it at any time with this command:

vagrant up --provision

Vagrant currently allow us to provision our boxes using different options, such as Ansible, Chef, Docker, a custom shell script, etc.

For example, we can copy files from our host to our virtual machine on startup using file provisioner. So if we want to import our .gitconfig file (where Git stores our configuration), we can add the following line:

config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig"

We should clarify that this makes a copy of the file, not a link. After the copy process done in the script, any changes on our host file will not be reflected on the Vagrant machine copied file.

For this guide, we want to install our MEAN stack. The "How to install MEAN stack on Ubuntu" guide explains how the MEAN stack needs a few technologies to work properly (Git, MongoDB and Node.js).

The easiest way to install the MEAN stack is by using a script. This script will contain all necessary commands. This file can be saved as provision.sh (as we will see later on), then add this line to our Vagrantfile:

config.vm.provision :shell, :path => "provision.sh"

Now if we execute vagrant up for the first time (or we force it using --provision parameter) Vagrant will execute our script as part of the starting.

This is tremendously helpful. We can have Vagrant boxes already configured with a Vagrantfile and a provision script, so if we need to create new machines, we will only need these two files. Run the vagrant up command and all will be set and running in a few minutes. Our efforts using Vagrant for development purposes are focused on creating a good provisioning script and a perfect Vagrantfile.

Setup Vagrant for MEAN

Now that we've seen how to configure Vagrant, let’s create our custom Vagrantfile to set up a machine that will run the MEAN stack. Summarizing our tasks, we need to:

  • Create a private network between both systems so we can use the web server installed on our Vagrant box.
  • Open ports for MongoDB (default: 27017) and Node.js (default: 3000).
  • Set up a synced folder so our project folder will be on our host system, not on our Vagrant box.
  • Install Git (prerequisite).
  • Install MongoDB.
  • Install Node.js.
  • Install bower and gulp (npm packages).

We've seen how to create a private network, set up a synced folder, and open some ports, so our Vagrantfile will look like this:

Vagrant.configure("2") do |config|
# Running standard Ubuntu 64 bits version
config.vm.box = "ubuntu/trusty64"
# Open ports for Node.js and MongoDB
config.vm.network "forwarded_port", guest: 3000, host: 3000 # Node.js
config.vm.network "forwarded_port", guest: 27017, host: 27017 # MongoDB
# Create a private network, which allows host-only access to the machine
# using a specific IP.
config.vm.network "private_network", ip: "192.168.33.10"
# Set up a synced folder
config.vm.synced_folder "/home/user/myproject", "/project"
# Provision VM only once
config.vm.provision :shell, :path => "provision.sh"
end

Now it's time to create our provision script. As mentioned previously, the easiest way is to compile all needed commands into one single script called provision.sh. If we take all commands from the How to Install MEAN on Ubuntu guide, we have a script like this:

#!/usr/bin/env bash
# Packages
NODE="nodejs"
BUILD_ESSENTIAL="build-essential"
MONGO="mongodb-org"
GIT="git"
# Prerequisites
GIT_INSTALLED=$(dpkg-query -W --showformat='${Status}\n' $GIT | grep "install ok installed")
echo "Checking for $GIT: $GIT_INSTALLED"
if [ "" == "$GIT_INSTALLED" ]; then
apt-get update
apt-get install -y $GIT
fi
# MongoDB
MONGO_INSTALLED=$(dpkg-query -W --showformat='${Status}\n' $MONGO | grep "install ok installed")
echo "Checking for $MONGO: $MONGO_INSTALLED"
if [ "" == "$MONGO_INSTALLED" ]; then
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list
apt-get update
apt-get install -y mongodb-org
fi
# Node.js
NODE_INSTALLED=$(dpkg-query -W --showformat='${Status}\n' $NODE | grep "install ok installed")
echo "Checking for $NODE: $NODE_INSTALLED"
if [ "" == "$NODE_INSTALLED" ]; then
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
apt-get install -y build-essential nodejs
fi
BOWER_INSTALLED=$(npm list --depth 1 --parseable=true --global bower > /dev/null 2>&1)
echo "Checking for $BOWER: $BOWER_INSTALLED"
if [ "" == "$BOWER_INSTALLED" ]; then
npm install -g bower
fi
GULP_INSTALLED=$(npm list --depth 1 --parseable=true --global gulp > /dev/null 2>&1)
echo "Checking for $GULP: $GULP_INSTALLED"
if [ "" == "$GULP_INSTALLED" ]; then
npm install -g gulp
fi

At the beginning, we defined a few variables with packages names that we want to install. As you can see, we also add a few checks before starting each installation.

We mentioned earlier that this provision is done the first time we do a vagrant up command, but we have the ability to force its execution if necessary. If we execute all the commands again, we are wasting time trying to install software that is already on the virtual machine. A simple way to check if a package is installed (e.g., to check if MongoDB is installed) is to execute the following command:

dpkg-query -W --showformat='${Status}\n' mongodb-org | grep "install ok installed"

This will return an "install ok installed" message if package is already installed on the system, or an error message otherwise. Using pipe and grep together to look for this specific message will return an empty string if the package is not currently installed. We could use this idea with an if statement to determine if the package should be installed or skipped.

There are likely other ways to check for package installation, but this is a very simple technique. Feel free to use your own techniques if you have more clever solutions. And if you are not familiar with shell scripting, don’t worry! Linux Academy has a great course called The System Administrator’s Guide to Bash Scripting that will teach you all you need to know.

With the Vagrantfile and the provision file, we have configured a virtual machine using Vagrant. All that's left is to execute a vagrant up command for our Vagrant box to be created and provisioned.

Conclusion

In this guide, we have learnt how to use Vagrant to create a virtual machine for development using the MEAN stack. We reviewed a minimalistic configuration, but your configurations in practice should be dependant of your own application (ports, domains, folders, permissions, etc).

Any configuration that we need can be defined manually when we connect to the virtual machine using ssh, or we can create a few commands and add them to our provision file (in case this onlt needs to be done once, for example).

Vagrant allow us to create complex configurations with multiple boxes, use different providers (such as AWS), create a public network so our Vagrant box will be public to our network, etc. We left a few concepts aside in this guide, but feel free to dig as deep as you want! Vagrant is an excellent tool and it will be extremely helpful in your future developments.

Related links



  • post-author-pic
    Derek M
    02-10-2017

    Awesome guide,  @franverona !

Looking For Team Training?

Learn More