I like to keep my environment organized and having all the dbs for all projects and some related packages I can only control through the OS package manager wasn't stable enough.

So I tried Docker to create my isolated development virtual machines, but after some tests I found Vagrant more mature, make sense because Vagrant has been here for more time. You could even use both though.

I created a nice isolated environment for a project, so I could also reuse the code of the configuration and keep everything more organized.

In these couple days I've been using Vagrant with Puppet for the config, I had several problems I am still working on, but It's almost done and I am happy about the change.

If you don't know anything about Puppet, this github repository and the puppet tutorial will give you some insight.

Because I have all the configuration management handled by Puppet, I found only three important things to keep in mind for the Vagrant configuration. Depending what you choose the road would be easier or more difficult.

  • The Vagrant box you are gonna use. You can use one with Puppet already installed or not, and different operating systems, there are several to choose from in the vagrantboxes site or you could create your own box. I chose ArchLinux because I use it every day, but I found almost every tutorial and vagrant/puppet config is made for ubuntu, so everything work much easier with this OS because there are more docs.

    Starting with a box (vagrant up) could be painful if you have errors before running puppet or any config, you could check your virtualbox gui virtual machine logs created by Vagrant to sort things out, I had a problem with one where I have to disable usb.

  • In case you go for a vanilla OS box and you want to install Puppet there, you will need to install it via shell, something like:

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

... my puppet config

And in the same directory we could do this (for Arch in this example)

#!/bin/bash
# bootstrap.sh

if ! command -v puppet >/dev/null; then  
  pacman -Sy
  pacman -S --noconfirm ruby  
  gem install --no-user-install puppet
else  
  echo "Puppet already installed"
fi
  • Networks. For some projects you only need to do port forwarding, but for others you might need a private network ip, I have a lot of problems with this in Arch, and I still have to make a workaround for it to work due to an issue, in the end I have to create a symlink manually after the first vagrant crash. In ubuntu worked flawlessly.
$ vagrant ssh

;; Inside the VM
$ sudo ln -sf /dev/null /etc/udev/rules.d/80-net-name-slot.rules

;; Outside the VM
$ vagrant reload --provision

Using debug and verbose options with puppet make everything easier.

//Vagrantfile config

config.vm.provision :puppet do |puppet|  
  puppet.manifests_path = 'puppet/manifests'
  puppet.manifest_file = 'site.pp'
  puppet.module_path = 'puppet/modules' 
  puppet.options = "--verbose --debug"
end  

It's very important to keep Puppet modules isolated, in case you want to use some functionality from one of the modules, split the logic on this one (for example service, configuration and package installation in mysql) and inherit from it. We could go further and define a completely configurable class, but that's beyond the scope of this tutorial. Example:

# In the PHP module php/manifests/init.pp
class php::configure {  
  $php_fpm = '/etc/php/php-fpm.conf'
  $php_ini = '/etc/php/php.ini'

  set_property { 'php-date-timezone':
    property => "date.timezone",
    value => "Europe\\/Amsterdam",
  }

  set_property { 'php-short_open_tag':
    property => "short_open_tag",
    value => "Off",
  }
}

... some definitions/classes after in the same file ...

class php {  
  include php::install
  include php::configure
  include php::service
}
# In my project module project/manifests/init.pp

class selltag::php-config inherits php::configure {  
  # We could use $php_ini and $php_fpm here
  # because we inherited

  $project_name = 'myproject'

  # functions defined below
  set_extension { 'openssl':
    extension => 'openssl'
  }

  set_extension { 'phar':
    extension => 'phar'
  }

  set_extension { 'zip':
    extension => 'zip'
  }
}

We could also define functions to reuse code, for example I defined two to change variables in php.ini and activate php modules. One thing I found out about define inside a class is they won't be inherited:

define set_extension ($extension, $file = '/etc/php/php.ini') {  
  exec { "php-set-${extension}":
      command => "sed -i \'s/^;extension=${extension}.so/extension=${extension}.so/g\' ${file}",
  }
}

define set_property ($property, $value, $file = '/etc/php/php.ini') {  
  exec { "php-set-${property}":
      command => "sed -i \"s/^[; ]*${property} =.*/${property} = ${value}/g\" ${file}",
  }
}

I have my first modules in a Github repository. You can use the ones made already by the community from the Puppet forge, It's better.

If you want to separate better the config from your projects, maybe you could use Hiera, I didn't try it yet though.

Conclusion: I still need to learn a lot about Puppet, but I am happy this could be the beginning of a nice friendship, and It was much easier than I thought. :-)

If you find any errors or I'm wrong about something please let me know!