Vagrantfile Explained: Setting Up and Provisioning with Shell

George Fekete
Share

In the introduction, we showed you how to create a Vagrant base box, installing the latest Ubuntu 14.04 LTS in the virtual machine to use it as the guest operating system.

In this part you will learn how to setup a development environment using Vagrant, which you can use and reuse in your development. Note that while you can use the box we created in the previous part for the remainder of this post, you don’t have to – this will all work on any Ubuntu based Vagrant box.

The Vagrantfile

The primary configuration location for any Vagrant development environment is a file called Vagrantfile which you need to place in your project’s folder.

The configuration syntax of this Vagrantfile is Ruby, but you do not need to be a Ruby programmer or have any knowledge of the programming language to write this configuration file. You’ll mostly do basic variable assignment in the configuration.

Every configuration option you will need you can place inside this file.

Let’s go ahead and create a test folder called vagrant-tutorial and inside this folder create the file named Vagrantfile so your folder structure looks like this:

Vagrantfile

About provisioning

The primary purpose of Vagrant is to have a base virtual machine and to give you the framework for creating automatic software installations and configurations in the virtual machine.

By letting Vagrant handle the provisioning of software, it also gives you the flexibility in configuration and more importantly, makes this process repeatable and automatic.

Vagrant doesn’t care how you provision the virtual machine, it offers multiple options ranging from basic shell scripts to software automation managers such as Puppet, Chef or Ansible. You can even configure it to use multiple provisioners at the same time.

Of course there’s always the possibility to vagrant ssh into the base virtual machine and install your required software manually, but that defeats the purpose of Vagrant and all the flexibility it offers when provisioning a box.

Provisioning prerequisites

Before we can start provisioning the base box, we need to set a few required options in our configuration file.

Vagrant API version

Vagrant uses API versions for its configuration file, this is how it can stay backwards compatible. So in every Vagrantfile we need to specify which version to use. The current one is version 2 which works with Vagrant 1.1 and up. Let’s write this block in our Vagrantfile.

Vagrant.configure("2") do |config|
end

Specifying the base box

Next, we need to specify the base Vagrant box we created in the introductory post. That base box is hosted on Vagrant Cloud, so we only need to specify the name and Vagrant will automatically get it from there.

Let’s write this option in the configuration file, inside the block:

config.vm.box = "primalskill/ubuntu-trusty64"

Network configurations

After all this is set up, we need to specify the network configurations. Vagrant has multiple ways of allowing us to communicate with the virtual machine from the outside world, such as public or private network and port forwarding.

We’re going to set up port forwarding for now. Insert the following line into your configuration file:

config.vm.network :forwarded_port, guest: 80, host: 8931, auto_correct: true

What the above line defines is that if you have a web server inside the virtual machine listening on port 80, then it can be accessed from the host machine on port 8931, for example by typing localhost:8931 in your browser.

The auto_correct option set to true tells Vagrant to handle port collisions automatically. There are cases when you have multiple vagrant boxes running with the same port open on the host machine, in these cases Vagrant will automatically resolve the conflicting ports.

If port collisions occur, Vagrant will output the corrections during the vagrant up boot up process.

Syncing project files

A good practice to follow when you’re using a Vagrant development environment, or as a matter of fact any virtualized development environment, is to share your project files between the host and the guest operating systems, so that your project files are not copied into the virtual machine, because if you delete your VM, the files will be lost with it.

Sharing folders between the host and the guest operating systems is very easy to do with Vagrant. Just enter the following config into the Vagrantfile:

config.vm.synced_folder "./", "/var/www", create: true, group: "www-data", owner: "www-data"

Let’s go over the arguments. The first argument is the host machine folder to be shared with the VM, in our case to share the current folder where the Vagrantfile was created. The second argument is the target folder inside the virtual machine.

create: true specifies that if the target folder (/var/www) does not exist, then create it automatically.

group: “www-data” and owner: “www-data” specifies the owner and the group of the shared folder inside the VM. By default most web servers use www-data as the owner accessing the files, it’s a good practice to set the ownership to this user.

VirtualBox specific configurations

Now that we configured the network and synced folders, we should configure the virtual machine itself. Vagrant lets you dynamically modify the VM – you can change the name, memory, etc.

We can do this within the provider block, which in our case is virtualbox. So let’s create the block, set the name and memory:

config.vm.provider "virtualbox" do |v|
    v.name = "SitePoint Test Vagrant"
    v.customize ["modifyvm", :id, "--memory", "1024"]
end

Vagrant uses VBoxManage for setting VM specific parameters before booting it up. For more configuration parameters you can visit the VirtualBox VBoxManage documentation.

The :id is the virtual machine’s ID, which needs to be passed to VBoxManage everytime we want to modify something VM specific. Fortunately, Vagrant handles this for us, so we just need to pass this variable.

Shell script provisioning

The easiest way to provision a base box is to use basic shell script commands which then run inside the virtual machine. This also removes the need for learning or installing Puppet, Ansible and similar tools – even though they can sometimes be more effective provisioners. We’ll deal with them in future articles.

We need to define the provisioning type, which in our case is called shell. Let’s write that inside this block in the configuration file:

config.vm.provision "shell" do |s|
    s.path "provision/setup.sh"
end

Vagrant has two types of shell provisioning, inline and external. With inline you can write shell commands in the Vagrantfile itself, but let’s focus on external provisioning, which simply means to load and run a shell script from a file (relative to Vagrantfile) or even from a URL.

In our case we want to load provision/setup.sh file, let’s create it and write the following in this file:

#!/bin/bash

echo "Provisioning virtual machine..."

Now, run vagrant up and it will output Provisioning virtual machine… on the screen. Note that Vagrant will provision the virtual machine only once on the first run, any subsequent provisioning must be executed with the --provision flag either vagrant up --provision or vagrant reload --provision. The provisioning will re-run also if you destroy the VM and rebuild it with vagrant destroy and vagrant up.

Installing Base Packages

Let the fun begin! Let’s install the base packages, namely: Git, Nginx, PHP-FPM and MySQL. In the provision/setup.sh file append the following lines:

echo "Installing Git"
    apt-get install git -y > /dev/null
    
    echo "Installing Nginx"
    apt-get install nginx -y > /dev/null

Simple as that, but while installing Git and Nginx is straightforward, this is not the case with PHP and MySQL, because each has specific configuration options.

Installing PHP

Unfortunately Ubuntu’s APT (Advanced Packaging Tool) database is not always up-to-date with the latest stable PHP version, therefore we need to switch to a different source when installing it. For this, we need to install a couple of tools before we can actually install PHP itself.

Add the following commands into the setup.sh file:

echo "Updating PHP repository"
    apt-get install python-software-properties build-essential -y > /dev/null
    add-apt-repository ppa:ondrej/php5 -y > /dev/null
    apt-get update > /dev/null

After this, type in the following lines to install PHP and a couple of essential extensions:

echo "Installing PHP"
    apt-get install php5-common php5-dev php5-cli php5-fpm -y > /dev/null
    
    echo "Installing PHP extensions"
    apt-get install curl php5-curl php5-gd php5-mcrypt php5-mysql -y > /dev/null

Installing MySQL

Installing MySQL is even trickier, because the installation process will prompt you for the root password, but Vagrant needs to automate the installation and somehow fill in the password automatically.

For this we need to install a tool called debconf-utils. Go ahead and type in the following lines in setup.sh:

apt-get install debconf-utils -y > /dev/null

Now, we can use this tool to tell the MySQL installation process to stop prompting for a password and use the password from the command line instead:

debconf-set-selections <<< "mysql-server mysql-server/root_password password 1234"
    
    debconf-set-selections <<< "mysql-server mysql-server/root_password_again password 1234"

In the two commands above 1234 is the actual password we set the root password to.

Now we can go ahead and install MySQL without getting the root password prompts:

apt-get install mysql-server -y > /dev/null

If you entered the commands corectly in the files, it should look like this:

Vagrantfile

Vagrantfile config

provision/setup.sh

setup.sh config

Notes

  • At the end of every command, you can see > /dev/null. This simply suppresses the output from the installation processes. If you would like to see the output when provisioning, simply remove it.
  • When you try to install a package using the apt-get install command, it will always ask for confirmation, the -y flag specifies “yes”, so it won’t prompt you for confirming each installation.

Configuring Nginx settings

Now that we installed the neccessary packages for a PHP development environment, we also need to configure Nginx to actually serve the project files.

The easiest way of doing this, is to simply create a file in our synced folder and use that as Nginx’s configuration file.

Let’s create a file called nginx_vhost (no file extension) in our provision folder inside another folder called config. I.e. the path to the file will be provision/config/nginx_vhost.

The file will contain an Nginx basic virtual host configuration:

server {
        listen 80;
        server_name localhost;
        
        root /var/www/src/;
        index index.php index.html;
        
        # Important for VirtualBox
        sendfile off;
        
        location / {
            try_files $uri $uri/ =404;
        }
        
        location ~* \.php {
            include fastcgi_params;
            
            fastcgi_pass unix:/var/run/php5-fpm.sock;
            
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_cache off;
            fastcgi_index index.php;
        }
    }

Insert the following lines into setup.sh to copy this configuration to the Nginx folder:

echo "Configuring Nginx"
    cp /var/www/provision/config/nginx_vhost /etc/nginx/sites-available/nginx_vhost > /dev/null
    
    ln -s /etc/nginx/sites-available/nginx_vhost /etc/nginx/sites-enabled/
    
    rm -rf /etc/nginx/sites-available/default
    
    service nginx restart > /dev/null

The Nginx virtual host configuration points to /var/www/src/ folder as the document root. Let’s create this folder and an index.php file inside it. Also, let’s write the following “mandatory” code inside this file:

<?php echo "Hello World!"; ?>

You final folder structure should look like this:

Folder structure

After running “vagrant up” you should be able to access the “Hello World” page by visiting localhost:8931 in your browser.

Note: All the configuration files are available at https://github.com/primalskill/vagrant-base-config

Conclusion

In this article, you learned how to provision a Vagrant base box using a shell script, installing Nginx, PHP, Git, MySQL and how to configure the web server. In future posts, we’ll look at other provisioning methods.

Got any feedback? Leave it in the comments below!