The Perfect Puppet Setup For Vagrant

by Roman Heinrich

To get the complete picture about the modules here, please take a look at the article
Testing Tricky Nginx Rewrite Rules

So, you're ready to jump in and start automating your server infractructure with some server provisioning software. Let's try Puppet.

I will present you the solution and later discuss, why it's done this way.

Vagrantfile: we specify puppet provisioner in Vagrantfile.
We keep the configs in puppet folder with two subfolders: manifests and modules

config.vm.provision :puppet do |puppet|
  puppet.manifests_path = "puppet/manifests"
  puppet.manifest_file  = "base.pp"
  puppet.module_path    = "puppet/modules"

puppet/manifests/base.pp: despite being quite short, it contains a bunch of Puppet best practices.

# this is the entry point for puppet client
# setup some basic stuff here

# puppet group
group { "puppet":
  ensure => "present",

# the default path for puppet to look for executables
Exec { path => [ "/bin/", "/sbin/" , "/usr/bin/", "/usr/sbin/" ] }

# the default file attributes
File { owner => 0, group => 0, mode => 0644 }

# we define run-stages, so we can prepare the system
# to have basic requirements installed
# http://docs.puppetlabs.com/puppet/2.7/reference/lang_run_stages.html

# first stage should do general OS updating/upgrading
stage { 'first': }

# last stage should cleanup/ do something unusual
stage { 'last': }

# declare dependancies
Stage['first'] -> Stage['main'] -> Stage['last']

# just some packages
package{"tmux": ensure => installed}
package{"curl": ensure => installed}

# a helper script to run puppet
  content => "sudo puppet apply -vv  --modulepath=/tmp/vagrant-puppet/modules-0/ /tmp/vagrant-puppet/manifests/base.pp\n",
  mode    => 0755

# a helper script to run nginx tests
# runs puppet + runs the unit tests
  content => "
    cd /vagrant/nginx_tests && \
    runpuppet && \
    sudo /etc/init.d/nginx restart && \
    sudo /etc/init.d/dnsmasq restart && \
    ruby nginx_test.rb",
  mode    => 0755

# brings the system up-to-date after importing it with Vagrant
# runs only once after booting (checks /tmp/apt-get-update existence)
class update_aptget{
  exec{"apt-get update && touch /tmp/apt-get-updated":
    unless => "test -e /tmp/apt-get-updated"

# run apt-get update before anything else runs
class {'update_aptget':
  stage => first,


# install dnsmasq as last, because it does something to
# the network and installing after dnsmasq would not work
# for a couple seconds.
class {'dnsmasq':
  stage => last,


# we use dnsmasq to allow wildchar testing for subdomains
# /etc/hosts is too limited
class dnsmasq{
  # include classes + declare dependencies in one step
    -> class{"dnsmasq::configs":}
    -> class{"dnsmasq::service":}

class dnsmasq::packages{
  package{"dnsmasq": ensure => installed}

class dnsmasq::configs{
  # all requests to (*.)dawanda.com will go to
    content => "address=/dawanda.com/\n",

class dnsmasq::service{
    ensure    => running,
    subscribe => Class["dnsmasq::configs"]


class nginx{
  include nginx::packages
  include nginx::configs
  include nginx::service
  Class['nginx::packages'] -> Class['nginx::configs']-> Class['nginx::service']

class nginx::packages{
  package { "nginx":
    ensure => installed,

class nginx::configs{
    content => template("nginx/nginx.conf.erb")

class nginx::service{
    ensure    => running,
    subscribe => Class["nginx::configs"]

The layout for those modules is applicable to nearly every software, you might encounter:

[1] You install packages  + install dependencies + download stuff
[2] You modify the configuration files or the downloaded software, create init scripts, folders, etc.
[3] You start your software, and subscribe to changes in config class

To see a more complex real example, check my
Playing With Tokudb-article.