Vagrant and Ansible tips for freelancers


When you're a freelancer or working on a wide range of projects, it's essential to have a consistent and repeatable way of setting up new development environments. Before the availability of tools like VirtualBox, Vagrant and Ansible, it was almost impossible to do this efficiently and you would ultimately end up working with a development environment that was far from the configuration of the live environment.

Over the past year or two, I've been evolving my own set of Ansible playbooks that allow me to quickly create VirtualBox VMs using Vagrant and Ansible. The beauty of this approach is that the same Ansible playbooks can be used, with some variation, to create a development environment for a client project and later used to create the live environment for the project.

I've documented a few tips here related to using Vagrant in your development environment. My Linux disto of choice is Ubuntu, so most of the following tips are related to running Ubuntu in both desktop and VM environments.

Vagrant Synced Folders Performance

Performance of Vagrant synced folders can be an issue. I started out using the default shared folder implementation until I started working on one of our own web sites that is quite a large statically generated site with many images that need scaling. The performance was extremely poor. I then switched to NFS synced folders and the issue improved significantly.

On a Windows host you do not have the option of NFS. In the same scenario as above, I've used Rsync shared folders with equal success in terms of performance. The disadvantage is that rsync only provides one-way synchronization.

VM Database backup

It's my convention to have a 'db' directory in every project root where I keep SQL dumps and scripts that I need to apply changes to the database directly. My Ansible roles include a task to create a cron job to backup the database in the VM. The one below is for MySQL projects and I have a similar task that gets applied on PostgreSQL projects. This gives me a regular dump of the database, which then gets backed up from my desktop via CrashPlan. I'm then able to recover from a VM crash or I can roll back to an earlier version of the database should the need arise during development.

- name: devel mysqldump cron job
  cron: >
    name='mysqldump'
    minute='*/20'
    job='mysqldump -uroot -p{{ db_root_passwd }} {{ db_schema }} > /vagrant/db/dev-db.sql'
    user='{{ admin_user }}'
  sudo: no

Initial Database Load

When creating a new VM for an existing project, I like the VM to come up with the project running and in a state ready to start work. That includes populating the database with any existing data. Therefore I include some Ansible tasks to look for an existing database dump. Usually this dump will be from the clients live website and may require some database changes after loading. I therefore also have a task to apply any database changes. Below are the Ansible tasks I have for Postgres based projects.

- name: get status of data load script
  stat: path=/vagrant/db/{{ data_load_script }}
  register: data_load

- name: get status of live data to devopment script
  stat: path=/vagrant/db/{{ data_convert_script }}
  register: data_convert

- name: populate database
  shell: psql -U {{ postgresql_admin_user }} -d {{ db_schema }} < /vagrant/db/{{ data_load_script }}
  when: data_load.stat.exists

- name: convert data for dev use
  shell: psql -U {{ db_user }} -d {{ db_schema }} < /vagrant/db/{{ data_convert_script }}
  when: data_convert.stat.exists

Watch tasks

I use grunt and, more recently, gulp as build systems for JavaScript compression, SASS/Compass compiling, live reloading the browser, etc. I initially tried running these inside the VM but had varying results. Firstly, it was very slow for changes to be detected in the filesystem and therefore the time until the web browser refreshed was unworkable. Secondly, it would just stop working altogether. I never investigated the reasons behind this and just decide the best solution was to run the build on the host rather than the guest VM.

Fortunately, with modern package managers, you can still keep your projects isolated when running build tools locally. The Ruby Version Manager (RVM) isolates you gems for SASS/Compass compiling and NPM isolates your node modules for your JavaScript build system.

Email Redirection

On my Vagrant VMs and live servers, I use email service providers like SendGrid to route all outgoing emails. On Vagrant VMs I also like to redirect all outgoing email to my email address so that in situations where I'm working with a copy of the client's live data there are no accidental emails sent to the client or, much worse, their clients. Here's the Ansible tasks and templates I use to redirect and route emails via SendGrid on Vagrant VMs using Exim4.

- name: Install exim4 daemon light
  apt: pkg={{ item }}  state=present
  with_items:
    - exim4-daemon-light

- name: exim4 SendGrid configuration
  template: src=update-exim4.conf.conf.j2 dest=/etc/exim4/update-exim4.conf.conf
  notify: Restart exim4

- name: exim4 SendGrid password
  template: src=passwd.client.j2 dest=/etc/exim4/passwd.client owner=root group=root mode=0644
  notify: Restart exim4

# In Vagrant VMs, redirect all email from server to a specified email address
- name: Redirect all email
  template: src=40_to-address-rewrite.j2 dest=/etc/exim4/conf.d/rewrite/40_to-address-rewrite owner=root group=root mode=0644
  notify: Restart exim4
  when: ansible_hostname == 'vagrant'

update-exim4.conf.conf.j2

dc_eximconfig_configtype='smarthost'
dc_other_hostnames=''
dc_local_interfaces='127.0.0.1 ; ::1'
dc_readhost='{{ server_hostname }}'
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets=''
dc_smarthost='smtp.sendgrid.net::587'
CFILEMODE='644'
dc_use_split_config='true'
dc_hide_mailname='true'
dc_mailname_in_oh='true'
dc_localdelivery='mail_spool'

passwd.client.j2

*:{{ email_service_user }}:{{ email_service_passwd }}

40_to-address-rewrite.j2

*@* {{ email_redirect_address }} Ttr