Most of us use dotfiles to personalise our systems - and I'm no different. I've recently taken to automating away my management and testing of dotfiles by using GitHub, Travis CI and Docker.

Within the last year I finally got bored of manually copying and applying dotfiles between machines, or recreating them whenever I re-installed. My solution to this wasn’t revolutionary:  I put my dotfiles in a repository.

Over the last year I’ve changed the way I manage my dotfiles quite a lot, including making my dotfiles repository public as I often share aspects of my setup with friends and colleagues.

Dotfiles setup script running in terminal

If you don’t already keep your dotfiles in a repository, I’d recommend that you do. I’ve managed to automate most aspects managing, apply and testing my dotfiles. I no longer have to deal with inconsistencies in my setup or apply bad or incomplete dotfile changes.

Setting up a dotfiles repository

The first and obvious step is to create a repository and place your dotfiles in there.

Keep it organised

I’ve gotten to the point where I don’t often need to change my dotfiles and configuration. Keep my repository organised by tool or topic helps me find and manage the dotfiles and configuration files for different tools and utilities.

Use a setup script

Keep your dotfiles in a repository is a good first step. The next big time saver is being able to apply these dotfiles quickly and easily.

The way that I achieved this was with a single setup script that symlinks my dotfiles to the files in the repository.

Install tools and dependencies

Have your setup script install all common tools and dependencies you use all the time. Installing software is boring, so why not automate away that hassle.

Common Scripts

I have a few scripts that I find useful to have on any machine. I happen to keep these in a scripts repository.

My scripts repository is setup as a submodule of my dotfiles repository. I also create symlinks in my local bin folder to the copy in the scripts submodule.

Test It

For a while I use to test my dotfiles setup using a VM with a clean operating system install. I had two problems with this approach:

  • Testing was slow
  • It required manual steps

Within the last couple of weeks I’ve transitioned to a process that now allows me to automatically test my dotfiles repository on every change, quickly, easily and without any manual steps.

I test my dotfiles using Docker, and Travis CI ensures that changes are tested on every push - Build Status.

My Dockerfile is quite simple, under a test user I run the setup script for my dotfiles repository which happens on a clean base image of the OS I am currently using:

FROM ubuntu:16.04
MAINTAINER James Ridgway

# OS updates and install
RUN apt-get -qq update
RUN apt-get install git sudo zsh -qq -y

# Create test user and add to sudoers
RUN useradd -m -s /bin/zsh tester
RUN usermod -aG sudo tester
RUN echo "tester   ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers

# Add dotfiles and chown
ADD . /home/tester/projects/dotfiles
RUN chown -R tester:tester /home/tester

# Switch testuser
USER tester
ENV HOME /home/tester

# Change working directory
WORKDIR /home/tester/projects/dotfiles

# Run setup
RUN ./setup

CMD ["/bin/bash"]

Running the setup script is run as part of the docker build, so if setup fails docker will fail to build the container.

I can test my dotfiles locally by running:

docker build -t docker.james-ridgway.co.uk/dotfiles .

And if I want to inspect the state of the container after a successful setup, I can run the container to get a bash prompt:

docker run -it docker.james-ridgway.co.uk/dotfiles

Travis CI is used to build the docker container:

sudo: required
services:
- docker

# Handle git submodules yourself
git:
    submodules: false

# Use sed to replace the SSH URL with the public URL, then initialize submodules
before_install:
    - sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules
    - git submodule update --init --recursive

script:
- docker build .

My .travis.yml file is fairly straightforward although I have added some special behaviour around submodules, as I use a submodule for my common scripts.

The default environment in which Travis will run your build won’t have access to your private key, so you won’t be able to clone submodules under SSH. The quick fix for this is to:

  1. Disable submodules
  2. Replace SSH clone URLs in .gitmodules with HTTPS clone URLs

And that's it!