Creating reproducible desktop environments for development

When programming and experimenting with new software on my computer, I tend to mess up the entire environment, frequently necessitating a full reinstall of the OS (yeah, I'm good at messing up badly). Having to reinstall my OS over and over again is a huge pain as it is time consuming and repetitive. There are chances that I miss some steps during the reinstall, or forget the special steps that I used to make the old configuration work. I have to keep on solving the same issues over and over again. Because of all these pains, I decided to look for ways to make throwaway and reproducible systems meant for programming and experimenting.

Virtual Machines

I program and experiment inside virtual machines now. The way I do it is to first create a base image. I do this by installing Ubuntu normally. Then I export and save the Ubuntu installation. Think of it as saving a snapshot. This means that if I mess up my current virtual machine, I can import the saved Ubuntu 'snapshot' to arrive at a clean slate without going through the Ubuntu installation steps again. One other advantage is that I can have several virtual machines in parallel used to experiment with different things. The best thing about virtual machines is the ability to save and resume state. This means that at the end of a long day, you can save the state of the virtual machine and shutdown your physical computer. Then, the following day, when you start your computer, you can resume from exactly where you left off by just resuming the virtual machine. This is all very convenient. I happen to use VirtualBox for virtual machines. You can also use VMWare if you prefer.

Version controlled configuration

The virtual machine solution suggested above solves many of the problems with quickly reproducing whole systems. However, importing and exporting virtual machines is a slow process. If you simply relied on virtual machines, you would have do the following every time you want to make a change to the system:

  • Import base machine.
  • Start base machine.
  • Make changes within the machine.
  • Export the machine, which is now your new base machine.

Even worse, the changes you made are not given to the virtual machines that have already been created from the old base image. This is what version-controlled configurations are meant to solve. You can create a Git or Mercurial repository that stores all your configuration files, along with a shell script that when executed, sets up the machine. By using version-control, the version-controlled configuration can be shared among the already existing virtual machines. Then all that is needed to get uniform configuration across the virtual machines is to run some shell scripts that you have written.

I currently version-control all my useful configuration files (e.g. for vim, emacs, git, etc.). My configuration repository contains shell scripts that install and/or update whatever software that I need. For example, in a file named 'apt-requirements.txt', I list all the Ubuntu software that I need. Then, I have a shell script that, when executed, installs or updates software listed in that file. That same shell script also copies configuration files to their appropriate locations, and sets up my SSH keys for GitHub, among other things.

I have found version-control to be very useful in looking back in time to see old configurations. There are times when I want to use a software that I have used before. Because of version-control, I just have to look back at the logs (history) to see what I how set things up before.


The combination of using virtual machines and version-controlled configuration files is very powerful for making quickly reproducible environments. There are, of course, other ways this may be accomplished (for example, using Docker or Nix), virtual machines and configuration files are easily understandable and easily configurable, making them a good starting point in organizing the programming environment. What I currently do is to export a bare bones virtual machine snapshot (i.e. full Ubuntu installation with one regular user set up), and use the version-controlled configuration to configure the newly imported virtual machines that were created based on the base snapshot. I save the version-controlled configuration on GitHub as backup. So far, this arrangement has been working out quite well, and I hope it will work out for you too. Happy hacking!